| /******************************************************************************* |
| * 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.model; |
| |
| 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.model.handles.MWHandle; |
| import org.eclipse.persistence.tools.db.model.handles.MWHandle.NodeReferenceScrubber; |
| import org.eclipse.persistence.tools.db.model.handles.MWTableHandle; |
| import org.eclipse.persistence.tools.db.model.spi.ExternalForeignKey; |
| import org.eclipse.persistence.tools.db.model.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 pairs) { |
| while (pairs.hasNext()) { |
| this.removeColumnPair((ELColumnPair) pairs.next()); |
| } |
| } |
| |
| public void removeColumnPairs(Collection pairs) { |
| this.removeColumnPairs(pairs.iterator()); |
| } |
| |
| private void clearColumnPairs() { |
| this.clearCollection(this.columnPairs, COLUMN_PAIRS_COLLECTION); |
| } |
| |
| /** |
| * used by MWColumnPairHandle |
| */ |
| public ELColumnPair columnPairNamed(String columnPairName) { |
| synchronized (this.columnPairs) { |
| for (Iterator stream = this.columnPairs.iterator(); stream.hasNext(); ) { |
| ELColumnPair pair = (ELColumnPair) stream.next(); |
| if (pair.getName().equals(columnPairName)) { |
| return pair; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public ELColumnPair columnPairFor(ELColumn sourceColumn, ELColumn targetColumn) { |
| synchronized (this.columnPairs) { |
| for (Iterator stream = this.columnPairs.iterator(); stream.hasNext(); ) { |
| ELColumnPair pair = (ELColumnPair) stream.next(); |
| if (pair.pairs(sourceColumn, targetColumn)) { |
| return pair; |
| } |
| } |
| } |
| 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()); |
| } |
| } |