All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jumpmind.db.model.Database Maven / Gradle / Ivy

Go to download

This library is a fork of Apache DdlUtils. DdlUtils is a small, easy-to-use component for working with Database Definition (DDL) files.

There is a newer version: 3.5.19
Show newest version
package org.jumpmind.db.model;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.Serializable;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents the database model, ie. the tables in the database. It also
 * contains the corresponding dyna classes for creating dyna beans for the
 * objects stored in the tables.
 */
public class Database implements Serializable, Cloneable {

    protected static final Logger log = LoggerFactory.getLogger(Database.class);

    /** Unique ID for serialization purposes. */
    private static final long serialVersionUID = -3160443396757573868L;

    /** The name of the database model. */
    private String name;

    /** The method for generating primary keys (currently ignored). */
    private String idMethod;

    /** The version of the model. */
    private String version;

    /** The tables. */
    private ArrayList tables = new ArrayList
(); private Map tableIndexCache = new HashMap(); public static Table[] sortByForeignKeys(Table... tables) { if (tables != null) { List
list = new ArrayList
(tables.length); for (Table table : tables) { list.add(table); } list = sortByForeignKeys(list); tables = list.toArray(new Table[list.size()]); } return tables; } /** * Implements modified topological sort of tables (@see topological * sorting). The 'depth-first search' is implemented in order to detect * and ignore cycles. * * @param tables * List of tables to sort. * @return List of tables in their dependency order - if table A has a * foreign key for table B then table B will precede table A in the * list. */ public static List
sortByForeignKeys(List
tables) { List
sorted = new ArrayList
(tables.size()); List
visited = new ArrayList
(tables.size()); List
stack = new ArrayList
(); Map tableMap = new HashMap(); for (int i = 0; i < tables.size(); i++) { tableMap.put(tables.get(i).getName(), tables.get(i)); } for (int i = 0; i < tables.size(); i++) { sortByForegnKeysVisit(tables.get(i), tableMap, sorted, visited, stack); } return sorted; } private static void sortByForegnKeysVisit(Table table, Map tableMap, List
sorted, List
visited, List
stack) { if (visited.contains(table)) { return; } if (stack.contains(table)) { return; } // cycle detected - ignore this FK visited.add(table); stack.add(table); for (ForeignKey fk : table.getForeignKeys()) { Table foreignTable = tableMap.get(fk.getForeignTableName()); if (foreignTable != null) { // ignore foreign keys to tables outside // of the input set sortByForegnKeysVisit(foreignTable, tableMap, sorted, visited, stack); } } sorted.add(table); stack.remove(stack.size() - 1); } /** * Adds all tables from the other database to this database. Note that the * other database is not changed. * * @param otherDb * The other database model */ public void mergeWith(Database otherDb) throws ModelException { for (Iterator
it = otherDb.tables.iterator(); it.hasNext();) { Table table = (Table) it.next(); if (findTable(table.getName()) != null) { // TODO: It might make more sense to log a warning and overwrite // the table (or merge them) ? throw new ModelException("Cannot merge the models because table " + table.getName() + " already defined in this model"); } try { addTable((Table) table.clone()); } catch (CloneNotSupportedException ex) { // won't happen } } } /** * Returns the name of this database model. * * @return The name */ public String getName() { return name; } /** * Sets the name of this database model. * * @param name * The name */ public void setName(String name) { this.name = name; } /** * Returns the version of this database model. * * @return The version */ public String getVersion() { return version; } /** * Sets the version of this database model. * * @param version * The version */ public void setVersion(String version) { this.version = version; } /** * Returns the method for generating primary key values. * * @return The method */ public String getIdMethod() { return idMethod; } /** * Sets the method for generating primary key values. Note that this value * is ignored by DdlUtils and only for compatibility with Torque. * * @param idMethod * The method */ public void setIdMethod(String idMethod) { this.idMethod = idMethod; } /** * Returns the number of tables in this model. * * @return The number of tables */ public int getTableCount() { return tables.size(); } /** * Returns the tables in this model. * * @return The tables */ public Table[] getTables() { return (Table[]) tables.toArray(new Table[tables.size()]); } /** * Returns the table at the specified position. * * @param idx * The index of the table * @return The table */ public Table getTable(int idx) { return (Table) tables.get(idx); } /** * Adds a table. * * @param table * The table to add */ public void addTable(Table table) { if (table != null) { tables.add(table); } } /** * Adds a table at the specified position. * * @param idx * The index where to insert the table * @param table * The table to add */ public void addTable(int idx, Table table) { if (table != null) { tables.add(idx, table); } } /** * Adds the given tables. * * @param tables * The tables to add */ public void addTables(Collection
tables) { for (Iterator
it = tables.iterator(); it.hasNext();) { addTable((Table) it.next()); } } public void addTables(Table[] tables) { for (Table table : tables) { addTable(table); } } /** * Removes the given table. * * @param table * The table to remove */ public void removeTable(Table table) { if (table != null) { tables.remove(table); } } /** * Removes the indicated table. * * @param idx * The index of the table to remove */ public void removeTable(int idx) { tables.remove(idx); } // Helper methods /** * Initializes the model by establishing the relationships between elements * in this model encoded eg. in foreign keys etc. Also checks that the model * elements are valid (table and columns have a name, foreign keys rference * existing tables etc.) */ public void initialize() throws ModelException { // we have to setup // * target tables in foreign keys // * columns in foreign key references // * columns in indices // * columns in uniques HashSet namesOfProcessedTables = new HashSet(); HashSet namesOfProcessedColumns = new HashSet(); HashSet namesOfProcessedFks = new HashSet(); HashSet namesOfProcessedIndices = new HashSet(); int tableIdx = 0; for (Iterator
tableIt = tables.iterator(); tableIt.hasNext(); tableIdx++) { Table curTable = tableIt.next(); if ((curTable.getName() == null) || (curTable.getName().length() == 0)) { throw new ModelException("The table nr. " + tableIdx + " has no name"); } if (namesOfProcessedTables.contains(curTable.getFullyQualifiedTableName())) { throw new ModelException("There are multiple tables with the name " + curTable.getName()); } namesOfProcessedTables.add(curTable.getFullyQualifiedTableName()); namesOfProcessedColumns.clear(); namesOfProcessedFks.clear(); namesOfProcessedIndices.clear(); for (int idx = 0; idx < curTable.getColumnCount(); idx++) { Column column = curTable.getColumn(idx); if ((column.getName() == null) || (column.getName().length() == 0)) { throw new ModelException("The column nr. " + idx + " in table " + curTable.getName() + " has no name"); } if (namesOfProcessedColumns.contains(column.getName())) { throw new ModelException("There are multiple column with the name " + column.getName() + " in the table " + curTable.getName()); } namesOfProcessedColumns.add(column.getName()); if ((column.getMappedType() == null) || (column.getMappedType().length() == 0)) { throw new ModelException("The column nr. " + idx + " in table " + curTable.getName() + " has no type"); } if ((column.getMappedTypeCode() == Types.OTHER) && !"OTHER".equalsIgnoreCase(column.getMappedType())) { throw new ModelException("The column nr. " + idx + " in table " + curTable.getName() + " has an unknown type " + column.getMappedType()); } namesOfProcessedColumns.add(column.getName()); } for (int idx = 0; idx < curTable.getForeignKeyCount(); idx++) { ForeignKey fk = curTable.getForeignKey(idx); String fkName = (fk.getName() == null ? "" : fk.getName()); String fkDesc = (fkName.length() == 0 ? "nr. " + idx : fkName); if (fkName.length() > 0) { if (namesOfProcessedFks.contains(fkName)) { throw new ModelException("There are multiple foreign keys in table " + curTable.getName() + " with the name " + fkName); } namesOfProcessedFks.add(fkName); } if (fk.getForeignTable() == null) { Table targetTable = findTable(fk.getForeignTableName(), true); if (targetTable != null) { fk.setForeignTable(targetTable); } else { log.debug("The foreignkey " + fkDesc + " in table " + curTable.getName() + " references the undefined table " + fk.getForeignTableName() + ". This could be because the foreign key table was in another schema which is a bug that should be fixed in the future."); } } if (fk.getForeignTable() != null) { for (int refIdx = 0; refIdx < fk.getReferenceCount(); refIdx++) { Reference ref = fk.getReference(refIdx); if (ref.getLocalColumn() == null) { Column localColumn = curTable .findColumn(ref.getLocalColumnName(), true); if (localColumn == null) { throw new ModelException("The foreignkey " + fkDesc + " in table " + curTable.getName() + " references the undefined local column " + ref.getLocalColumnName()); } else { ref.setLocalColumn(localColumn); } } if (ref.getForeignColumn() == null) { Column foreignColumn = fk.getForeignTable().findColumn( ref.getForeignColumnName(), true); if (foreignColumn == null) { throw new ModelException("The foreignkey " + fkDesc + " in table " + curTable.getName() + " references the undefined local column " + ref.getForeignColumnName() + " in table " + fk.getForeignTable().getName()); } else { ref.setForeignColumn(foreignColumn); } } } } } for (int idx = 0; idx < curTable.getIndexCount(); idx++) { IIndex index = curTable.getIndex(idx); String indexName = (index.getName() == null ? "" : index.getName()); if (indexName.length() > 0) { if (namesOfProcessedIndices.contains(indexName)) { throw new ModelException("There are multiple indices in table " + curTable.getName() + " with the name " + indexName); } namesOfProcessedIndices.add(indexName); } for (int indexColumnIdx = 0; indexColumnIdx < index.getColumnCount(); indexColumnIdx++) { IndexColumn indexColumn = index.getColumn(indexColumnIdx); Column column = curTable.findColumn(indexColumn.getName(), true); indexColumn.setColumn(column); } } } } /** * Finds the table with the specified name, using case insensitive matching. * Note that this method is not called getTable to avoid introspection * problems. * * @param name * The name of the table to find * @return The table or null if there is no such table */ public Table findTable(String name) { return findTable(name, false); } /** * Finds the table with the specified name, using case insensitive matching. * Note that this method is not called getTable) to avoid introspection * problems. * * @param name * The name of the table to find * @param caseSensitive * Whether case matters for the names * @return The table or null if there is no such table */ public Table findTable(String name, boolean caseSensitive) { for (Iterator
iter = tables.iterator(); iter.hasNext();) { Table table = (Table) iter.next(); if (caseSensitive) { if (table.getName().equals(name)) { return table; } } else { if (table.getName().equalsIgnoreCase(name)) { return table; } } } return null; } /** * Catalog & Schema aware finder for ddlutils Database class * * * @param catalogName * @param schemaName * @param tableName * @param caseSensitive * @return */ public Table findTable(String catalogName, String schemaName, String tableName, boolean caseSensitive) { String cacheKey = catalogName + "." + schemaName + "." + tableName + "." + caseSensitive; Integer tableIndex = tableIndexCache.get(cacheKey); if (tableIndex != null) { if (tableIndex < getTableCount()) { Table table = getTable(tableIndex); if (doesMatch(table, catalogName, schemaName, tableName, caseSensitive)) { return table; } } } Table[] tables = getTables(); for (int i = 0; i < tables.length; i++) { Table table = tables[i]; if (doesMatch(table, catalogName, schemaName, tableName, caseSensitive)) { tableIndexCache.put(cacheKey, i); return table; } } return null; } private boolean doesMatch(Table table, String catalogName, String schemaName, String tableName, boolean caseSensitive) { if (caseSensitive) { return ((catalogName == null || (catalogName != null && catalogName.equals(table .getCatalog()))) && (schemaName == null || (schemaName != null && schemaName.equals(table .getSchema()))) && table.getName().equals(tableName)); } else { return ((catalogName == null || (catalogName != null && catalogName .equalsIgnoreCase(table.getCatalog()))) && (schemaName == null || (schemaName != null && schemaName .equalsIgnoreCase(table.getSchema()))) && table.getName() .equalsIgnoreCase(tableName)); } } public Table findTable(String catalogName, String schemaName, String tableName) { return findTable(catalogName, schemaName, tableName, false); } public void resetTableIndexCache() { tableIndexCache.clear(); } public void removeAllTablesExcept(String... tableNames) { Iterator
tableIterator = this.tables.iterator(); while(tableIterator.hasNext()) { Table table = tableIterator.next(); boolean foundTable = false; for (String tableName : tableNames) { if (tableName.equals(table.getName())) { foundTable = true; break; } } if (!foundTable) { tableIterator.remove(); } } } public Database copy() { try { return (Database) this.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } /** * {@inheritDoc} */ public Object clone() throws CloneNotSupportedException { Database result = (Database) super.clone(); result.name = name; result.idMethod = idMethod; result.version = version; result.tables = new ArrayList
(tables.size()); for (Table table : tables) { result.tables.add((Table)table.clone()); } return result; } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof Database) { Database other = (Database) obj; // Note that this compares case sensitive return new EqualsBuilder().append(name, other.name).append(tables, other.tables) .isEquals(); } else { return false; } } /** * {@inheritDoc} */ public int hashCode() { return new HashCodeBuilder(17, 37).append(name).append(tables).toHashCode(); } /** * {@inheritDoc} */ public String toString() { StringBuffer result = new StringBuffer(); result.append("Database [name="); result.append(getName()); result.append("; "); result.append(getTableCount()); result.append(" tables]"); return result.toString(); } /** * Returns a verbose string representation of this database. * * @return The string representation */ public String toVerboseString() { StringBuffer result = new StringBuffer(); result.append("Database ["); result.append(getName()); result.append("] tables:"); for (int idx = 0; idx < getTableCount(); idx++) { result.append(" "); result.append(getTable(idx).toVerboseString()); } return result.toString(); } }