/* * _____________________________________________________________________________ * * 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; import it.inaf.ia2.tsm.xmlmodel.TableModel; import it.inaf.ia2.tsm.xmlmodel.TapSchemaModel; import it.inaf.ia2.tsm.xmlmodel.TapSchemaModels; 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; /** * The main implementation of {@link TapSchema}. * * @author Sonia Zorba {@literal } */ public class TapSchema implements EntitiesContainer, Serializable { // 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"; private static final long serialVersionUID = 1678083091602571256L; private static final Logger LOG = LoggerFactory.getLogger(TapSchema.class); private final Map schemas; private final List keysMetadata; private final Set allKeys; private final ConsistencyChecks consistencyChecks; private boolean loading; private String version; private DBWrapper dbWrapper; private String tapSchemaName; private boolean exists; private transient DBBroker sourceDBBroker; private transient DBBroker tapSchemaDBBroker; public final DBBroker getSourceDBBroker() { if (sourceDBBroker == null) { sourceDBBroker = DBBrokerFactory.getDBBroker(dbWrapper.getSourceDataSourceWrapper()); } return sourceDBBroker; } public final DBBroker getTapSchemaDBBroker() { if (tapSchemaDBBroker == null) { tapSchemaDBBroker = DBBrokerFactory.getDBBroker(dbWrapper.getTapSchemaDataSourceWrapper()); } return tapSchemaDBBroker; } private TapSchema() { // for serialization schemas = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); allKeys = new HashSet<>(); keysMetadata = new ArrayList<>(); consistencyChecks = new ConsistencyChecks(); } public TapSchema(String version, DBWrapper dbWrapper, String tapSchemaName, boolean exists) throws SQLException { this(); loading = true; this.version = version; this.dbWrapper = dbWrapper; this.tapSchemaName = tapSchemaName; this.exists = exists; // Initializing schemas map for (String schemaName : getSourceDBBroker().getAllSchemaNames()) { schemas.put(schemaName, null); } schemas.put(tapSchemaName, null); // the TAP_SCHEMA contains itself // if (exists) { // DaoSchema.fillSavedSchemas(dbWrapper, (this)); // DaoTable.fillSavedTables(dbWrapper, (this)); // DaoColumn.fillSavedColumns(dbWrapper, (this)); // DaoKey.fillSavedKeys(dbWrapper, (this)); // } loading = false; checkKeys(); } public DBBroker getDBBroker(String schemaName) { if (schemaName.equals(tapSchemaName)) { return getTapSchemaDBBroker(); } else { return getSourceDBBroker(); } } /** * The name of the TAP_SCHEMA schema. */ public String getName() { return tapSchemaName; } /** * The version selected for this TAP_SCHEMA. */ public String getVersion() { return version; } private void loadSchemaKeysMetadata(String schemaName) throws SQLException { keysMetadata.addAll(getDBBroker(schemaName).getKeysMetadata(schemaName)); } protected Set getAllKeys() { return allKeys; } /** * {@inheritDoc} */ @Override public Schema addChild(String schemaName) throws SQLException { LOG.debug("Adding schema {}", schemaName); Schema schema; if (!schemas.containsKey(schemaName)) { consistencyChecks.addUnexistingSchema(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(); 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 Schema getChild(String childName, Status... statuses) { return TSMUtil.getChild(schemas, childName, statuses); } /** * {@inheritDoc} */ @Override public List getChildren(Status... statuses) { return TSMUtil.getChildrenByStatus(schemas.values(), statuses); } /** * {@inheritDoc} */ @Override public List getAddableChildrenNames() { return TSMUtil.getAddableChildrenNames(schemas); } /** * {@inheritDoc} */ @Override public List getAddedChildren() { return getChildren(Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED); } /** * {@inheritDoc} */ @Override public List 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); } /** * Save or update the TAP_SCHEMA changes into the database. */ public void save() throws SQLException { fillKeyIds(); DBBroker broker = getTapSchemaDBBroker(); broker.save(this); if (!exists) { // Adding TAP_SCHEMA into TAP_SCHEMA Schema tapSchemaSchema = addChild(tapSchemaName); for (String tableName : tapSchemaSchema.getAddableChildrenNames()) { Table table = tapSchemaSchema.addChild(tableName); for (String columnName : table.getAddableChildrenNames()) { table.addChild(columnName); } } LOG.debug(this.toString()); TSMUtil.putInfoIntoTapSchemaSchema(tapSchemaSchema); exists = true; // important! //broker.save(this); // save again } exists = true; consistencyChecks.getInconsistencies().clear(); } /** * 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 && !KeyMetadata.NEW_KEY.equals(key.getId())) { int keyId = Integer.parseInt(key.getId()); if (keyId > maxKeyId) { maxKeyId = keyId; } } } return maxKeyId; } private void fillKeyIds() { List newKeys = new ArrayList<>(); for (Key key : allKeys) { if (key.isVisible() && KeyMetadata.NEW_KEY.equals(key.getId())) { 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}). // */ // protected void checkKeys() { // // int currentKey = getMaxKeyId() + 1; // // for (Key key : allKeys) { // // ((Key) key).setVisible(false); // // Schema fromSchema = getChild(key.getFromSchemaName(), Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED); // Schema targetSchema = getChild(key.getTargetSchemaName(), Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED); // if (fromSchema != null && targetSchema != null) { // // Table fromTable = fromSchema.getChild(key.getFromTableSimpleName(), Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED); // Table targetTable = targetSchema.getChild(key.getTargetTableSimpleName(), Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED); // // if (fromTable != null && targetTable != null) { // // boolean allColumnsVisible = true; // // for (KeyColumn keyColumn : key.getKeyColumns()) { // // Column fromColumn = fromTable.getChild(keyColumn.getFromColumn(), Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED); // Column targetColumn = targetTable.getChild(keyColumn.getTargetColumn(), Status.ADDED_PERSISTED, Status.ADDED_NOT_PERSISTED); // // if (fromColumn == null || targetColumn == null) { // allColumnsVisible = false; // break; // } // } // // if (allColumnsVisible) { // ((Key) key).setVisible(true); // if (key.getId() == null) { // key.setId(currentKey + ""); // currentKey++; // } // } // } // } // } // for (Key key : allKeys) { // log.debug("{} [{}]", key, key.getStatus()); // } // } // // public void addFictitiousKey(Table fromTable, String[] fromColumns, Table targetTable, String[] targetColumns) { // Key key = new Key(dbWrapper, this, fromTable.getCompleteName(), targetTable.getCompleteName()); // key.setId((getMaxKeyId() + 1) + ""); // // for (int i = 0; i < fromColumns.length; i++) { // key.addKeyColumn(fromColumns[i], targetColumns[i]); // } // // fromTable.addFromKey(key); // targetTable.addTargetKey(key); // // allKeys.add(key); // checkKeys(); // } /** * Automatically add and remove keys that should be visible. */ public final void checkKeys() { if (!loading) { for (KeyMetadata keyMetadata : keysMetadata) { // Check if key should be exposed in TAP_SCHEMA boolean keyVisible = true; for (String fromColumn : keyMetadata.getFromColumns()) { if (!isColumnVisible(keyMetadata.getFromSchema(), keyMetadata.getFromTable(), fromColumn)) { keyVisible = false; break; } } if (keyVisible) { for (String targetColumn : keyMetadata.getTargetColumns()) { if (!isColumnVisible(keyMetadata.getTargetSchema(), keyMetadata.getTargetTable(), targetColumn)) { keyVisible = false; break; } } } Key key = null; for (Key k : allKeys) { if (k.getKeyMetadata().equals(keyMetadata)) { key = k; break; } } // TODO: use status instead of set visibile [?] if (keyVisible) { if (key == null) { key = new Key(this, keyMetadata); allKeys.add(key); } key.setVisible(true); } else if (key != null) { key.setVisible(false); } } } } /** * Print all TAP_SCHEMA tree (useful for debugging). */ @Override public String toString() { StringBuilder sb = new StringBuilder("\n"); sb.append(String.format(">> TAP_SCHEMA %s <<\n", tapSchemaName)); // for (Schema schema : getChildren()) { // sb.append("--"); // sb.append(schema.getName()); // sb.append(String.format(" [%s]", schema.getStatus())); // sb.append("\n"); // List 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"); // } // // if (table.getAllFromKeys().size() > 0) { // sb.append(padder); // sb.append("** From keys **\n"); // for (Key fromKey : table.getAllFromKeys()) { // sb.append(padder); // sb.append("* "); // sb.append(fromKey.toString()); // sb.append(String.format(" [visible=%s]", fromKey.isVisible())); // sb.append("\n"); // } // } // if (table.getAllTargetKeys().size() > 0) { // sb.append(padder); // sb.append("** Target keys **\n"); // for (Key targetKey : table.getAllTargetKeys()) { // sb.append(padder); // sb.append("* "); // sb.append(targetKey.toString()); // sb.append(String.format(" [visible=%s]", targetKey.isVisible())); // sb.append("\n"); // } // } // // sb.append("\n"); // } // } 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; } public TapSchemaModel getTapSchemaModel() { return TapSchemaModels.getTapSchemaModel(version); } public TableModel getTableModel(String tableName) { return getTapSchemaModel().getTables().get(tableName); } public KeyMetadata searchKeyMetadata(String fromSchemaName, String fromTableName, String fromColumnName) { for (KeyMetadata km : keysMetadata) { if (km.getFromSchema().equals(fromSchemaName) && km.getFromTable().equals(fromTableName) && km.getFromColumns().contains(fromColumnName)) { return km; } } return null; } public Map getSchemaMetadata(String schemaName) { Map metadata = new HashMap<>(); metadata.put(Schema.SCHEMA_NAME_KEY, schemaName); return metadata; } public List getVisibileKeys() { List visibleKeys = new ArrayList<>(); for (Key key : allKeys) { if (key.isVisible()) { visibleKeys.add(key); } } return visibleKeys; } }