/******************************************************************************* * Copyright (c) 1998, 2015 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.workbench.mappingsmodel.mapping.relational; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel; import org.eclipse.persistence.tools.workbench.mappingsmodel.ProblemConstants; import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWColumn; import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWColumnPair; import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWReference; import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWTable; import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational.MWRelationalClassDescriptor; import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational.MWRelationalDescriptor; import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational.MWTableDescriptor; import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWHandle; import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWReferenceHandle; import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWTableHandle; import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWHandle.NodeReferenceScrubber; import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapping; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassAttribute; import org.eclipse.persistence.tools.workbench.mappingsmodel.project.relational.MWRelationalProject; import org.eclipse.persistence.tools.workbench.utility.CollectionTools; import org.eclipse.persistence.tools.workbench.utility.node.Node; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.ManyToManyMapping; import org.eclipse.persistence.mappings.OneToOneMapping; import org.eclipse.persistence.oxm.XMLDescriptor; import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping; public final class MWManyToManyMapping extends MWCollectionMapping { private MWReferenceHandle targetReferenceHandle; // holds and persists the target table reference... public final static String TARGET_REFERENCE_PROPERTY = "targetReference"; private MWTableHandle relationTableHandle; public final static String RELATION_TABLE_PROPERTY = "relationTable"; // ********** constructors ********** /** Default constructor - for TopLink use only */ private MWManyToManyMapping() { super(); } MWManyToManyMapping(MWRelationalClassDescriptor descriptor, MWClassAttribute attribute, String name) { super(descriptor, attribute, name); } protected void initialize(Node parent) { super.initialize(parent); this.targetReferenceHandle = new MWReferenceHandle(this, this.buildTargetReferenceScrubber()); this.relationTableHandle = new MWTableHandle(this, this.buildRelationTableScrubber()); } // ********** accessors ********** public MWTable getRelationTable() { return this.relationTableHandle.getTable(); } public void setRelationTable(MWTable newValue) { Object oldValue = this.relationTableHandle.getTable(); this.relationTableHandle.setTable(newValue); firePropertyChanged(RELATION_TABLE_PROPERTY, oldValue, newValue); } public void setSourceReference(MWReference ref) { setReference(ref); } public MWReference getSourceReference() { return getReference(); } public MWReference getTargetReference() { return this.targetReferenceHandle.getReference(); } public void setTargetReference(MWReference newValue) { MWReference oldValue = getTargetReference(); this.targetReferenceHandle.setReference(newValue); firePropertyChanged(TARGET_REFERENCE_PROPERTY, oldValue, newValue); } public boolean isManyToManyMapping(){ return true; } // ************ morphing ************** /** * IMPORTANT: See MWRMapping class comment. */ protected void initializeOn(MWMapping newMapping) { newMapping.initializeFromMWManyToManyMapping(this); } public MWManyToManyMapping asMWManyToManyMapping() { return this; } // ************ containment hierarchy ************** protected void addChildrenTo(List children) { super.addChildrenTo(children); children.add(this.targetReferenceHandle); children.add(this.relationTableHandle); } private NodeReferenceScrubber buildTargetReferenceScrubber() { return new NodeReferenceScrubber() { public void nodeReferenceRemoved(Node node, MWHandle handle) { MWManyToManyMapping.this.setTargetReference(null); } public String toString() { return "MWManyToManyMapping.buildTargetReferenceScrubber()"; } }; } private NodeReferenceScrubber buildRelationTableScrubber() { return new NodeReferenceScrubber() { public void nodeReferenceRemoved(Node node, MWHandle handle) { MWManyToManyMapping.this.setRelationTable(null); } public String toString() { return "MWManyToManyMapping.buildRelationTableScrubber()"; } }; } // ************ MWMapping implementation ************** public String iconKey() { return "mapping.manyToMany"; } // ************* Automap Support **************** public void automap() { super.automap(); this.automapRelationTable(); this.automapSourceReference(); this.automapTargetReference(); } protected void automapTableReference() { // override to do nothing - in a many-to-many mapping the 'table // reference' field holds the the 'source reference', and the source // reference is automapped below (and has nothing to do with the // reference descriptor's table) } /** * find a relation table */ private void automapRelationTable() { if (this.getRelationTable() != null) { return; // if we already have a relation table, do nothing } Iterator tables = this.candidateRelationTables(); if (tables.hasNext()) { // take the first one this.setRelationTable((MWTable) tables.next()); } } /** * find a reference from the relation table to the mapping's descriptor's table */ private void automapSourceReference() { if (this.getSourceReference() != null) { return; // if we already have a source reference, do nothing } Iterator references = this.candidateRelationTableSourceReferences(); if (references.hasNext()) { // take the first one this.setSourceReference((MWReference) references.next()); } } /** * find a reference from the relation table to the reference descriptor's table */ private void automapTargetReference() { if (this.getTargetReference() != null) { return; // if we already have a target reference, do nothing } Iterator stream = this.candidateRelationTableTargetReferences(); if (stream.hasNext()) { // take the first one this.setTargetReference((MWReference) stream.next()); } } /** * return the relation table's source and target references */ protected Set buildCandidateReferences() { Set references = new HashSet(); references.addAll(this.buildCandidateRelationTableSourceReferences()); references.addAll(this.buildCandidateRelationTableTargetReferences()); return references; } /** * return all the tables that might be relation tables for the mapping */ public Iterator candidateRelationTables() { MWTableDescriptor sourceDescriptor = (MWTableDescriptor) this.getParentDescriptor(); MWTableDescriptor referenceDescriptor = (MWTableDescriptor) this.getReferenceDescriptor(); if (referenceDescriptor == null) { // if we don't have a reference descriptor, return *all* the tables return this.getProject().getDatabase().tables(); } Set sourceTables = CollectionTools.set(sourceDescriptor.associatedTables()); Set targetTables = CollectionTools.set(referenceDescriptor.associatedTables()); Collection candidateRelationTables = new ArrayList(); for (Iterator stream = this.unmappedTables(); stream.hasNext(); ) { MWTable unmappedTable = (MWTable) stream.next(); if (this.tableIsPossibleRelationTable(unmappedTable, sourceTables, targetTables)) { candidateRelationTables.add(unmappedTable); } } return candidateRelationTables.iterator(); } /** * return the references from the relation table to the mapping's descriptor's table */ private Set buildCandidateRelationTableSourceReferences() { MWTable relationTable = this.getRelationTable(); if (relationTable == null) { return Collections.EMPTY_SET; } Set references = new HashSet(); Set targetTables = CollectionTools.set(this.getParentRelationalDescriptor().candidateTablesIncludingInherited()); for (Iterator stream = relationTable.references(); stream.hasNext();) { MWReference reference = (MWReference) stream.next(); if (targetTables.contains(reference.getTargetTable())) { references.add(reference); } } return references; } public Iterator candidateRelationTableSourceReferences() { return this.buildCandidateRelationTableSourceReferences().iterator(); } /** * return the references from the relation table to the reference descriptor's table */ private Set buildCandidateRelationTableTargetReferences() { MWTable relationTable = this.getRelationTable(); MWRelationalDescriptor referenceDescriptor = (MWRelationalDescriptor) this.getReferenceDescriptor(); if ((relationTable == null) || (referenceDescriptor == null)) { return Collections.EMPTY_SET; } Set references = new HashSet(); Set targetTables = CollectionTools.set(referenceDescriptor.candidateTablesIncludingInherited()); for (Iterator stream = relationTable.references(); stream.hasNext();) { MWReference reference = (MWReference) stream.next(); if (targetTables.contains(reference.getTargetTable())) { references.add(reference); } } return references; } public Iterator candidateRelationTableTargetReferences() { return this.buildCandidateRelationTableTargetReferences().iterator(); } /** * return whether the specified table could be a relational table for the mapping */ public boolean tableIsPossibleRelationTable(MWTable table) { return CollectionTools.contains(this.candidateRelationTables(), table); } /** * return all the tables that are not associated with a descriptor */ private Iterator unmappedTables() { Set tables = CollectionTools.set(this.getDatabase().tables()); for (Iterator stream = this.getProject().descriptors(); stream.hasNext(); ) { MWRelationalDescriptor descriptor = (MWRelationalDescriptor) stream.next(); CollectionTools.removeAll(tables, descriptor.associatedTables()); } return tables.iterator(); } /** * return whether the specified unmapped table can be used as a relation table * between the two specified sets of tables */ private boolean tableIsPossibleRelationTable(MWTable unmappedTable, Set sourceTables, Set targetTables) { boolean sourceFound = false; boolean targetFound = false; for (Iterator references = unmappedTable.references(); references.hasNext(); ) { MWReference reference = (MWReference) references.next(); boolean workingWithSource = false; boolean workingWithTarget = false; if ( ! sourceFound && sourceTables.contains(reference.getTargetTable())) { workingWithSource = true; } if ( ! targetFound && targetTables.contains(reference.getTargetTable())) { workingWithTarget = true; } if ( ! workingWithSource && ! workingWithTarget) { continue; // skip to the next reference } boolean allTargetColumnsHaveUniqueConstraint = true; for (Iterator columnPairs = reference.columnPairs(); columnPairs.hasNext(); ) { MWColumnPair columnPair = (MWColumnPair) columnPairs.next(); MWColumn targetColumn = columnPair.getTargetColumn(); // check whether the target column is unique (primary keys are always marked 'unique') allTargetColumnsHaveUniqueConstraint &= targetColumn.isUnique(); } if (allTargetColumnsHaveUniqueConstraint) { if (workingWithSource) { sourceFound = true; } if (workingWithTarget) { targetFound = true; } } if (sourceFound && targetFound) { return true; } } return false; } //************** Problem Handling *************** protected void addProblemsTo(List newProblems) { super.addProblemsTo(newProblems); this.addRelationTableNotSpecifiedProblemTo(newProblems); this.addRelationTableNotDedicatedProblemTo(newProblems); this.addSourceReferenceNotSpecifiedProblemTo(newProblems); this.addTargetReferenceNotSpecifiedProblemTo(newProblems); } private void addRelationTableNotSpecifiedProblemTo(List newProblems) { if(getRelationTable() == null) { newProblems.add(buildProblem(ProblemConstants.MAPPING_RELATION_TABLE_NOT_SPECIFIED)); } } private void addRelationTableNotDedicatedProblemTo(List newProblems) { if (this.isReadOnly()) { return; } for (Iterator mappings = ((MWRelationalProject) getProject()).allWriteableManyToManyMappings(); mappings.hasNext(); ) { MWManyToManyMapping mapping = (MWManyToManyMapping) mappings.next(); if (mapping != this && mapping.getRelationTable() == this.getRelationTable()) { if (mapping.getParentRelationalDescriptor().getPrimaryTable() != null && (mapping.getParentRelationalDescriptor().getPrimaryTable() != this.getParentRelationalDescriptor().getPrimaryTable())) newProblems.add(buildProblem(ProblemConstants.MAPPING_RELATION_TABLE_NOT_DEDICATED)); } } } private void addSourceReferenceNotSpecifiedProblemTo(List newProblems) { if (this.getSourceReference() == null) { newProblems.add(buildProblem(ProblemConstants.MAPPING_SOURCE_TABLE_REFERENCE_NOT_SPECIFIED)); } } private void addTargetReferenceNotSpecifiedProblemTo(List newProblems) { if (this.getTargetReference() == null) { newProblems.add(buildProblem(ProblemConstants.MAPPING_TARGET_TABLE_REFERENCE_NOT_SPECIFIED)); } } // ************ runtime conversion ************** protected DatabaseMapping buildRuntimeMapping() { return new ManyToManyMapping(); } public DatabaseMapping runtimeMapping() { ManyToManyMapping manyToManyMapping = (ManyToManyMapping) super.runtimeMapping(); MWTable relationTable = getRelationTable(); if (relationTable != null) { manyToManyMapping.setRelationTableName(relationTable.getName()); } if (getSourceReference() != null) { for (Iterator stream = getSourceReference().columnPairs(); stream.hasNext(); ) { MWColumnPair pair = (MWColumnPair) stream.next(); MWColumn sourceColumn = pair.getSourceColumn(); MWColumn targetColumn = pair.getTargetColumn(); if ((sourceColumn != null) && (targetColumn != null)) { if (!parentDescriptorIsAggregate()) { manyToManyMapping.addSourceRelationKeyFieldName(sourceColumn.qualifiedName(), targetColumn.qualifiedName()); } else { manyToManyMapping.addSourceRelationKeyFieldName(sourceColumn.qualifiedName(), getName() + "->" + targetColumn.getName() + "_IN_REFERENCE_" + getSourceReference().getName()); } } } } if (getTargetReference() != null) { for (Iterator stream = getTargetReference().columnPairs(); stream.hasNext(); ) { MWColumnPair pair = (MWColumnPair) stream.next(); MWColumn sourceColumn = pair.getSourceColumn(); MWColumn targetColumn = pair.getTargetColumn(); if ((sourceColumn != null) && (targetColumn != null)) { manyToManyMapping.addTargetRelationKeyFieldName(sourceColumn.qualifiedName(), targetColumn.qualifiedName()); } } } return manyToManyMapping; } // ************* TopLink methods ************* public static XMLDescriptor buildDescriptor() { XMLDescriptor descriptor = new XMLDescriptor(); descriptor.setJavaClass(MWManyToManyMapping.class); descriptor.getInheritancePolicy().setParentClass(MWCollectionMapping.class); XMLCompositeObjectMapping relationTableMapping = new XMLCompositeObjectMapping(); relationTableMapping.setAttributeName("relationTableHandle"); relationTableMapping.setGetMethodName("getRelationTableHandleForTopLink"); relationTableMapping.setSetMethodName("setRelationTableHandleForTopLink"); relationTableMapping.setReferenceClass(MWTableHandle.class); relationTableMapping.setXPath("relation-table-handle"); descriptor.addMapping(relationTableMapping); XMLCompositeObjectMapping targetReferenceHandleMapping = new XMLCompositeObjectMapping(); targetReferenceHandleMapping.setAttributeName("targetReferenceHandle"); targetReferenceHandleMapping.setGetMethodName("getTargetReferenceHandleForTopLink"); targetReferenceHandleMapping.setSetMethodName("setTargetReferenceHandleForTopLink"); targetReferenceHandleMapping.setReferenceClass(MWReferenceHandle.class); targetReferenceHandleMapping.setXPath("target-reference-handle"); descriptor.addMapping(targetReferenceHandleMapping); return descriptor; } /** * check for null */ private MWTableHandle getRelationTableHandleForTopLink() { return (this.relationTableHandle.getTable() == null) ? null : this.relationTableHandle; } private void setRelationTableHandleForTopLink(MWTableHandle relationTableHandle) { NodeReferenceScrubber scrubber = this.buildRelationTableScrubber(); this.relationTableHandle = ((relationTableHandle == null) ? new MWTableHandle(this, scrubber) : relationTableHandle.setScrubber(scrubber)); } /** * check for null */ private MWReferenceHandle getTargetReferenceHandleForTopLink() { return (this.targetReferenceHandle.getReference() == null) ? null : this.targetReferenceHandle; } private void setTargetReferenceHandleForTopLink(MWReferenceHandle targetReferenceHandle) { NodeReferenceScrubber scrubber = this.buildTargetReferenceScrubber(); this.targetReferenceHandle = ((targetReferenceHandle == null) ? new MWReferenceHandle(this, scrubber) : targetReferenceHandle.setScrubber(scrubber)); } }