Skip to content
ConsistencyChecks.java 16.3 KiB
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.model.ColumnModel;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Data model for storing consistency checking result. The consistency checking
 * phase verify if the data stored into the TAP_SCHEMA is consistent with the
 * data read from the database metadata (or information_schema).
 * <p>
 * Naming convention:
 * <ul>
 * <li><strong>inconsistency</strong>: when a property of a column exposed by
 * the TAP_SCHEMA is different from the value read from the database metadata
 * (for example the column is defined with a given size which is different from
 * the arraysize stored into the TAP_SCHEMA;</li>
 * <li><strong>inexistent entity</strong>: when a TAP_SCHEMA entity (schema,
 * table, etc.) is represented into the TAP_SCHEMA, but it doesn't exists
 * according to information retrieved from the database metadata;</li>
 * <li><strong>missing entity</strong>: when a mandatory TAP_SCHEMA entity has
 * not been added to an existing TAP_SCHEMA (this is used for ObsCore mandatory
 * columns).</li>
 * </ul>
 * @author Sonia Zorba {@literal <zorba at oats.inaf.it>}
 */
public class ConsistencyChecks implements Serializable {

    private static final long serialVersionUID = 4412404312756740093L;
    private final static Logger LOG = LoggerFactory.getLogger(ConsistencyChecks.class);

    private final List<InconsistentColumnProperty> inconsistencies;
    private final Set<String> unexisingSchemas;
    private final Set<String> unexistingTables;
    private final Set<ColumnHolder> unexistingColumns;
    private final Set<KeyHolder> unexistingKeys;
    private final Map<String, Set<String>> missingTables;
    private final Map<String, Set<String>> tablesToAdd;
    private final Map<ColumnHolder, ColumnModel> missingColumns;
    private final Set<ColumnHolder> columnsToAdd;
    private final List<WrongDataType> wrongDataTypes;
    private boolean missingObscore;
    private boolean obscoreToAdd;
    // This is not consider an inconsistency: it is only used to display a warning
    private final Set<ColumnHolder> unaddedOptionalColumns;

    public ConsistencyChecks() {
        inconsistencies = new ArrayList<>();
        unexisingSchemas = new HashSet<>();
        unexistingTables = new HashSet<>();
        unexistingColumns = new HashSet<>();
        unexistingKeys = new HashSet<>();
        missingTables = new HashMap<>();
        tablesToAdd = new HashMap<>();
        missingColumns = new HashMap<>();
        columnsToAdd = new HashSet<>();
        unaddedOptionalColumns = new HashSet<>();
        wrongDataTypes = new ArrayList<>();
    /**
     * Adds an inconsistent column property.
     */
    public void addInconsistency(InconsistentColumnProperty inconsistentProperty) {
        inconsistencies.add(inconsistentProperty);
    /**
     * Returns a list of all inconsistent column properties detected.
     */
    public List<InconsistentColumnProperty> getInconsistencies() {
    /**
     * Returns a set of schema names that have been stored into the TAP_SCHEMA
     * but are not currently existing according to the information read from the
     * database metadata.
     */
    public Set<String> getUnexisingSchemas() {
    /**
     * Adds the name of a schema that has been stored into the TAP_SCHEMA but is
     * not currently existing according to the information read from the
     * database metadata.
     */
    public void addUnexistingSchema(String schemaName) {
        unexisingSchemas.add(schemaName);
    }

    /**
     * Returns a set of table names that have been stored into the TAP_SCHEMA
     * but are not currently existing according to the information read from the
     * database metadata. Unexisting columns.
     */
    public Set<String> getUnexistingTables() {
        return unexistingTables;
    /**
     * Adds the name of a table that has been stored into the TAP_SCHEMA but is
     * not currently existing according to the information read from the
     * database metadata.
     */
    public void addUnexistingTable(String schemaName, String tableSimpleName) {
        unexistingTables.add(schemaName + "." + tableSimpleName);
    /**
     * Returns a set of column names that have been stored into the TAP_SCHEMA
     * but are not currently existing according to the information read from the
     * database metadata.
     */
    public Set<ColumnHolder> getUnexistingColumns() {
    /**
     * Adds the representation of a column that has been stored into the
     * TAP_SCHEMA but is not currently existing according to the information
     * read from the database metadata.
     *
     * @param schemaName the name of the schema owning the inexistent column.
     * @param tableName the name of the table owning the inexistent column.
     * @param columnName the name of the inexistent column.
     */
    public void addUnexistingColumn(String schemaName, String tableName, String columnName) {
        unexistingColumns.add(new ColumnHolder(schemaName, tableName, columnName));
    /**
     * Adds the representation of a key that has been stored into the TAP_SCHEMA
     * but is not currently existing according to the information read from the
     * database metadata.
     *
     * @param keyId the identifier of the inexistent key stored into the
     * TAP_SCHEMA.
     * @param fromTable the table owning the foreign key.
     * @param fromColumns the column owning the foreign key.
     * @param targetTable the table owning the primary key.
     * @param targetColumns the table owning the primary.
     */
    public void addUnexistingKey(String keyId, String fromTable, String[] fromColumns, String targetTable, String[] targetColumns) {
        if (keyId == null) {
            throw new IllegalArgumentException("key_id can't be null");
        }
        unexistingKeys.add(new KeyHolder(keyId, fromTable, fromColumns, targetTable, targetColumns));
    /**
     * Returns a set of key representations that have been stored into the
     * TAP_SCHEMA but are not currently existing according to the information
     * read from the database metadata.
     */
    public Set<KeyHolder> getUnexistingKeys() {
        return unexistingKeys;
    }

    /**
     * Adds a model representing a column that must be created and then exposed
     * by the TAP_SCHEMA and currently is not existing according to the database
     * metadata (this could happen for TAP_SCHEMA schema columns and ObsCore
     * columns).
     *
     * @param columnHolder the object representing the missing column.
     * @param columnModel the model defining the column properties.
     */
    public void addMissingColumn(ColumnHolder columnHolder, ColumnModel columnModel) {
        // Removing table from unexisting columns set
        unexistingColumns.remove(columnHolder);
        missingColumns.put(columnHolder, columnModel);
    /**
     * Returns a map of all columns which are not existing yet and must be
     * created and then exposed by the TAP_SCHEMA (this could happen for
     * TAP_SCHEMA schema columns and ObsCore columns).
     *
     * @return a {@code Map} having {@link ColumnHolder} instances as keys and
     * {@link ColumnModel} instances as values.
     */
    public Map<ColumnHolder, ColumnModel> getMissingColumns() {
        return missingColumns;
    private void addTableToMap(String schemaName, String tableName, Map<String, Set<String>> map) {
        Set<String> tables = map.get(schemaName);
        if (tables == null) {
            tables = new HashSet<>();
            map.put(schemaName, tables);
        tables.add(tableName);
    /**
     * Adds a table that must be created and then exposed by the TAP_SCHEMA and
     * currently is not existing (this could happen for TAP_SCHEMA tables and
     * ObsCore table).
     *
     * @param schemaName the name of the schema owning the missing table.
     * @param tableName the name of the missing table.
     */
    public void addMissingTable(String schemaName, String tableName) {
        // Removing table from unexisting table set
        unexistingTables.remove(String.format("%s.%s", schemaName, tableName));
        // Removing table from unexisting columns set
        Iterator<ColumnHolder> ite = unexistingColumns.iterator();
        while (ite.hasNext()) {
            ColumnHolder ch = ite.next();
            if (ch.getSchemaName().equals(schemaName) && ch.getTableName().equals(tableName)) {
                ite.remove();
            }
        }
        addTableToMap(schemaName, tableName, missingTables);
    }
    /**
     * Adds a table that is existing and must be exposed by the TAP_SCHEMA but
     * currently has not been added to it (this could happen for TAP_SCHEMA
     * tables and ObsCore table).
     *
     * @param schemaName
     * @param tableName
     */
    public void addTableToAdd(String schemaName, String tableName) {
        addTableToMap(schemaName, tableName, tablesToAdd);
    }
    /**
     * Returns all the existing tables that must be exposed by the TAP_SCHEMA
     * and currently are not (this could happen for TAP_SCHEMA tables and
     * ObsCore table).
     *
     * @return a {@code Map} the keys of which are schema names and the values
     * of which are {@code Set}s of table names owned by the schema having its
     * name used as the key in the {@code Map}.
     */
    public Map<String, Set<String>> getTablesToAdd() {
        return tablesToAdd;
    }
    /**
     * Returns all the tables that must be added into the TAP_SCHEMA and then
     * exposed by it and currently are not (this could happen for TAP_SCHEMA
     * schema tables and ObsCore table).
     *
     * @return a {@code Map} the keys of which are schema names and the values
     * of which are {@code Set}s of table names owned by the schema having its
     * name used as the key in the {@code Map}.
     */
    public Map<String, Set<String>> getMissingTables() {
        return missingTables;
    }
    /**
     * Adds an existing column which must be exposed by the TAP_SCHEMA but
     * currently has not been added to it (this could happen for TAP_SCHEMA
     * schema columns and ObsCore columns).
     *
     * @param columnHolder a representation for the column.
     */
    public void addColumnToAdd(ColumnHolder columnHolder) {
        columnsToAdd.add(columnHolder);
    }
    /**
     * Returns a set of all column representations regarding existing columns
     * which must be exposed by the TAP_SCHEMA but have not been added to it
     * yet.
     */
    public Set<ColumnHolder> getColumnsToAdd() {
        return columnsToAdd;
    }
    /**
     * Indicates if the ObsCore table should be created.
     *
     * @return true if the ObsCore table doesn't exist, false otherwise.
     */
    public boolean isMissingObscore() {
        return missingObscore;
    }
    /**
     * @param missingObscore true if the ObsCore table doesn't exist, false
     * otherwise.
     */
    public void setMissingObscore(boolean missingObscore) {
        this.missingObscore = missingObscore;
    }
    /**
     * Indicates if the ObsCore table must be added to the TAP_SCHEMA. In this
     * case the ObsCore table already exists, it has simply not been exposed by
     * the TAP_SCHEMA yet.
     *
     * @return true if the ObsCore table must be added to the TAP_SCHEMA, false
     * otherwise.
     */
    public boolean isObscoreToAdd() {
        return obscoreToAdd;
    }
    /**
     * @param obscoreToAdd true if the ObsCore table must be added to the
     * TAP_SCHEMA, false otherwise.
     */
    public void setObscoreToAdd(boolean obscoreToAdd) {
        this.obscoreToAdd = obscoreToAdd;
    }
    /**
     * Adds an existing optional column which has not been added to the
     * TAP_SCHEMA (used for ObsCore). This is not consider an inconsistency: it
     * is only used to display a suggestion.
     *
     * @param columnHolder a representation for the column
     */
    public void addUnaddedOptionalColumn(ColumnHolder columnHolder) {
        unaddedOptionalColumns.add(columnHolder);
    }

    /**
     * Returns the set of all existing optional columns which have not been
     * added to the TAP_SCHEMA yet. This doesn't represents an inconsistency: it
     * is only used to display a suggestion.
     */
    public Set<ColumnHolder> getUnaddedOptionalColumns() {
        return unaddedOptionalColumns;
    }

    /**
     * Adds a {@link WrongDataType} model meaning that an existing column has a
     * structure which is incoherent with its expected definition according to
     * its {@link ColumnModel}. To fix this issue an {@code ALTER TABLE} is
     * necessary.
     *
     * @param columnHolder the representation of the broken column.
     * @param wrongDataType the current column datatype, according to
     * information retrieved from the database metadata.
     * @param correctDataType the expected column datatype, according to the
     * {@link ColumnModel}.
     * @param adqlCorrectDataType the expected column datatype, using ADQL
     * datatype syntax.
     * @param size the desired column size.
     */
    public void addWrongDataType(ColumnHolder columnHolder, String wrongDataType, String correctDataType, String adqlCorrectDataType, Integer size) {
        // If datatype needs to be changed inconsistency on it doesn't make sense anymore.
        Iterator<InconsistentColumnProperty> ite = inconsistencies.iterator();
        while (ite.hasNext()) {
            InconsistentColumnProperty inconsistency = ite.next();
            if (inconsistency.getColumnHolder().equals(columnHolder)
                    && (inconsistency.getKey().equals(Column.DATATYPE_KEY)
                    || inconsistency.getKey().equals(Column.SIZE_KEY)
                    || inconsistency.getKey().equals(Column.ARRAYSIZE_KEY))) {
                ite.remove();
            }
        }

        WrongDataType wdt = new WrongDataType(columnHolder, wrongDataType, correctDataType, adqlCorrectDataType, size);
        wrongDataTypes.add(wdt);
    }

    /**
     * Returns a list of the {@link WrongDataType}s detected. To fix this issue
     * an {@code ALTER TABLE} is necessary.
     */
    public List<WrongDataType> getWrongDataTypes() {
        return wrongDataTypes;
    }

    /**
     * Indicates if the loaded TAP_SCHEMA contains some consistency problems to
     * fix.
     *
     * @return true if the TAP_SCHEMA contains consistency problems, false
     * otherwise.
     */
    public boolean isInconsistent() {
        return !inconsistencies.isEmpty() || !wrongDataTypes.isEmpty()
                || !unexisingSchemas.isEmpty() || !unexistingTables.isEmpty() || !unexistingColumns.isEmpty()
                || !missingTables.isEmpty() || !missingColumns.isEmpty()
                || !columnsToAdd.isEmpty() || !tablesToAdd.isEmpty()
                || obscoreToAdd || missingObscore;
    /**
     * Indicates if the TAP_SCHEMA has been loaded with warnings/suggestions.
     *
     * @return true if warnings have been produced during the consistency
     * checking process, false otherwise.
     */
    public boolean isHasWarnings() {
        return !unaddedOptionalColumns.isEmpty();
    }