Newer
Older
/*
* _____________________________________________________________________________
*
* INAF - OATS National Institute for Astrophysics - Astronomical Observatory of
* Trieste INAF - IA2 Italian Center for Astronomical Archives
* _____________________________________________________________________________
*
* Copyright (C) 2016 Istituto Nazionale di Astrofisica
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License Version 3 as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package it.inaf.ia2.tsm;
import it.inaf.ia2.tsm.datalayer.DBBroker;
import it.inaf.ia2.tsm.datalayer.DBBrokerFactory;
import it.inaf.ia2.tsm.datalayer.DBWrapper;
Sonia Zorba
committed
import it.inaf.ia2.tsm.datalayer.DataTypeMode;
import it.inaf.ia2.tsm.model.ColumnModel;
import it.inaf.ia2.tsm.model.TableModel;
import it.inaf.ia2.tsm.model.SchemaModel;
import it.inaf.ia2.tsm.model.SchemaModels;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Sonia Zorba {@literal <zorba at oats.inaf.it>}
*/
public class TapSchema implements EntitiesContainer<Schema>, Serializable {
public static final String STANDARD_NAME = "TAP_SCHEMA";
// Mandatory tables constants
public static final String TABLES_TABLE = "tables";
public static final String SCHEMAS_TABLE = "schemas";
public static final String COLUMNS_TABLE = "columns";
public static final String KEYS_TABLE = "keys";
public static final String KEY_COLUMNS_TABLE = "key_columns";
public static final String DESCRIPTION_KEY = "description";
private static final long serialVersionUID = 1678083091602571256L;
private static final Logger LOG = LoggerFactory.getLogger(TapSchema.class);
private final Map<String, Schema> schemas;
private final Set<Key> allKeys;
private boolean loading;
private DBWrapper dbWrapper;
private String dbName;
private String name;
private boolean exists;
Sonia Zorba
committed
private TapSchemaSettings settings;
private DataTypeMode dataTypeMode;
private transient DBBroker sourceDBBroker;
private transient DBBroker tapSchemaDBBroker;
private ConsistencyChecks consistencyChecks;
public final DBBroker getSourceDBBroker() {
if (sourceDBBroker == null) {
Sonia Zorba
committed
sourceDBBroker = DBBrokerFactory.getDBBroker(dbWrapper.getSourceDataSourceWrapper(), dataTypeMode);
}
return sourceDBBroker;
}
public final DBBroker getTapSchemaDBBroker() {
if (tapSchemaDBBroker == null) {
Sonia Zorba
committed
tapSchemaDBBroker = DBBrokerFactory.getDBBroker(dbWrapper.getTapSchemaDataSourceWrapper(), dataTypeMode);
}
return tapSchemaDBBroker;
}
private TapSchema() {
// for serialization
schemas = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
allKeys = new HashSet<>();
//consistencyChecks = new ConsistencyChecks();
public TapSchema(DBWrapper dbWrapper, TapSchemaSettings settings, boolean exists) throws SQLException {
this();
this.dbWrapper = dbWrapper;
this.exists = exists;
Sonia Zorba
committed
this.settings = settings;
// Don't change the instructions order!
loadDBName();
loadName();
Sonia Zorba
committed
dataTypeMode = getTapSchemaModel().getDataTypeMode();
private void loadDBName() {
// Detect if the TAP_SCHEMA version supports dbmodel
SchemaModel tapSchemaModel = SchemaModels.getTapSchemaModel(settings.getTapSchemaVersion());
boolean hasDBName = tapSchemaModel.getTable(SCHEMAS_TABLE).get("dbname") != null;
if (hasDBName && !STANDARD_NAME.equals(settings.getTapSchemaName())) {
dbName = settings.getTapSchemaName();
}
}
private void loadName() {
if (dbName != null) {
name = STANDARD_NAME;
} else {
name = settings.getTapSchemaName();
}
}
public final void load() throws SQLException {
loading = true;
// Initializing schemas map
for (String schemaName : getSourceDBBroker().getAllSchemaNames()) {
schemas.put(schemaName, null);
}
schemas.put(getName(), null); // the TAP_SCHEMA contains itself
consistencyChecks = TapSchemaLoader.loadExistingTapSchema((this));
loading = false;
public DBBroker getDBBroker(String schemaName) {
Sonia Zorba
committed
if (schemaName.equals(getName())) {
return getTapSchemaDBBroker();
} else {
return getSourceDBBroker();
}
}
/**
* The name of the TAP_SCHEMA schema.
*/
public final String getName() {
}
/**
* The version selected for this TAP_SCHEMA.
*/
public String getVersion() {
Sonia Zorba
committed
return settings.getTapSchemaVersion();
}
public DataTypeMode getDataTypeMode() {
return dataTypeMode;
}
private void loadSchemaKeysMetadata(String schemaName) throws SQLException {
allKeys.addAll(getDBBroker(schemaName)
.getKeys(this, schemaName, getRealSchemaName(schemaName)));
public Set<Key> getAllKeys() {
return allKeys;
}
/**
* {@inheritDoc}
*/
@Override
public final Schema addChild(String schemaName) throws SQLException {
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
LOG.debug("Adding schema {}", schemaName);
Schema schema;
if (!schemas.containsKey(schemaName)) {
schema = null;
} else {
schema = schemas.get(schemaName);
if (schema == null) {
schema = new Schema(this, schemaName);
schema.setStatus(Status.ADDED_NOT_PERSISTED);
schemas.put(schemaName, schema);
loadSchemaKeysMetadata(schemaName);
} else {
switch (schema.getStatus()) {
case TO_REMOVE:
schema.setStatus(Status.ADDED_PERSISTED);
break;
case REMOVED_NOT_PERSISTED:
schema.setStatus(Status.ADDED_NOT_PERSISTED);
break;
default:
throw new IllegalArgumentException("Cannot add the schema " + schemaName + ". Invalid status. Schema status is " + schema.getStatus());
}
}
checkKeys();
}
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
return schema;
}
/**
* {@inheritDoc}
*/
@Override
public void removeChild(String schemaName) {
LOG.debug("Removing schema {}", schemaName);
if (!schemas.containsKey(schemaName)) {
throw new IllegalArgumentException("The database doesn't contains a schema named " + schemaName);
}
Schema schema = schemas.get(schemaName);
if (schema == null || schema.getStatus() == Status.LOADED) {
throw new IllegalArgumentException("Cannot remove the schema " + schemaName + ". It has never been added.");
}
if (schema.getStatus() == Status.ADDED_NOT_PERSISTED) {
schema.setStatus(Status.REMOVED_NOT_PERSISTED);
} else if (schema.getStatus() == Status.ADDED_PERSISTED) {
schema.setStatus(Status.TO_REMOVE);
}
checkKeys();
}
/**
* {@inheritDoc}
*/
@Override
public final Schema getChild(String childName, Status... statuses) {
return TSMUtil.getChild(schemas, childName, statuses);
}
/**
* {@inheritDoc}
*/
@Override
public List<Schema> getChildren(Status... statuses) {
return TSMUtil.getChildrenByStatus(schemas.values(), statuses);
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getAddableChildrenNames() {
return TSMUtil.getAddableChildrenNames(schemas);
}
@Override
public boolean isAddable(String childName) {
return schemas.containsKey(childName);
}
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/**
* {@inheritDoc}
*/
@Override
public List<Schema> getAddedChildren() {
return getChildren(Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED);
}
/**
* {@inheritDoc}
*/
@Override
public List<Schema> getAddedOrRemovedChildren() {
return getChildren(Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED, Status.TO_REMOVE, Status.REMOVED_NOT_PERSISTED);
}
/**
* This method has to be used after TAP_SCHEMA modifications are committed
* to the database, in order to remove from the memory the schemas with
* status {@code Status.TO_REMOVE} or {@code Status.REMOVED_NOT_PERSISTED}.
*/
public void cleanSchema(String schemaName) {
if (!schemas.containsKey(schemaName)) {
throw new IllegalArgumentException("The TAP_SCHEMA doesn't contain the schema " + schemaName);
}
schemas.put(schemaName, null);
}
public SchemaModel getIvoaSchemaModel() {
Sonia Zorba
committed
if (settings.isHasObscore()) {
return SchemaModels.getIvoaSchemaModel(settings.getObscoreVersion());
public void addEntireSchema(String schemaName) throws SQLException {
Schema schema = addChild(schemaName);
for (String tableName : schema.getAddableChildrenNames()) {
Table table = schema.addChild(tableName);
for (String columnName : table.getAddableChildrenNames()) {
table.addChild(columnName);
}
}
}
/**
* Save or update the TAP_SCHEMA changes into the database.
*/
public void save() throws SQLException {
DBBroker broker = getTapSchemaDBBroker();
if (!exists) {
SchemaModel tapSchemaModel = getTapSchemaModel();
broker.createTapSchemaStructure(getRealName(), tapSchemaModel);
// Adding TAP_SCHEMA into TAP_SCHEMA
Sonia Zorba
committed
addEntireSchema(getName());
fillColumnProperties(tapSchemaModel, getName());
Sonia Zorba
committed
if (settings.isHasObscore()) {
fillKeyIds();
broker.save(this);
exists = true;
// Clean inconsistency
consistencyChecks = null;
}
public void createAndAddIvoaSchema() throws SQLException {
SchemaModel ivoaSchemaModel = getIvoaSchemaModel();
// ivoa schema has to be created into source database
getSourceDBBroker().createIvoaSchemaStructure(ivoaSchemaModel);
// Initializing ivoa schema slot in schemata maps
schemas.put(ivoaSchemaModel.getName(), null);
// Add ivoa schema into TAP_SCHEMA
addEntireSchema(ivoaSchemaModel.getName());
fillColumnsProperties(ivoaSchemaModel);
}
/**
* Retrieve the maximum key id from all the schemas that are added into the
* TAP_SCHEMA.
*
* @return the maximum key, if it exists, zero otherwise.
*/
private int getMaxKeyId() {
int maxKeyId = 0;
for (Key key : allKeys) {
if (key.getId() != null) {
int keyId = Integer.parseInt(key.getId());
if (keyId > maxKeyId) {
maxKeyId = keyId;
}
}
}
return maxKeyId;
}
private void fillKeyIds() {
List<Key> newKeys = new ArrayList<>();
for (Key key : allKeys) {
if (key.isVisible()) {
newKeys.add(key);
}
}
int maxKeyId = getMaxKeyId();
for (Key newKey : newKeys) {
maxKeyId++;
newKey.setId(maxKeyId + "");
}
}
/**
* Set keys visibility based on other entities visibility (a key is visible
* if all schemas, tables and columns involved have
* {@link Status} {@code ADDED_PERSISTED} or {@code ADDED_NOT_PERSISTED}).
*/
public final void checkKeys() {
if (!loading) {
for (Key key : allKeys) {
// Check if key should be exposed in TAP_SCHEMA
boolean keyVisible = true;
for (KeyColumn keyColumn : key.getKeyColumns()) {
String schemaName = keyColumn.getParent().getFromSchemaName();
String tableName = keyColumn.getParent().getFromTableSimpleName();
String columnName = keyColumn.getFromColumn();
if (!isColumnVisible(schemaName, tableName, columnName)) {
keyVisible = false;
break;
}
schemaName = keyColumn.getParent().getTargetSchemaName();
tableName = keyColumn.getParent().getTargetTableSimpleName();
columnName = keyColumn.getTargetColumn();
if (!isColumnVisible(schemaName, tableName, columnName)) {
keyVisible = false;
break;
}
}
// TODO: use status instead of set visibile [?]
key.setVisible(keyVisible);
}
}
}
/**
* Print all TAP_SCHEMA tree (useful for debugging).
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("\n");
Sonia Zorba
committed
sb.append(String.format(">> TAP_SCHEMA %s <<\n", getName()));
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
for (Schema schema : getChildren()) {
sb.append("--");
sb.append(schema.getName());
sb.append(String.format(" [%s]", schema.getStatus()));
sb.append("\n");
List<Table> tables = schema.getChildren();
for (int i = 0; i < tables.size(); i++) {
Table table = tables.get(i);
sb.append(" |--");
sb.append(table.getName());
sb.append(String.format(" [%s]", table.getStatus()));
sb.append("\n");
String padder = i < tables.size() - 1 ? " | " : " ";
for (Column column : table.getChildren()) {
sb.append(padder);
sb.append("|--");
sb.append(column.getName());
sb.append(String.format(" [%s]", column.getStatus()));
sb.append("\n");
}
sb.append("\n");
}
}
sb.append("** Keys **\n");
for (Key key : getVisibileKeys()) {
sb.append(key);
sb.append("\n");
}
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
return sb.toString();
}
public boolean isSchemaVisible(String schemaName) {
Schema schema = schemas.get(schemaName);
if (schema == null) {
return false;
}
return schema.getStatus() == Status.ADDED_PERSISTED
|| schema.getStatus() == Status.ADDED_NOT_PERSISTED;
}
public boolean isTableVisible(String schemaName, String tableName) {
if (!isSchemaVisible(schemaName)) {
return false;
}
Table table = schemas.get(schemaName).getChild(tableName);
if (table == null) {
return false;
}
if (table.getStatus() == Status.ADDED_PERSISTED
|| table.getStatus() == Status.ADDED_NOT_PERSISTED) {
return isSchemaVisible(schemaName);
}
return false;
}
public boolean isColumnVisible(String schemaName, String tableName, String columnName) {
if (!isTableVisible(schemaName, tableName)) {
return false;
}
Column column = schemas.get(schemaName).getChild(tableName).getChild(columnName);
if (column == null) {
return false;
}
if (column.getStatus() == Status.ADDED_PERSISTED
|| column.getStatus() == Status.ADDED_NOT_PERSISTED) {
return isTableVisible(schemaName, tableName);
}
return false;
}
/**
* Define if the TAP_SCHEMA schema was already written into the database.
*/
public boolean exists() {
return exists;
}
public ConsistencyChecks getConsistencyChecks() {
return consistencyChecks;
}
Sonia Zorba
committed
public final SchemaModel getTapSchemaModel() {
return SchemaModels.getTapSchemaModel(getVersion());
public final TableModel getTableModel(String tableName) {
return getTapSchemaModel().getTable(tableName);
}
public Map<String, Object> getSchemaMetadata(String schemaName) {
Map<String, Object> metadata = new HashMap<>();
metadata.put(Schema.SCHEMA_NAME_KEY, schemaName);
String dbNameMetadata = null;
if (dbName != null && schemaName.equals(STANDARD_NAME)) {
dbNameMetadata = dbName;
}
metadata.put(Schema.DBNAME, dbNameMetadata);
return metadata;
}
public List<Key> getVisibileKeys() {
List<Key> visibleKeys = new ArrayList<>();
for (Key key : allKeys) {
if (key.isVisible()) {
visibleKeys.add(key);
}
}
return visibleKeys;
}
Sonia Zorba
committed
private Integer getIntAsBool(Boolean value) {
if (value == null) {
return null;
}
return value ? 1 : 0;
}
public boolean isHasObscore() {
Sonia Zorba
committed
return settings.isHasObscore();
Sonia Zorba
committed
* Fill descriptions of the TAP_SCHEMA schema entities for a given
* SchemaModel (TAP_SCHEMA or ivoa).
Sonia Zorba
committed
private void fillColumnProperties(SchemaModel schemaModel, String schemaName) {
// check only on std, but valid also for principal (it depends on TS version)
boolean useIntegerAsBool = getTapSchemaModel().getTable(COLUMNS_TABLE).get(Column.STD_KEY).getJavaType() == Integer.class;
Sonia Zorba
committed
Schema schema = getChild(schemaName);
schema.setValue(DESCRIPTION_KEY, schemaModel.getDescription());
for (TableModel tableModel : schemaModel.getTables()) {
Table table = schema.getChild(tableModel.getName());
table.setValue(DESCRIPTION_KEY, tableModel.getDescription());
Sonia Zorba
committed
for (ColumnModel columnModel : tableModel.getColumns()) {
Column column = table.getChild(columnModel.getName());
if (!columnModel.isMandatory() && column == null) {
// column could be null if it is not mandatory
continue;
}
column.setValue(DESCRIPTION_KEY, columnModel.getDescription());
column.setValue(Column.UCD_KEY, columnModel.getUcd());
column.setValue(Column.UNIT_KEY, columnModel.getUnit());
column.setValue(Column.UTYPE_KEY, columnModel.getUtype());
Object compatibleStd = useIntegerAsBool ? getIntAsBool(columnModel.isStandard()) : columnModel.isStandard();
Object compatiblePrincipal = useIntegerAsBool ? getIntAsBool(columnModel.isPrincipal()) : columnModel.isPrincipal();
Sonia Zorba
committed
column.setValue(Column.STD_KEY, compatibleStd);
column.setValue(Column.PRINCIPAL_KEY, compatiblePrincipal);
public void fillColumnsProperties(SchemaModel schemaModel) {
Sonia Zorba
committed
fillColumnProperties(schemaModel, schemaModel.getName());
/**
* Allows to name the TAP_SCHEMA using the standard name, but referring to a
* differently named schema. This value is null if the TAP_SCHEMA version
* doesn't support the dbname column or if the schema name is already the
* standard value.
*/
public String getDBName() {
return dbName;
}
public String getRealName() {
return getRealSchemaName(getName());
}
public String getRealSchemaName(String schemaName) {
if (dbName != null && STANDARD_NAME.equals(schemaName)) {
return dbName;
}
return schemaName;
}