blob: e1d371afb90de2e190cc7834ce690ad21200c68e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. 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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.persistence.tools.db.relational.handles.MWHandle;
import org.eclipse.persistence.tools.db.relational.handles.MWHandle.NodeReferenceScrubber;
import org.eclipse.persistence.tools.db.relational.handles.MWTableHandle;
import org.eclipse.persistence.tools.db.relational.spi.ExternalForeignKey;
import org.eclipse.persistence.tools.db.relational.spi.ExternalForeignKeyColumnPair;
import org.eclipse.persistence.tools.utility.collection.CollectionTools;
import org.eclipse.persistence.tools.utility.iterable.LiveCloneIterable;
import org.eclipse.persistence.tools.utility.node.Node;
/**
* A reference describes a foreign-key relationship from a "source" table (the reference's "parent")
* to a "target" table. The foreign-key constraint can either be real ("on database") or virtual
* (only implied by the joins performed by TopLink).
*
* @version 2.6
*/
@SuppressWarnings("nls")
public final class ELReference extends ELModel {
/** the name should never be null or empty */
private volatile String name;
public static final String NAME_PROPERTY = "name";
private MWTableHandle targetTableHandle;
public static final String TARGET_TABLE_PROPERTY = "targetTable";
private Collection<ELColumnPair> columnPairs;
public static final String COLUMN_PAIRS_COLLECTION = "columnPairs";
/**
* indicate whether the reference is an actual database constraint or
* only used by TopLink for joining
*/
private volatile boolean onDatabase;
public static final String ON_DATABASE_PROPERTY = "onDatabase";
// ********** constructors **********
ELReference(ELTable parent, String name, ELTable targetTable) {
super(parent);
this.name = name;
this.targetTableHandle.setTable(targetTable);
}
// ********** initialization **********
/**
* initialize persistent state
*/
@Override
protected void initialize(Node parent) {
super.initialize(parent);
this.targetTableHandle = new MWTableHandle(this, this.buildTargetTableScrubber());
this.columnPairs = new Vector<ELColumnPair>();
this.onDatabase = false;
}
// ********** accessors **********
public ELTable getSourceTable() {
return (ELTable) this.getParent();
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.getSourceTable().checkReferenceName(name);
Object old = this.name;
this.name = name;
this.firePropertyChanged(NAME_PROPERTY, old, name);
}
public ELTable getTargetTable() {
return this.targetTableHandle.getTable();
}
/**
* if the target table changes, we clear out all the
* column pairs, because they are now invalid
*/
public void setTargetTable(ELTable targetTable) {
Object old = this.targetTableHandle.getTable();
this.targetTableHandle.setTable(targetTable);
this.firePropertyChanged(TARGET_TABLE_PROPERTY, old, targetTable);
if (this.attributeValueHasChanged(old, targetTable)) {
this.clearColumnPairs();
}
}
public boolean isOnDatabase() {
return this.onDatabase;
}
public void setOnDatabase(boolean onDatabase) {
boolean old = this.onDatabase;
this.onDatabase = onDatabase;
this.firePropertyChanged(ON_DATABASE_PROPERTY, old, onDatabase);
}
// ********** column pairs
public Iterable<ELColumnPair> columnPairs() {
return new LiveCloneIterable<ELColumnPair>(this.columnPairs) {
@Override
protected void remove(ELColumnPair current) {
ELReference.this.removeColumnPair(current);
}
};
}
public int columnPairsSize() {
return this.columnPairs.size();
}
public ELColumnPair addColumnPair(ELColumn sourceColumn, ELColumn targetColumn) {
if (sourceColumn.getTable() != this.getSourceTable()) {
throw new IllegalArgumentException("invalid source column: " + sourceColumn);
}
if (targetColumn.getTable() != this.getTargetTable()) {
throw new IllegalArgumentException("invalid target column: " + targetColumn);
}
return this.addColumnPair(new ELColumnPair(this, sourceColumn, targetColumn));
}
private ELColumnPair addColumnPair(ELColumnPair columnPair) {
this.addItemToCollection(columnPair, this.columnPairs, COLUMN_PAIRS_COLLECTION);
return columnPair;
}
public void removeColumnPair(ELColumnPair columnPair) {
this.removeItemFromCollection(columnPair, this.columnPairs, COLUMN_PAIRS_COLLECTION);
}
public void removeColumnPairs(Iterator<ELColumnPair> pairs) {
while (pairs.hasNext()) {
this.removeColumnPair(pairs.next());
}
}
public void removeColumnPairs(Collection<ELColumnPair> pairs) {
this.removeColumnPairs(pairs.iterator());
}
private void clearColumnPairs() {
clearCollection(this.columnPairs, COLUMN_PAIRS_COLLECTION);
}
/**
* used by MWColumnPairHandle
*/
public ELColumnPair columnPairNamed(String columnPairName) {
synchronized (this.columnPairs) {
for (ELColumnPair columnPair : this.columnPairs) {
if (columnPair.getName().equals(columnPairName)) {
return columnPair;
}
}
}
return null;
}
public ELColumnPair columnPairFor(ELColumn sourceColumn, ELColumn targetColumn) {
synchronized (this.columnPairs) {
for (ELColumnPair columnPair : this.columnPairs) {
if (columnPair.pairs(sourceColumn, targetColumn)) {
return columnPair;
}
}
}
return null;
}
// ********** queries **********
/**
* this test is not entirely accurate, but it's close enough...
*/
public boolean isForeignKeyReference() {
return ! this.isPrimaryKeyReference();
}
/**
* Returns whether the reference is a JOIN between the
* primary keys on the source table with the primary keys
* on the target table
*/
public boolean isPrimaryKeyReference() {
Collection sourcePrimaryKeys = CollectionTools.collection(this.getSourceTable().primaryKeyColumns());
if (sourcePrimaryKeys.size() != this.columnPairs.size()) {
return false;
}
Collection targetPrimaryKeys = CollectionTools.collection(this.getTargetTable().primaryKeyColumns());
if (targetPrimaryKeys.size() != this.columnPairs.size()) {
return false;
}
synchronized (this.columnPairs) {
for (Iterator stream = this.columnPairs.iterator(); stream.hasNext(); ) {
ELColumnPair pair = (ELColumnPair) stream.next();
if ( ! sourcePrimaryKeys.remove(pair.getSourceColumn())) {
return false;
}
if ( ! targetPrimaryKeys.remove(pair.getTargetColumn())) {
return false;
}
}
}
return true;
}
// ********** model synchronization support **********
@Override
protected void addChildrenTo(List children) {
super.addChildrenTo(children);
children.add(this.targetTableHandle);
synchronized (this.columnPairs) { children.addAll(this.columnPairs); }
}
private NodeReferenceScrubber buildTargetTableScrubber() {
return new NodeReferenceScrubber() {
@Override
public void nodeReferenceRemoved(Node node, MWHandle handle) {
ELReference.this.setTargetTable(null);
}
@Override
public String toString() {
return "MWReference.buildTargetTableScrubber()";
}
};
}
// ********** importing/refreshing **********
/**
* the reference has the same name as the specified "external" foreign key,
* synchronize the reference's column pairs with the "external" foreign key's column pairs
*/
void refreshColumnPairs(ExternalForeignKey externalForeignKey) {
// after we have looped through the "external" column pairs,
// 'removedColumnPairs' will be left with the column pairs that need to be removed
Collection removedColumnPairs;
synchronized (this.columnPairs) {
removedColumnPairs = new HashSet(this.columnPairs);
}
ExternalForeignKeyColumnPair[] externalPairs = externalForeignKey.getColumnPairs();
for (int i = externalPairs.length; i-- > 0; ) {
this.refreshColumnPair(externalPairs[i], removedColumnPairs);
}
this.removeColumnPairs(removedColumnPairs);
}
private void refreshColumnPair(ExternalForeignKeyColumnPair externalPair, Collection removedColumnPairs) {
for (Iterator stream = removedColumnPairs.iterator(); stream.hasNext(); ) {
if (((ELColumnPair) stream.next()).matches(externalPair)) {
stream.remove();
return;
}
}
this.addColumnPair(this.sourceColumn(externalPair), this.targetColumn(externalPair));
}
/**
* Returns the column in the source table with the same name as the
* "external" column pair's source column
*/
ELColumn sourceColumn(ExternalForeignKeyColumnPair externalPair) {
return this.getSourceTable().column(externalPair.getSourceColumn());
}
/**
* Returns the column in the target table with the same name as the
* "external" column pair's target column
*/
ELColumn targetColumn(ExternalForeignKeyColumnPair externalPair) {
return this.getTargetTable().column(externalPair.getTargetColumn());
}
/**
* Returns whether the reference has the same column pairs as
* the specified "external" foreign key;
* we compare column pairs because some references have system-
* generated names that can change over the life of the reference
* and we don't want to remove a reference simply because its
* name changes - this would force the user to rebuild any objects
* that referenced the [mistakenly] removed reference, e.g. mappings
*/
boolean matchesColumnPairs(ExternalForeignKey externalForeignKey) {
ExternalForeignKeyColumnPair[] externalPairs = externalForeignKey.getColumnPairs();
int externalPairsLength = externalPairs.length;
if (this.columnPairs.size() != externalPairsLength) {
return false;
}
Collection columnPairsCopy;
synchronized (this.columnPairs) {
columnPairsCopy = new HashSet(this.columnPairs);
}
for (int i = externalPairsLength; i-- > 0; ) {
ExternalForeignKeyColumnPair externalPair = externalPairs[i];
boolean match = false;
for (Iterator stream = columnPairsCopy.iterator(); stream.hasNext(); ) {
if (((ELColumnPair) stream.next()).matches(externalPair)) {
stream.remove();
match = true;
break;
}
}
if ( ! match) {
return false;
}
}
return true;
}
// ********** displaying and printing **********
@Override
public String displayString() {
return this.name;
}
public void toString(StringBuffer sb) {
sb.append(this.name);
sb.append(" : ");
this.printTableNameOn(this.getSourceTable(), sb);
sb.append("=>");
this.printTableNameOn(this.getTargetTable(), sb);
}
private void printTableNameOn(ELTable table, StringBuffer sb) {
sb.append((table == null) ? "null" : table.getName());
}
}