blob: 7d7cfa2ff287bda20c6678f50654145509d61015 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2012 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.db.relational;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.persistence.tools.db.relational.platformsmodel.DatabasePlatform;
import org.eclipse.persistence.tools.db.relational.spi.ExternalColumn;
import org.eclipse.persistence.tools.db.relational.spi.ExternalForeignKey;
import org.eclipse.persistence.tools.db.relational.spi.ExternalForeignKeyColumnPair;
import org.eclipse.persistence.tools.db.relational.spi.ExternalTable;
import org.eclipse.persistence.tools.db.relational.spi.ExternalTableDescription;
import org.eclipse.persistence.tools.utility.NameTools;
import org.eclipse.persistence.tools.utility.StringTools;
import org.eclipse.persistence.tools.utility.iterable.LiveCloneIterable;
import org.eclipse.persistence.tools.utility.iterator.CompositeIterator;
import org.eclipse.persistence.tools.utility.iterator.FilteringIterator;
import org.eclipse.persistence.tools.utility.iterator.TransformationIterator;
import org.eclipse.persistence.tools.utility.node.Node;
/**
* @version 2.5
*/
@SuppressWarnings("nls")
public final class ELTable extends ELModel {
/** the catalog should never be empty - it can be null, but not empty */
private volatile String catalog;
public static final String CATALOG_PROPERTY = "catalog";
/** the schema should never be empty - it can be null, but not empty */
private volatile String schema;
public static final String SCHEMA_PROPERTY = "schema";
/** the short name should never be null OR empty */
private volatile String shortName;
public static final String SHORT_NAME_PROPERTY = "shortName";
public static final String QUALIFIED_NAME_PROPERTY = "qualifiedName";
/** this will be null if it is not known */
private Date lastRefreshTimestamp;
public static final String LAST_REFRESH_TIMESTAMP_PROPERTY = "lastRefreshTimestamp";
private Collection<ELColumn> columns;
public static final String COLUMNS_COLLECTION = "columns";
private Collection<ELReference> references;
public static final String REFERENCES_COLLECTION = "references";
private boolean legacyIsFullyQualified;
// ********** constructors **********
ELTable(ELDatabase database, String catalog, String schema, String shortName) {
super(database);
this.catalog = catalog;
this.schema = schema;
this.shortName = shortName;
}
// ********** initialization **********
/**
* initialize persistent state
*/
@Override
protected void initialize(Node parent) {
super.initialize(parent);
this.lastRefreshTimestamp = null; // if the table is built by hand, this is left null
this.columns = new Vector<ELColumn>();
this.references = new Vector<ELReference>();
}
// ********** name: catalog, schema, shortName **********
public String getCatalog() {
return this.catalog;
}
/**
* private - @see #rename(String, String, String)
*/
private void setCatalog(String catalog) {
Object old = this.catalog;
this.catalog = catalog;
if (this.attributeValueHasChanged(old, catalog)) {
this.firePropertyChanged(CATALOG_PROPERTY, old, catalog);
this.qualifiedNameChanged();
}
}
public String getSchema() {
return this.schema;
}
/**
* private - @see #rename(String, String, String)
*/
private void setSchema(String schema) {
Object old = this.schema;
this.schema = schema;
if (this.attributeValueHasChanged(old, schema)) {
this.firePropertyChanged(SCHEMA_PROPERTY, old, schema);
this.qualifiedNameChanged();
}
}
public String getShortName() {
return this.shortName;
}
/**
* private - @see #rename(String, String, String)
*/
private void setShortName(String shortName) {
Object old = this.shortName;
this.shortName = shortName;
if (this.attributeValueHasChanged(old, shortName)) {
this.firePropertyChanged(SHORT_NAME_PROPERTY, old, shortName);
this.qualifiedNameChanged();
}
}
private void qualifiedNameChanged() {
this.firePropertyChanged(QUALIFIED_NAME_PROPERTY, this.qualifiedName());
this.getParent().nodeRenamed(this);
for (ELColumn column : this.columns()) {
column.qualifiedNameChanged();
}
}
// ********** columns **********
/**
* this will be null if it is not known
*/
public Date getLastRefreshTimestamp() {
return this.lastRefreshTimestamp;
}
/**
* PRIVATE - this can only be set internally
*/
private void setLastRefreshTimestamp(Date lastRefreshTimestamp) {
Object old = this.lastRefreshTimestamp;
this.lastRefreshTimestamp = lastRefreshTimestamp;
this.firePropertyChanged(LAST_REFRESH_TIMESTAMP_PROPERTY, old, lastRefreshTimestamp);
}
// ********** columns **********
public Iterable<ELColumn> columns() {
return new LiveCloneIterable<ELColumn>(this.columns) {
@Override
protected void remove(ELColumn current) {
ELTable.this.removeColumn(current);
}
};
}
public int columnsSize() {
return this.columns.size();
}
public ELColumn addColumn(String name) {
this.checkColumnName(name);
return this.addColumn(new ELColumn(this, name));
}
private ELColumn addColumn(ELColumn column) {
this.addItemToCollection(column, this.columns, COLUMNS_COLLECTION);
return column;
}
public void removeColumn(ELColumn column) {
this.removeNodeFromCollection(column, this.columns, COLUMNS_COLLECTION);
}
public void removeColumns(Iterator<ELColumn> cols) {
while (cols.hasNext()) {
this.removeColumn(cols.next());
}
}
public void removeColumns(Collection<ELColumn> cols) {
this.removeColumns(cols.iterator());
}
public boolean containsColumnNamed(String columnName) {
return this.columnNamed(columnName) != null;
}
/**
* only used for unqualified column names
* @see #columnWithQualifiedName(String qualifiedName)
*/
public ELColumn columnNamed(String unqualifiedColumnName) {
synchronized (this.columns) {
for (Iterator<ELColumn> stream = this.columns.iterator(); stream.hasNext(); ) {
ELColumn column = stream.next();
if (column.getName().equals(unqualifiedColumnName)) {
return column;
}
}
}
return null;
}
/**
* Returns the column with the specified "qualified" name
*/
public ELColumn columnWithQualifiedName(String name) {
if ( ! ELColumn.parseTableNameFromQualifiedName(name).equals(this.getName())) {
throw new IllegalArgumentException();
}
return this.columnNamed(ELColumn.parseColumnNameFromQualifiedName(name));
}
public Iterator<String> columnNames() {
return new TransformationIterator<ELColumn, String>(this.columns()) {
@Override
protected String transform(ELColumn next) {
return next.getName();
}
};
}
public int primaryKeyColumnsSize() {
int size = 0;
synchronized (this.columns) {
for (ELColumn column : this.columns) {
if (column.isPrimaryKey()) {
size++;
}
}
}
return size;
}
public Iterator<ELColumn> primaryKeyColumns() {
return new FilteringIterator<ELColumn>(this.columns()) {
@Override
protected boolean accept(ELColumn o) {
return o.isPrimaryKey();
}
};
}
public Iterator<String> primaryKeyColumnNames() {
return new TransformationIterator<ELColumn, String>(this.primaryKeyColumns()) {
@Override
protected String transform(ELColumn next) {
return next.getName();
}
};
}
public Iterator<ELColumn> nonPrimaryKeyColumns() {
return new FilteringIterator<ELColumn>(this.columns()) {
@Override
protected boolean accept(ELColumn o) {
return ! o.isPrimaryKey();
}
};
}
/**
* used by table generation
*/
public ELColumn addColumnLike(ELColumn original) {
ELColumn copy = this.addColumn(original.getName());
copy.copySettingsFrom(original);
return copy;
}
// ********** references **********
public Iterable<ELReference> references() {
return new LiveCloneIterable<ELReference>(this.references) {
@Override
protected void remove(ELReference current) {
ELTable.this.removeReference(current);
}
};
}
public int referencesSize() {
return this.references.size();
}
public ELReference addReference(String name, ELTable targetTable) {
this.checkReferenceName(name);
return this.addReference(new ELReference(this, name, targetTable));
}
private ELReference addReference(ELReference reference) {
this.addItemToCollection(reference, this.references, REFERENCES_COLLECTION);
return reference;
}
public void removeReference(ELReference reference) {
this.removeNodeFromCollection(reference, this.references, REFERENCES_COLLECTION);
}
public void removeReferences(Iterator<ELReference> refs) {
while (refs.hasNext()) {
this.removeReference(refs.next());
}
}
public void removeReferences(Collection<ELReference> refs) {
this.removeReferences(refs.iterator());
}
/**
* remove only the references among those specified
* that are defined on the database; leave any "virtual"
* user-defined references
*/
private void removeDatabaseReferences(Iterator<ELReference> refs) {
while (refs.hasNext()) {
ELReference ref = refs.next();
if (ref.isOnDatabase()) {
this.removeReference(ref);
}
}
}
/**
* remove only the references among those specified
* that are defined on the database; leave any "virtual"
* user-defined references
*/
private void removeDatabaseReferences(Collection<ELReference> refs) {
this.removeDatabaseReferences(refs.iterator());
}
public boolean containsReferenceNamed(String referenceName) {
return this.referenceNamed(referenceName) != null;
}
public ELReference referenceNamed(String referenceName) {
synchronized (this.references) {
for (Iterator<ELReference> stream = this.references.iterator(); stream.hasNext(); ) {
ELReference reference = stream.next();
if (reference.getName().equals(referenceName)) {
return reference;
}
}
return null;
}
}
public Iterator<String> referenceNames(){
return new TransformationIterator<ELReference, String>(this.references()) {
@Override
protected String transform(ELReference next) {
return next.getName();
}
};
}
/**
* Returns the references that are actually present
* as constraints on the database
*/
public Iterator<ELReference> databaseReferences() {
return new FilteringIterator<ELReference>(this.references()) {
@Override
protected boolean accept(ELReference o) {
return o.isOnDatabase();
}
};
}
/**
* Returns all the references between the table and the
* specified table (either table can be the source and/or target)
*/
@SuppressWarnings("unchecked")
public Iterator<ELReference> referencesBetween(ELTable table) {
return new CompositeIterator<ELReference>(
this.referencesTo(table),
table.referencesTo(this)
);
}
/**
* Returns all the references with the table as the source and the
* specified table as the target
*/
public Iterator<ELReference> referencesTo(final ELTable targetTable) {
return new FilteringIterator<ELReference>(this.references()) {
@Override
protected boolean accept(ELReference o) {
return o.getTargetTable() == targetTable;
}
};
}
// ********** Nominative implementation **********
/**
* Returns the appropriately-qualified name
*/
public String getName() {
return this.qualifiedName();
}
// ********** queries **********
public DatabasePlatform databasePlatform() {
return this.getParent().getDatabasePlatform();
}
boolean nameMatches(String cat, String sch, String sn) {
return this.valuesAreEqual(this.catalog, cat) &&
this.valuesAreEqual(this.schema, sch) &&
this.valuesAreEqual(this.shortName, sn);
}
boolean nameMatchesIgnoreCase(String cat, String sch, String sn) {
return StringTools.equalsIgnoreCase(this.catalog, cat) &&
StringTools.equalsIgnoreCase(this.schema, sch) &&
StringTools.equalsIgnoreCase(this.shortName, sn);
}
/**
* if either the 'catalog' or 'schema' are specified,
* the table's name is "qualified"
*/
public boolean nameIsQualified() {
if (this.catalog != null) {
return true;
}
if (this.schema != null) {
return true;
}
return false;
}
public boolean nameIsUnqualified() {
return ! this.nameIsQualified();
}
public String qualifiedName() {
return NameTools.buildQualifiedName(this.catalog, this.schema, this.shortName);
}
public String unqualifiedName() {
return this.shortName;
}
/**
* used for table generation:
* catalog.schema
* catalog.
* schema
*/
private String qualifier() {
if (this.nameIsUnqualified()) {
return StringTools.EMPTY_STRING;
}
StringBuffer sb = new StringBuffer(100);
if (this.catalog != null) {
sb.append(this.catalog);
}
if (this.schema != null) {
if (this.catalog != null) {
sb.append('.');
}
sb.append(this.schema);
}
return sb.toString();
}
// ********** miscellaneous behavior **********
/**
* {@inheritDoc}
*/
@Override
public ELDatabase getParent() {
return (ELDatabase)super.getParent();
}
@Override
protected void addChildrenTo(List<Node> children) {
super.addChildrenTo(children);
synchronized (this.columns) { children.addAll(this.columns); }
synchronized (this.references) { children.addAll(this.references); }
}
public void rename(String newCatalog, String newSchema, String newShortName) {
if (this.nameMatches(newCatalog, newSchema, newShortName)) {
// if someone is tryng to rename a table to its existing name, ignore it
return;
}
this.getParent().checkTableName(newCatalog, newSchema, newShortName, this);
this.setCatalog(newCatalog);
this.setSchema(newSchema);
this.setShortName(newShortName);
}
/**
* disallow duplicate column names
*/
void checkColumnName(String columnName) {
if ((columnName == null) || (columnName.length() == 0)) {
throw new IllegalArgumentException();
}
if (this.containsColumnNamed(columnName)) {
throw new IllegalArgumentException("duplicate column name: " + columnName);
}
}
/**
* disallow duplicate reference names
*/
void checkReferenceName(String referenceName) {
if ((referenceName == null) || (referenceName.length() == 0)) {
throw new IllegalArgumentException();
}
if (this.containsReferenceNamed(referenceName)) {
throw new IllegalArgumentException("duplicate reference name: " + referenceName);
}
}
void databasePlatformChanged() {
synchronized (this.columns) {
for (Iterator<ELColumn> stream = this.columns.iterator(); stream.hasNext(); ) {
stream.next().databasePlatformChanged();
}
}
}
// ********** importing/refreshing **********
/**
* Returns the "external" table descriptions that share the table's name;
* Returns multiple entries only when the table's name
* is unqualified
*/
public Iterator<ExternalTableDescription> matchingExternalTableDescriptions() {
return this.getParent().externalTableDescriptions(this.catalog, this.schema, this.shortName, null);
}
/**
* refresh the table's columns (but not the table's references - that
* must be performed separately);
*/
void refreshColumns(ExternalTable externalTable) {
// after we have looped through the external columns,
// 'removedColumns' will be left with the columns that need to be removed
Collection<ELColumn> removedColumns;
synchronized (this.columns) {
removedColumns = new HashSet<ELColumn>(this.columns);
}
ExternalColumn[] externalColumns = externalTable.getColumns();
for (int i = externalColumns.length; i-- > 0; ) {
this.refreshColumn(externalColumns[i], removedColumns);
}
this.removeColumns(removedColumns);
this.setLastRefreshTimestamp(new Date());
}
/**
* refresh the column corresponding to the specified "external" column
*/
private void refreshColumn(ExternalColumn externalColumn, Collection<ELColumn> removedColumns) {
ELColumn existingColumn = this.columnNamed(externalColumn.getName());
if (existingColumn == null) {
// we have a new column
existingColumn = this.addColumn(externalColumn.getName());
} else {
// retain the existing column
removedColumns.remove(existingColumn);
}
existingColumn.refresh(externalColumn);
}
/**
* refresh the table's references - this should be called after
* the table's columns have been refreshed and any target tables
* have had their columns refreshed - this will allow us to build
* the references properly
*/
void refreshReferences(ExternalTable externalTable) {
// after we have looped through the foreign keys,
// 'removedReferences' will be left with the references that need to be removed
Collection<ELReference> removedReferences;
synchronized (this.references) {
removedReferences = new HashSet<ELReference>(this.references);
}
ExternalForeignKey[] externalForeignKeys = externalTable.getForeignKeys();
for (int i = externalForeignKeys.length; i-- > 0; ) {
this.refreshReference(externalForeignKeys[i], removedReferences);
}
// remove *only* the remaining references that were originally defined on the database;
// leave the "virtual" user-defined references intact
this.removeDatabaseReferences(removedReferences);
}
/**
* refresh the reference corresponding to the specified "external" foreign key;
* search the 'removedReferences' so that we don't ever
* refresh the same reference twice; first search for a match based
* on name, then search for a match based on the column pairs - this
* should prevent us from getting a match based on columns that has
* the same name as another reference with different columns that
* is further down the list
*/
private void refreshReference(ExternalForeignKey externalForeignKey, Collection<ELReference> removedReferences) {
// first, find the target table
ExternalTableDescription ttd = externalForeignKey.getTargetTableDescription();
ELTable targetTable = this.getParent().tableNamed(ttd.getCatalogName(), ttd.getSchemaName(), ttd.getName());
if (targetTable == null) {
// the target table may have been imported without
// a fully-qualified name, so try that also
targetTable = this.getParent().tableNamed(null, null, ttd.getName());
}
// if we don't have the target table, we can't build a reference to it
if (targetTable == null) {
return;
}
// look for a match based on name
for (Iterator<ELReference> stream = removedReferences.iterator(); stream.hasNext(); ) {
ELReference ref = stream.next();
if (ref.getName().equals(externalForeignKey.getName())) {
ref.setTargetTable(targetTable);
ref.refreshColumnPairs(externalForeignKey);
ref.setOnDatabase(true);
removedReferences.remove(ref);
return;
}
}
// look for a match based on column pairs
for (Iterator<ELReference> stream = removedReferences.iterator(); stream.hasNext(); ) {
ELReference ref = stream.next();
if (ref.matchesColumnPairs(externalForeignKey)) {
ref.setName(externalForeignKey.getName());
ref.setTargetTable(targetTable);
ref.setOnDatabase(true);
removedReferences.remove(ref);
return;
}
}
// no match - we have a new reference
ELReference ref = this.addReference(externalForeignKey.getName(), targetTable);
ExternalForeignKeyColumnPair[] pairs = externalForeignKey.getColumnPairs();
for (int i = pairs.length; i-- > 0; ) {
ref.addColumnPair(this.column(pairs[i].getSourceColumn()), targetTable.column(pairs[i].getTargetColumn()));
}
ref.setOnDatabase(true);
}
/**
* Returns the column with the same name as the specified "external" column
*/
ELColumn column(ExternalColumn externalColumn) {
return (externalColumn == null) ? null : this.columnNamed(externalColumn.getName());
}
// ********** printing and displaying **********
public void toString(StringBuffer sb) {
sb.append(this.qualifiedName());
}
@Override
public String displayString() {
return this.qualifiedName();
}
}