/******************************************************************************* * 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.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; 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.descriptor.MWDescriptor; import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.MWMappingDescriptor; import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational.MWAggregateDescriptor; 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.MWDescriptorHandle; import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWHandle; 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.mapping.MWReferenceObjectMapping; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClass; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassAttribute; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassCodeGenPolicy; import org.eclipse.persistence.tools.workbench.mappingsmodel.query.MWQueryable; import org.eclipse.persistence.tools.workbench.utility.CollectionTools; import org.eclipse.persistence.tools.workbench.utility.filters.Filter; import org.eclipse.persistence.tools.workbench.utility.iterators.CloneIterator; import org.eclipse.persistence.tools.workbench.utility.node.Node; import org.eclipse.persistence.tools.workbench.utility.string.CollectionStringHolder; import org.eclipse.persistence.tools.workbench.utility.string.PartialStringComparator; import org.eclipse.persistence.tools.workbench.utility.string.PartialStringMatcher; import org.eclipse.persistence.tools.workbench.utility.string.SimplePartialStringMatcher; import org.eclipse.persistence.tools.workbench.utility.string.StringTools; import org.eclipse.persistence.tools.workbench.utility.string.PartialStringMatcher.StringHolderScore; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.mappings.AggregateObjectMapping; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.OneToOneMapping; import org.eclipse.persistence.oxm.XMLDescriptor; import org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping; import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping; import org.eclipse.persistence.oxm.mappings.XMLDirectMapping; public final class MWAggregateMapping extends MWMapping implements MWReferenceObjectMapping { private volatile boolean allowsNull; public final static String ALLOWS_NULL_PROPERTY = "allowsNull"; private Collection pathsToFields; public final static String PATHS_TO_FIELDS_COLLECTION = "pathsToFields"; private MWDescriptorHandle referenceDescriptorHandle; //************** constructors ******************* /** Default constructor - for TopLink use only */ private MWAggregateMapping() { super(); } MWAggregateMapping(MWMappingDescriptor descriptor, MWClassAttribute attribute, String name) { super(descriptor, attribute, name); } //************** initialization ******************* protected void initialize(Node parent) { super.initialize(parent); this.pathsToFields = new Vector(); this.referenceDescriptorHandle = new MWDescriptorHandle(this, this.buildReferenceDescriptorScrubber()); } //************** containment hierarchy ******************* protected void addChildrenTo(List children) { super.addChildrenTo(children); synchronized (this.pathsToFields) { children.addAll(this.pathsToFields); } children.add(this.referenceDescriptorHandle); } private NodeReferenceScrubber buildReferenceDescriptorScrubber() { return new NodeReferenceScrubber() { public void nodeReferenceRemoved(Node node, MWHandle handle) { MWAggregateMapping.this.setReferenceDescriptor(null); } public String toString() { return "MWAggregateMapping.buildReferenceDescriptorScrubber()"; } }; } public void descriptorReplaced(MWDescriptor oldDescriptor, MWDescriptor newDescriptor) { if (this.getReferenceDescriptor() == oldDescriptor) { this.setReferenceDescriptor(newDescriptor); } } // **************** MWRelationalMapping implementation *************** public boolean parentDescriptorIsAggregate() { return ((MWRelationalDescriptor) getParentDescriptor()).isAggregateDescriptor(); } public MWRelationalDescriptor getParentRelationalDescriptor() { return (MWRelationalDescriptor) getParentDescriptor(); } // **************** Accessors *************** public boolean allowsNull() { return this.allowsNull; } public void setAllowsNull(boolean newValue) { boolean oldValue = this.allowsNull; this.allowsNull = newValue; this.firePropertyChanged(ALLOWS_NULL_PROPERTY, oldValue, newValue); } public Iterator pathsToFields() { return new CloneIterator(this.pathsToFields); } public int pathsToFieldsSize() { return this.pathsToFields.size(); } private Collection getPathsToFieldsDeepCopy(MWAggregateMapping newParent) { Collection result = new Vector(this.pathsToFields.size()); for (Iterator stream = this.pathsToFields(); stream.hasNext();) { result.add(((MWAggregatePathToColumn) stream.next()).copy(newParent)); } return result; } public MWDescriptor getReferenceDescriptor() { return this.referenceDescriptorHandle.getDescriptor(); } public void setReferenceDescriptor(MWDescriptor referenceDescriptor) { Object oldValue = getReferenceDescriptor(); this.referenceDescriptorHandle.setDescriptor(referenceDescriptor); firePropertyChanged(REFERENCE_DESCRIPTOR_PROPERTY, oldValue, referenceDescriptor); updatePathsToFields(); getProject().recalculateAggregatePathsToColumn(getParentDescriptor(), this); //newValue will not be null if the method resetReferenceDescriptor called this method. //resetReferenceDescriptor is called when morphing mappings. The mapping's parent would //not be set at this point, so we must get the project from a different object. if (referenceDescriptor != null) referenceDescriptor.getProject().notifyExpressionsToRecalculateQueryables(); else getProject().notifyExpressionsToRecalculateQueryables(); } public boolean descriptorIsValidReferenceDescriptor(MWDescriptor descriptor) { return ((MWRelationalDescriptor) descriptor).isAggregateDescriptor(); } // **************** morphing *************** /** * IMPORTANT: See MWRMapping class comment. */ protected void initializeOn(MWMapping newMapping) { newMapping.initializeFromMWAggregateMapping(this); } protected void initializeFromMWReferenceObjectMapping(MWReferenceObjectMapping oldMapping) { super.initializeFromMWReferenceObjectMapping(oldMapping); this.setReferenceDescriptor(oldMapping.getReferenceDescriptor()); } public MWAggregateMapping asMWAggregateMapping() { return this; } // **************** pathToFields behavior *************** /** * Insert the given rootNode into the mappingNodes list of all the given subAssociations */ private void insertRootNodeIntoAll(MWMapping rootNode, Collection subAPTFs) { for (Iterator stream = subAPTFs.iterator(); stream.hasNext();) { MWAggregatePathToColumn aptf = (MWAggregatePathToColumn) stream.next(); aptf.insertRootMappingNode(rootNode); } } private boolean compareAPTFs(MWAggregatePathToColumn aptf1, MWAggregatePathToColumn aptf2) { return (aptf1.getMappingNodes().containsAll(aptf2.getMappingNodes())) && (aptf2.getMappingNodes().containsAll(aptf1.getMappingNodes())) && (aptf1.getAggregateRuntimeFieldNameGenerator().fieldNameForRuntime().equals(aptf2.getAggregateRuntimeFieldNameGenerator().fieldNameForRuntime())); } /** * Remove the specified APTF from the specified collection. * Return whether the collection changed. */ private boolean collectionRemoveAPTF(Collection collection, MWAggregatePathToColumn aptf) { for (Iterator stream = collection.iterator(); stream.hasNext();) { if (this.compareAPTFs((MWAggregatePathToColumn) stream.next(), aptf)) { stream.remove(); return true; } } return false; } /** * Sets the pathsToFields to the union of the existing * pathsToFields and the argument, plus any additional * paths in the argument. * * In other words, if the existing path contains A, B, and C, and the * argument contains C', D', and E', (where C and C' have the same * path), then the new pathsToFields will contain C, D', and E'. */ private void mergePathsToFields(Collection recalculatedPathsToFields) { // remove any APTFs not in the recalculated set, // while simultaneously removing the duplicates from the recalculated set for (Iterator stream = this.pathsToFields.iterator(); stream.hasNext();) { MWAggregatePathToColumn aptf = (MWAggregatePathToColumn) stream.next(); if (!this.collectionRemoveAPTF(recalculatedPathsToFields, aptf)) { stream.remove(); } } // add whatever is left in the recalculated set addItemsToCollection(recalculatedPathsToFields, this.pathsToFields, PATHS_TO_FIELDS_COLLECTION); } private MWAggregatePathToColumn buildAPTF(MWMapping node, AggregateRuntimeFieldNameGenerator aggregateFieldNameGenerator) { MWAggregatePathToColumn newAPTF = new MWAggregatePathToColumn(this); newAPTF.addMappingNode(node); newAPTF.setAggregateRuntimeFieldNameGenerator(aggregateFieldNameGenerator); return newAPTF; } private MWAggregatePathToColumn buildAPTF(AggregateRuntimeFieldNameGenerator aggregateFieldNameGenerator) { MWAggregatePathToColumn newAPTF = new MWAggregatePathToColumn(this); newAPTF.setAggregateRuntimeFieldNameGenerator(aggregateFieldNameGenerator); return newAPTF; } /** * Updates the paths to fields for this aggregate mapping. */ public void updatePathsToFields() { Collection newAPTFs = new ArrayList(); if (this.getReferenceDescriptor() != null && getReferenceDescriptor() != getParentDescriptor()) { for (Iterator stream1 = this.getReferenceDescriptor().mappingsIncludingInherited(); stream1.hasNext();) { MWMapping subMapping = (MWMapping) stream1.next(); if (subMapping instanceof MWAggregateMapping) { ((MWAggregateMapping) subMapping).updatePathsToFields(); Collection subAPTFs = ((MWAggregateMapping) subMapping).getPathsToFieldsDeepCopy(this); insertRootNodeIntoAll(subMapping, subAPTFs); newAPTFs.addAll(subAPTFs); } else { for (Iterator stream2 = subMapping.aggregateFieldNameGenerators(); stream2.hasNext();) { newAPTFs.add(this.buildAPTF(subMapping, (AggregateRuntimeFieldNameGenerator) stream2.next())); } } } for (Iterator stream = ((MWRelationalDescriptor) getReferenceDescriptor()).buildAggregateFieldNameGenerators().iterator(); stream.hasNext(); ) { newAPTFs.add(this.buildAPTF((AggregateRuntimeFieldNameGenerator) stream.next())); } } this.mergePathsToFields(newAPTFs); this.fireCollectionChanged(PATHS_TO_FIELDS_COLLECTION); } // ************** Code Generation ************** /** * Used for code gen. * See MWRMapping.initialValue() */ public String initialValue(MWClassCodeGenPolicy classCodeGenPolicy) { String initialValue = super.initialValue(classCodeGenPolicy); if (! this.allowsNull()) { if (getReferenceDescriptor() != null) { initialValue = getInstanceVariable().initialValueSourceCodeFor(getReferenceDescriptor().getMWClass()); } if ("".equals(initialValue)) { // the initial value could not be determined initialValue = classCodeGenPolicy.aggregateMappingDoesNotAllowNullImplementationClassNotDeterminedComment() + StringTools.CR + "\t\t" + null; } initialValue = StringTools.CR + "\t\t" + classCodeGenPolicy.aggregateMappingDoesNotAllowNullComment(this) + StringTools.CR + "\t\t" + initialValue; } return initialValue; } // ************** MWQueryable implementation ************** public boolean allowsChildren() { return true; } public boolean isLeaf(Filter queryableFilter) { return subQueryableElements(queryableFilter).size() == 0; } public List subQueryableElements(Filter queryableFilter) { List subQueryableElements = new ArrayList(); if (getReferenceDescriptor() != null) { subQueryableElements = ((MWRelationalDescriptor) getReferenceDescriptor()).getQueryables(queryableFilter); Collections.sort(subQueryableElements, DEFAULT_COMPARATOR); } return subQueryableElements; } public MWQueryable subQueryableElementAt(int index, Filter queryableFilter) { return (MWQueryable) subQueryableElements(queryableFilter).get(index); } public boolean isTraversableForReadAllQueryOrderable() { return true; } public boolean isTraversableForBatchReadAttribute() { return true; } public boolean isTraversableForJoinedAttribute() { return true; } public boolean isTraversableForQueryExpression() { return true; } public boolean isValidForQueryExpression() { return true; } public boolean isTraversableForReportQueryAttribute() { return true; } public String iconKey() { return "mapping.aggregate"; } // ************* Automap Support ************************* public void automap() { super.automap(); this.automapReferenceDescriptor(); } private void automapReferenceDescriptor() { if (this.getReferenceDescriptor() != null) { return; } MWDescriptor referenceDescriptor = this.findReferenceDescriptor(); if (referenceDescriptor != null) { this.setReferenceDescriptor(referenceDescriptor); } } private MWDescriptor findReferenceDescriptor() { MWClass type = this.getInstanceVariable().getType(); // calculate a name to use for finding a reference descriptor; // if the type is "non-descript", use the attribute name; // otherwise, use the "short" name of the attribute's declared type String name; if (type.isPrimitive() || type.isValueHolder() || type.isAssignableToMap() || type.isAssignableToCollection() || type.isAssignableTo(this.typeFor(Number.class)) || type == this.typeFor(String.class)) { name = this.getName(); } else { name = type.shortName(); } CollectionStringHolder[] holders = this.buildMultiDescriptorStringHolders(); StringHolderScore shs = this.match(name.toLowerCase(), holders); if (shs.getScore() < 0.80) { // ??? return null; } // look for a descriptor in the same package as this descriptor String packageName = this.getParentDescriptor().packageName(); MWDescriptor descriptor = null; for (Iterator stream = ((CollectionStringHolder) shs.getStringHolder()).iterator(); stream.hasNext(); ) { descriptor = (MWDescriptor) stream.next(); if (descriptor.packageName().equals(packageName)) { return descriptor; } } // if none of the descriptors is in the same package, take the last one return descriptor; } /** * gather together all the "candidate" descriptors that have * the same "short" name but different packages */ private CollectionStringHolder[] buildMultiDescriptorStringHolders() { Collection descriptors = this.candidateReferenceDescriptors(); Map holders = new HashMap(descriptors.size()); for (Iterator stream = descriptors.iterator(); stream.hasNext(); ) { MWDescriptor descriptor = (MWDescriptor) stream.next(); String shortName = descriptor.shortName().toLowerCase(); CollectionStringHolder holder = (CollectionStringHolder) holders.get(shortName); if (holder == null) { holder = new CollectionStringHolder(shortName); holders.put(shortName, holder); } holder.add(descriptor); } return (CollectionStringHolder[]) holders.values().toArray(new CollectionStringHolder[holders.size()]); } private Collection candidateReferenceDescriptors() { Collection descriptors = new ArrayList(); for (Iterator stream = this.getProject().descriptors(); stream.hasNext(); ) { MWDescriptor descriptor = (MWDescriptor) stream.next(); if (this.descriptorIsCandidateReferenceDescriptor(descriptor)) { descriptors.add(descriptor); } } return descriptors; } private boolean descriptorIsCandidateReferenceDescriptor(MWDescriptor descriptor) { return (descriptor != this.getParentDescriptor()) && ((MWRelationalDescriptor) descriptor).isAggregateDescriptor(); } private StringHolderScore match(String string, CollectionStringHolder[] multiDescriptorStringHolders) { return PARTIAL_STRING_MATCHER.match(string, multiDescriptorStringHolders); } private static final PartialStringMatcher PARTIAL_STRING_MATCHER = new SimplePartialStringMatcher(PartialStringComparator.DEFAULT_COMPARATOR); //************** Problem Handling ******************* protected void addProblemsTo(List newProblems) { super.addProblemsTo(newProblems); this.checkReferenceDescriptorIsValid(newProblems); this.checkColumnsAreUnique(newProblems); this.checkColumnsAreValid(newProblems); } private void checkReferenceDescriptorIsValid(List newProblems) { if (this.getReferenceDescriptor() == null) { newProblems.add(this.buildProblem(ProblemConstants.MAPPING_REFERENCE_DESCRIPTOR_NOT_SPECIFIED)); } else if ( ! this.getReferenceDescriptor().isActive()) { newProblems.add(this.buildProblem(ProblemConstants.MAPPING_REFERENCE_DESCRIPTOR_IS_INACTIVE, this.getInstanceVariable().getName(), this.getReferenceDescriptor().getMWClass().shortName())); } else if ( ! this.descriptorIsValidReferenceDescriptor(this.getReferenceDescriptor())) { newProblems.add(this.buildProblem(ProblemConstants.MAPPING_REFERENCE_DESCRIPTOR_NOT_AGGREGATE_DESCRIPTOR)); } } private void checkColumnsAreUnique(List newProblems) { if (this.getParentDescriptor() instanceof MWAggregateDescriptor) return; int numFields = this.pathsToFieldsSize(); HashSet writeableFieldsSet = new HashSet(numFields); for (Iterator stream = this.pathsToFields(); stream.hasNext(); ) { MWAggregatePathToColumn pathToField = (MWAggregatePathToColumn) stream.next(); MWColumn field = pathToField.getColumn(); if (field != null && pathToField.fieldIsWritten() && ! writeableFieldsSet.add(field)) { newProblems.add(this.buildProblem(ProblemConstants.MAPPING_AGGREGATE_COLUMNS_NOT_UNIQUE)); } } } private void checkColumnsAreValid(List newProblems) { if (this.parentDescriptorIsAggregate()) { return; } for (Iterator stream = this.pathsToFields(); stream.hasNext(); ) { MWColumn field = ((MWAggregatePathToColumn) stream.next()).getColumn(); if (field == null) { newProblems.add(this.buildProblem(ProblemConstants.MAPPING_AGGREGATE_COLUMNS_NOT_SPECIFIED)); } else if ( ! CollectionTools.contains(((MWTableDescriptor) this.getParentDescriptor()).allAssociatedColumns(), field)) { newProblems.add(this.buildProblem(ProblemConstants.MAPPING_AGGREGATE_COLUMNS_NOT_VALID)); } } } // **************** MWRelationalMapping implementation ***************** public void addWrittenFieldsTo(Collection writtenFields) { for (Iterator stream = pathsToFields(); stream.hasNext(); ) { MWAggregatePathToColumn pathToField = (MWAggregatePathToColumn) stream.next(); if (pathToField.getColumn() != null && pathToField.fieldIsWritten()) { writtenFields.add(pathToField.getColumn()); } } } //************** Runtime Conversion ******************* protected DatabaseMapping buildRuntimeMapping() { return new AggregateObjectMapping(); } public DatabaseMapping runtimeMapping() { AggregateObjectMapping runtimeMapping = (AggregateObjectMapping) super.runtimeMapping(); if (getReferenceDescriptor() != null) { runtimeMapping.setReferenceClassName(getReferenceDescriptor().getMWClass().getName()); } runtimeMapping.setIsNullAllowed(allowsNull()); convertPathsToFieldsToRuntime(runtimeMapping); return runtimeMapping; } private void convertPathsToFieldsToRuntime(AggregateObjectMapping runtimeMapping) { for (Iterator i = pathsToFields(); i.hasNext(); ) { MWAggregatePathToColumn pathToField = (MWAggregatePathToColumn) i.next(); pathToField.adjustRuntimeMapping(runtimeMapping); } } // ********** TopLink methods ********** public static XMLDescriptor buildDescriptor() { XMLDescriptor descriptor = new XMLDescriptor(); descriptor.setJavaClass(MWAggregateMapping.class); descriptor.getInheritancePolicy().setParentClass(MWMapping.class); XMLCompositeObjectMapping referenceDescriptorHandleMapping = new XMLCompositeObjectMapping(); referenceDescriptorHandleMapping.setAttributeName("referenceDescriptorHandle"); referenceDescriptorHandleMapping.setGetMethodName("getReferenceDescriptorHandleForTopLink"); referenceDescriptorHandleMapping.setSetMethodName("setReferenceDescriptorHandleForTopLink"); referenceDescriptorHandleMapping.setReferenceClass(MWDescriptorHandle.class); referenceDescriptorHandleMapping.setXPath("reference-descriptor-handle"); descriptor.addMapping(referenceDescriptorHandleMapping); XMLDirectMapping allowsNullMapping = (XMLDirectMapping) descriptor.addDirectMapping("allowsNull", "allows-null/text()"); allowsNullMapping.setNullValue(Boolean.FALSE); XMLCompositeCollectionMapping pathsToFieldsMapping = new XMLCompositeCollectionMapping(); pathsToFieldsMapping.setAttributeName("pathsToFields"); pathsToFieldsMapping.setGetMethodName("getPathsToFieldsForTopLink"); pathsToFieldsMapping.setSetMethodName("setPathsToFieldsForTopLink"); pathsToFieldsMapping.setReferenceClass(MWAggregatePathToColumn.class); pathsToFieldsMapping.setXPath("paths-to-fields/aggregate-path-to-field"); descriptor.addMapping(pathsToFieldsMapping); return descriptor; } /** * check for null */ private MWDescriptorHandle getReferenceDescriptorHandleForTopLink() { return (this.referenceDescriptorHandle.getDescriptor() == null) ? null : this.referenceDescriptorHandle; } private void setReferenceDescriptorHandleForTopLink(MWDescriptorHandle referenceDescriptorHandle) { NodeReferenceScrubber scrubber = this.buildReferenceDescriptorScrubber(); this.referenceDescriptorHandle = ((referenceDescriptorHandle == null) ? new MWDescriptorHandle(this, scrubber) : referenceDescriptorHandle.setScrubber(scrubber)); } //TODO should pathsToFields be sorted? private Collection getPathsToFieldsForTopLink() { return this.pathsToFields; } private void setPathsToFieldsForTopLink(Collection pathsToFields) { this.pathsToFields = pathsToFields; } }