All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.toplink.essentials.mappings.CollectionMapping Maven / Gradle / Ivy

The newest version!
/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the "License").  You may not use this file except 
 * in compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * glassfish/bootstrap/legal/CDDLv1.0.txt or 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html. 
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * HEADER in each file and include the License file at 
 * glassfish/bootstrap/legal/CDDLv1.0.txt.  If applicable, 
 * add the following below this CDDL HEADER, with the 
 * fields enclosed by brackets "[]" replaced with your 
 * own identifying information: Portions Copyright [yyyy] 
 * [name of copyright owner]
 */
// Copyright (c) 1998, 2006, Oracle. All rights reserved.  
package oracle.toplink.essentials.mappings;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.indirection.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.internal.indirection.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.sessions.ObjectCopyingPolicy;
import oracle.toplink.essentials.expressions.Expression;
import oracle.toplink.essentials.expressions.ExpressionBuilder;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * 

Purpose: Abstract class for relationship mappings which store collection of objects * * @author Sati * @since TOPLink/Java 1.0 */ public abstract class CollectionMapping extends ForeignReferenceMapping implements ContainerMapping { /** Used for delete all in m-m, dc and delete all optimization in 1-m. */ protected transient ModifyQuery deleteAllQuery; protected transient boolean hasCustomDeleteAllQuery; protected ContainerPolicy containerPolicy; protected transient boolean hasOrderBy; /** * PUBLIC: * Default constructor. */ public CollectionMapping() { this.selectionQuery = new ReadAllQuery(); this.hasCustomDeleteAllQuery = false; this.containerPolicy = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class); this.hasOrderBy = false; } /** * PUBLIC: * Provide order support for queryKeyName in ascending order */ public void addAscendingOrdering(String queryKeyName) { if (queryKeyName == null) { return; } ((ReadAllQuery)getSelectionQuery()).addAscendingOrdering(queryKeyName); } /** * PUBLIC: * Provide order support for queryKeyName in descending order. */ public void addDescendingOrdering(String queryKeyName) { if (queryKeyName == null) { return; } ((ReadAllQuery)getSelectionQuery()).addDescendingOrdering(queryKeyName); } /** * PUBLIC: * Provide order support for queryKeyName in descending or ascending order. * Called from the EJBAnnotationsProcessor when an @OrderBy is found. */ public void addOrderBy(String queryKeyName, boolean isDescending) { this.hasOrderBy = true; if (isDescending) { addDescendingOrdering(queryKeyName); } else { addAscendingOrdering(queryKeyName); } } /** * PUBLIC: * Provide order support for queryKeyName in ascending order. * Called from the EJBAnnotationsProcessor when an @OrderBy on an * aggregate is found. */ public void addAggregateOrderBy(String aggregateName, String queryKeyName, boolean isDescending) { this.hasOrderBy = true; ReadAllQuery readAllQuery = (ReadAllQuery) getSelectionQuery(); ExpressionBuilder builder = readAllQuery.getExpressionBuilder(); Expression expression = builder.get(aggregateName).get(queryKeyName).toUpperCase(); if (isDescending) { readAllQuery.addOrdering(expression.descending()); } else { readAllQuery.addOrdering(expression.ascending()); } } /** * INTERNAL: * Used during building the backup shallow copy to copy * the vector without re-registering the target objects. */ public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) { // Check for null if (attributeValue == null) { return getContainerPolicy().containerInstance(1); } else { return getContainerPolicy().cloneFor(attributeValue); } } /** * INTERNAL: * Require for cloning, the part must be cloned. * Ignore the objects, use the attribute value. */ public Object buildCloneForPartObject(Object attributeValue, Object original, Object clone, UnitOfWorkImpl unitOfWork, boolean isExisting) { ContainerPolicy containerPolicy = getContainerPolicy(); if (attributeValue == null) { Object container = containerPolicy.containerInstance(1); return container; } Object clonedAttributeValue = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue)); // I need to synchronize here to prevent the collection from changing while I am cloning it. // This will occur when I am merging into the cache and I am instantiating a UOW valueHolder at the same time // I can not synchronize around the clone, as this will cause deadlocks, so I will need to copy the collection then create the clones // I will use a temporary collection to help speed up the process Object temporaryCollection = null; synchronized (attributeValue) { temporaryCollection = containerPolicy.cloneFor(attributeValue); } for (Object valuesIterator = containerPolicy.iteratorFor(temporaryCollection); containerPolicy.hasNext(valuesIterator);) { Object cloneValue = buildElementClone(containerPolicy.next(valuesIterator, unitOfWork), unitOfWork, isExisting); containerPolicy.addInto(cloneValue, clonedAttributeValue, unitOfWork); } return clonedAttributeValue; } /** * INTERNAL: * Copy of the attribute of the object. * This is NOT used for unit of work but for templatizing an object. */ public void buildCopy(Object copy, Object original, ObjectCopyingPolicy policy) { Object attributeValue = getRealCollectionAttributeValueFromObject(original, policy.getSession()); Object valuesIterator = getContainerPolicy().iteratorFor(attributeValue); attributeValue = getContainerPolicy().containerInstance(getContainerPolicy().sizeFor(attributeValue)); while (getContainerPolicy().hasNext(valuesIterator)) { Object originalValue = getContainerPolicy().next(valuesIterator, policy.getSession()); Object copyValue = originalValue; if (policy.shouldCascadeAllParts() || (policy.shouldCascadePrivateParts() && isPrivateOwned())) { copyValue = policy.getSession().copyObject(originalValue, policy); } else { // Check for backrefs to copies. copyValue = policy.getCopies().get(originalValue); if (copyValue == null) { copyValue = originalValue; } } getContainerPolicy().addInto(copyValue, attributeValue, policy.getSession()); } setRealAttributeValueInObject(copy, attributeValue); } /** * INTERNAL: * Clone the element, if necessary. */ protected Object buildElementClone(Object element, UnitOfWorkImpl unitOfWork, boolean isExisting) { // optimize registration to knowledge of existence if (isExisting) { return unitOfWork.registerExistingObject(element); } else {// not known whether existing or not return unitOfWork.registerObject(element); } } /** * INTERNAL: * Cascade perform delete through mappings that require the cascade */ public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){ Object cloneAttribute = null; cloneAttribute = getAttributeValueFromObject(object); if ((cloneAttribute == null) || (!this.isCascadeRemove())) { return; } ContainerPolicy cp = getContainerPolicy(); Object cloneObjectCollection = null; cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow); Object cloneIter = cp.iteratorFor(cloneObjectCollection); while (cp.hasNext(cloneIter)) { Object nextObject = cp.next(cloneIter, uow); if (nextObject != null && (! visitedObjects.contains(nextObject)) ){ visitedObjects.put(nextObject, nextObject); uow.performRemove(nextObject, visitedObjects); } } } /** * INTERNAL: * Cascade registerNew for Create through mappings that require the cascade */ public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){ Object cloneAttribute = null; cloneAttribute = getAttributeValueFromObject(object); if ((cloneAttribute == null) || (!this.isCascadePersist()) || (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) { return; } ContainerPolicy cp = getContainerPolicy(); Object cloneObjectCollection = null; cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow); Object cloneIter = cp.iteratorFor(cloneObjectCollection); while (cp.hasNext(cloneIter)) { Object nextObject = cp.next(cloneIter, uow); if (nextObject != null && (! visitedObjects.contains(nextObject)) ){ visitedObjects.put(nextObject, nextObject); uow.registerNewObjectForPersist(nextObject, visitedObjects); } } } /** * INTERNAL: * Common validation for a collection mapping using a Map class. */ private void checkMapClass(Class concreteClass) { // the reference class has to be specified before coming here if (getReferenceClass() == null) { throw DescriptorException.referenceClassNotSpecified(this); } if (! Helper.classImplementsInterface(concreteClass, ClassConstants.Map_Class)) { throw ValidationException.illegalContainerClass(concreteClass); } } /** * INTERNAL: * Used by AttributeLevelChangeTracking to update a changeRecord with calculated changes * as apposed to detected changes. If an attribute can not be change tracked it's * changes can be detected through this process. */ public void calculateDeferredChanges(ChangeRecord changeRecord, AbstractSession session){ CollectionChangeRecord collectionRecord = (CollectionChangeRecord) changeRecord; //clear incase events were fired since the set of the collection // collectionRecord.getAddObjectList().clear(); // collectionRecord.getRemoveObjectList().clear(); compareCollectionsForChange(collectionRecord.getOriginalCollection(), collectionRecord.getLatestCollection(), collectionRecord, session); } /** * INTERNAL: * Cascade the merge to the component object, if appropriate. */ public void cascadeMerge(Object sourceElement, MergeManager mergeManager) { if (shouldMergeCascadeParts(mergeManager)) { mergeManager.mergeChanges(mergeManager.getObjectToMerge(sourceElement), null); } } /** * INTERNAL: * This method is used to calculate the differences between two collections. * It is passed to the container policy to calculate the changes. */ public void compareCollectionsForChange(Object oldCollection, Object newCollection, ChangeRecord changeRecord, AbstractSession session) { getContainerPolicy().compareCollectionsForChange(oldCollection, newCollection, (CollectionChangeRecord) changeRecord, session, getReferenceDescriptor()); } /** * INTERNAL: * This method is used to create a change record from comparing two collections * @return prototype.changeset.ChangeRecord */ public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) { Object cloneAttribute = null; Object backUpAttribute = null; Object backUpObjectCollection = null; cloneAttribute = getAttributeValueFromObject(clone); if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) { return null; } if (!owner.isNew()) {// if the changeSet is for a new object then we must record all off the attributes backUpAttribute = getAttributeValueFromObject(backUp); if ((cloneAttribute == null) && (backUpAttribute == null)) { return null; } backUpObjectCollection = getRealCollectionAttributeValueFromObject(backUp, session); } Object cloneObjectCollection = null; if (cloneAttribute != null) { cloneObjectCollection = getRealCollectionAttributeValueFromObject(clone, session); } else { cloneObjectCollection = getContainerPolicy().containerInstance(1); } CollectionChangeRecord changeRecord = new CollectionChangeRecord(owner); changeRecord.setAttribute(getAttributeName()); changeRecord.setMapping(this); compareCollectionsForChange(backUpObjectCollection, cloneObjectCollection, changeRecord, session); if (changeRecord.hasChanges()) { return changeRecord; } return null; } /** * INTERNAL: * Compare the attributes belonging to this mapping for the objects. */ public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { Object firstObjectCollection = getRealCollectionAttributeValueFromObject(firstObject, session); Object secondObjectCollection = getRealCollectionAttributeValueFromObject(secondObject, session); return super.compareObjects(firstObjectCollection, secondObjectCollection, session); } /** * INTERNAL: * The memory objects are compared and only the changes are written to the database */ protected void compareObjectsAndWrite(Object previousObjects, Object currentObjects, WriteObjectQuery query) throws DatabaseException, OptimisticLockException { ContainerPolicy cp = getContainerPolicy(); // If it is for an aggregate collection let it continue so that all of // the correct values are deleted and then re-added This could be // changed to make AggregateCollection changes smarter. if ((query.getObjectChangeSet() != null) && !this.isAggregateCollectionMapping()) { ObjectChangeSet changeSet = query.getObjectChangeSet(); CollectionChangeRecord record = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); if (record != null) { ObjectChangeSet removedChangeSet = null; ObjectChangeSet addedChangeSet = null; UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)changeSet.getUOWChangeSet(); Enumeration removedObjects = record.getRemoveObjectList().elements(); while (removedObjects.hasMoreElements()) { removedChangeSet = (ObjectChangeSet)removedObjects.nextElement(); objectRemovedDuringUpdate(query, removedChangeSet.getUnitOfWorkClone()); } Enumeration addedObjects = record.getAddObjectList().elements(); while (addedObjects.hasMoreElements()) { addedChangeSet = (ObjectChangeSet)addedObjects.nextElement(); objectAddedDuringUpdate(query, addedChangeSet.getUnitOfWorkClone(), addedChangeSet); } } return; } Hashtable previousObjectsByKey = new Hashtable(cp.sizeFor(previousObjects) + 2);// Read from db or from backup in uow. Hashtable currentObjectsByKey = new Hashtable(cp.sizeFor(currentObjects) + 2);// Current value of object's attribute (clone in uow). IdentityHashtable cacheKeysOfCurrentObjects = new IdentityHashtable(cp.sizeFor(currentObjects) + 1); // First index the current objects by their primary key. for (Object currentObjectsIter = cp.iteratorFor(currentObjects); cp.hasNext(currentObjectsIter);) { Object currentObject = cp.next(currentObjectsIter, query.getSession()); try { Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(currentObject, query.getSession()); CacheKey key = new CacheKey(primaryKey); currentObjectsByKey.put(key, currentObject); cacheKeysOfCurrentObjects.put(currentObject, key); } catch (NullPointerException e) { // For CR#2646 quietly discard nulls added to a collection mapping. // This try-catch is essentially a null check on currentObject, for // ideally the customer should check for these themselves. if (currentObject != null) { throw e; } } } // Next index the previous objects (read from db or from backup in uow) // and process the difference to current (optimized in same loop). for (Object previousObjectsIter = cp.iteratorFor(previousObjects); cp.hasNext(previousObjectsIter);) { Object previousObject = cp.next(previousObjectsIter, query.getSession()); Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(previousObject, query.getSession()); CacheKey key = new CacheKey(primaryKey); previousObjectsByKey.put(key, previousObject); // Delete must occur first, incase object with same pk is removed and added, // (technically should not happen, but same applies to unquie constainsts) if (!currentObjectsByKey.containsKey(key)) { objectRemovedDuringUpdate(query, previousObject); } } for (Object currentObjectsIter = cp.iteratorFor(currentObjects); cp.hasNext(currentObjectsIter);) { Object currentObject = cp.next(currentObjectsIter, query.getSession()); try { CacheKey cacheKey = (CacheKey)cacheKeysOfCurrentObjects.get(currentObject); if (!(previousObjectsByKey.containsKey(cacheKey))) { objectAddedDuringUpdate(query, currentObject, null); } else { objectUnchangedDuringUpdate(query, currentObject, previousObjectsByKey, cacheKey); } } catch (NullPointerException e) { // For CR#2646 skip currentObject if it is null. if (currentObject != null) { throw e; } } } } /** * Compare two objects if their parts are not private owned */ protected boolean compareObjectsWithoutPrivateOwned(Object firstCollection, Object secondCollection, AbstractSession session) { ContainerPolicy cp = getContainerPolicy(); if (cp.sizeFor(firstCollection) != cp.sizeFor(secondCollection)) { return false; } Object firstIter = cp.iteratorFor(firstCollection); Object secondIter = cp.iteratorFor(secondCollection); Vector keyValue = new Vector(); while (cp.hasNext(secondIter)) { Object secondObject = cp.next(secondIter, session); Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject, session); keyValue.addElement(new CacheKey(primaryKey)); } while (cp.hasNext(firstIter)) { Object firstObject = cp.next(firstIter, session); Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject, session); if (!keyValue.contains(new CacheKey(primaryKey))) { return false; } } return true; } /** * Compare two objects if their parts are private owned */ protected boolean compareObjectsWithPrivateOwned(Object firstCollection, Object secondCollection, AbstractSession session) { ContainerPolicy cp = getContainerPolicy(); if (cp.sizeFor(firstCollection) != cp.sizeFor(secondCollection)) { return false; } Object firstIter = cp.iteratorFor(firstCollection); Object secondIter = cp.iteratorFor(secondCollection); Hashtable keyValueToObject = new Hashtable(cp.sizeFor(firstCollection) + 2); CacheKey cacheKey; while (cp.hasNext(secondIter)) { Object secondObject = cp.next(secondIter, session); Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject, session); keyValueToObject.put(new CacheKey(primaryKey), secondObject); } while (cp.hasNext(firstIter)) { Object firstObject = cp.next(firstIter, session); Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject, session); cacheKey = new CacheKey(primaryKey); if (keyValueToObject.containsKey(cacheKey)) { Object object = keyValueToObject.get(cacheKey); if (!session.compareObjects(firstObject, object)) { return false; } } else { return false; } } return true; } /** * INTERNAL: * Convert all the class-name-based settings in this mapping to actual class-based * settings * This method is implemented by subclasses as necessary. * @param classLoader */ public void convertClassNamesToClasses(ClassLoader classLoader){ super.convertClassNamesToClasses(classLoader); containerPolicy.convertClassNamesToClasses(classLoader); }; /** * INTERNAL: * Returns the receiver's containerPolicy. */ public ContainerPolicy getContainerPolicy() { return containerPolicy; } protected ModifyQuery getDeleteAllQuery() { if (deleteAllQuery == null) { deleteAllQuery = new DataModifyQuery(); } return deleteAllQuery; } /** * INTERNAL: * Return the value of an attribute, unwrapping value holders if necessary. * Also check to ensure the collection is a vector. */ public Object getRealAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException { Object value = super.getRealAttributeValueFromObject(object, session); if (value != null) { if (!getContainerPolicy().isValidContainer(value)) { throw DescriptorException.attributeTypeNotValid(this); } } return value; } /** * Convenience method. * Return the value of an attribute, unwrapping value holders if necessary. * If the value is null, build a new container. */ public Object getRealCollectionAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException { Object value = this.getRealAttributeValueFromObject(object, session); if (value == null) { value = this.getContainerPolicy().containerInstance(1); } return value; } protected boolean hasCustomDeleteAllQuery() { return hasCustomDeleteAllQuery; } /** * INTERNAL: * Return true if ascending or descending ordering has been set on this * mapping via the @OrderBy annotation. */ public boolean hasOrderBy() { return hasOrderBy; } /** * INTERNAL: * Initialize the state of mapping. */ public void initialize(AbstractSession session) throws DescriptorException { super.initialize(session); setFields(collectFields()); getContainerPolicy().prepare(getSelectionQuery(), session); // Check that the container policy is correct for the collection type. if ((!usesIndirection()) && (!getAttributeAccessor().getAttributeClass().isAssignableFrom(getContainerPolicy().getContainerClass()))) { throw DescriptorException.incorrectCollectionPolicy(this, getAttributeAccessor().getAttributeClass(), getContainerPolicy().getContainerClass()); } } /** * INTERNAL: */ public boolean isCollectionMapping() { return true; } /** * INTERNAL: * Iterate on the specified element. */ public void iterateOnElement(DescriptorIterator iterator, Object element) { iterator.iterateReferenceObjectForMapping(element, this); } /** * INTERNAL: * Iterate on the attribute value. * The value holder has already been processed. */ public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) { if (realAttributeValue == null) { return; } ContainerPolicy cp = getContainerPolicy(); for (Object iter = cp.iteratorFor(realAttributeValue); cp.hasNext(iter);) { iterateOnElement(iterator, cp.next(iter, iterator.getSession())); } } /** * INTERNAL: * Merge changes from the source to the target object. Because this is a * collection mapping, values are added to or removed from the collection * based on the change set. */ public void mergeChangesIntoObject(Object target, ChangeRecord chgRecord, Object source, MergeManager mergeManager) { Object valueOfTarget = null; Object valueOfSource = null; AbstractSession parentSession = null; ContainerPolicy containerPolicy = getContainerPolicy(); CollectionChangeRecord changeRecord = (CollectionChangeRecord) chgRecord; UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)changeRecord.getOwner().getUOWChangeSet(); // Collect the changes into a vector. Check to see if the target has an instantiated // collection, if it does then iterate over the changes and merge the collections. if (isAttributeValueInstantiated(target)) { // If it is new will need a new collection. if (changeRecord.getOwner().isNew()) { valueOfTarget = containerPolicy.containerInstance(changeRecord.getAddObjectList().size()); } else { valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeManager.getSession()); } // Remove must happen before add to allow for changes in hash keys. // This is required to return the appropriate object from the parent when unwrapping. if (mergeManager.getSession().isUnitOfWork() && !mergeManager.shouldMergeWorkingCopyIntoBackup()) { parentSession = ((UnitOfWorkImpl)mergeManager.getSession()).getParent(); } else { parentSession = mergeManager.getSession(); } containerPolicy.mergeChanges(changeRecord, valueOfTarget, shouldMergeCascadeParts(mergeManager), mergeManager, parentSession); } else { // The valueholder has not been instantiated if (mergeManager.shouldMergeChangesIntoDistributedCache()) { return; // do nothing } // If I'm not merging on another server then create instance of the collection. valueOfSource = getRealCollectionAttributeValueFromObject(source, mergeManager.getSession()); Object iterator = containerPolicy.iteratorFor(valueOfSource); valueOfTarget = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource)); while (containerPolicy.hasNext(iterator)) { // CR2195 - Problem with merging Collection mapping in unit of work and inheritance. Object objectToMerge = containerPolicy.next(iterator, mergeManager.getSession()); ObjectChangeSet changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(objectToMerge); if (shouldMergeCascadeParts(mergeManager) && (valueOfSource != null)) { mergeManager.mergeChanges(objectToMerge, changeSet); } // Let the mergemanager get it because I don't have the change for the object. // CR2188 - Problem with merging Collection mapping in unit of work and transparent indirection. containerPolicy.addInto(mergeManager.getTargetVersionOfSourceObject(objectToMerge), valueOfTarget, mergeManager.getSession()); } } if (valueOfTarget == null) { valueOfTarget = containerPolicy.containerInstance(); } setRealAttributeValueInObject(target, valueOfTarget); } /** * INTERNAL: * Merge changes from the source to the target object. This merge is only called when a changeSet for the target * does not exist or the target is uninitialized */ public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager) { if (isTargetUnInitialized) { // This will happen if the target object was removed from the cache before the commit was attempted if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) { setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), mergeManager.getSession())); return; } } if (!shouldMergeCascadeReference(mergeManager)) { // This is only going to happen on mergeClone, and we should not attempt to merge the reference return; } if (mergeManager.shouldMergeOriginalIntoWorkingCopy()) { if (!isAttributeValueInstantiated(target)) { // This will occur when the clone's value has not been instantiated yet and we do not need // the refresh that attribute return; } } else if (!isAttributeValueInstantiated(source)) { // I am merging from a clone into an original. No need to do merge if the attribute was never // modified return; } Object valueOfSource = getRealCollectionAttributeValueFromObject(source, mergeManager.getSession()); // There is a very special case when merging into the shared cache that the original // has been refreshed and now has non-instantiated indirection objects. // Force instantiation is not necessary and can cause problem with JTS drivers. AbstractSession mergeSession = mergeManager.getSession(); Object valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeSession); ContainerPolicy containerPolicy = getContainerPolicy(); boolean fireChangeEvents = false; if (! mergeManager.shouldMergeOriginalIntoWorkingCopy()){ // if we are copying from original to clone then the source will be // instantiated anyway and we must continue to use the UnitOfWork // valueholder in the case of transparent indirection Object newContainer = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource)); valueOfTarget = newContainer; }else{ //bug 3953038 - set a new collection in the object until merge completes, this // prevents rel-maint. from adding duplicates. setRealAttributeValueInObject(target, containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource))); containerPolicy.clear(valueOfTarget); } synchronized(valueOfSource){ Object sourceIterator = containerPolicy.iteratorFor(valueOfSource); while (containerPolicy.hasNext(sourceIterator)) { Object object = containerPolicy.next(sourceIterator, mergeManager.getSession()); if (object == null){ continue; // skip the null } if (shouldMergeCascadeParts(mergeManager)) { if ((mergeManager.getSession().isUnitOfWork()) && (((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet() != null)) { // If it is a unit of work, we have to check if I have a change Set fot this object mergeManager.mergeChanges(mergeManager.getObjectToMerge(object), (ObjectChangeSet)((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object)); } else { mergeManager.mergeChanges(mergeManager.getObjectToMerge(object), null); } } object = getReferenceDescriptor().getObjectBuilder().wrapObject(mergeManager.getTargetVersionOfSourceObject(object), mergeManager.getSession()); synchronized (valueOfTarget){ containerPolicy.addInto(object, valueOfTarget, mergeManager.getSession()); } } } // Must re-set variable to allow for set method to re-morph changes if the collection is not being stored directly. setRealAttributeValueInObject(target, valueOfTarget); } /** * INTERNAL: * An object was added to the collection during an update, insert it if private. */ protected void objectAddedDuringUpdate(ObjectLevelModifyQuery query, Object objectAdded, ObjectChangeSet changeSet) throws DatabaseException, OptimisticLockException { if (!shouldObjectModifyCascadeToParts(query)) {// Called always for M-M return; } // Only cascade dependents writes in uow. if (query.shouldCascadeOnlyDependentParts()) { return; } // Insert must not be done for uow or cascaded queries and we must cascade to cascade policy. // We should distiguish between insert and write (optimization/paraniod). if (isPrivateOwned()) { InsertObjectQuery insertQuery = new InsertObjectQuery(); insertQuery.setObject(objectAdded); insertQuery.setCascadePolicy(query.getCascadePolicy()); query.getSession().executeQuery(insertQuery); } else { // Always write for updates, either private or in uow if calling this method. UnitOfWorkChangeSet uowChangeSet = null; if ((changeSet == null) && query.getSession().isUnitOfWork() && (((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet() != null)) { uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet(); changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(query.getObject()); } WriteObjectQuery writeQuery = new WriteObjectQuery(); writeQuery.setObject(objectAdded); writeQuery.setObjectChangeSet(changeSet); writeQuery.setCascadePolicy(query.getCascadePolicy()); query.getSession().executeQuery(writeQuery); } } /** * INTERNAL: * An object was removed to the collection during an update, delete it if private. */ protected void objectRemovedDuringUpdate(ObjectLevelModifyQuery query, Object objectDeleted) throws DatabaseException, OptimisticLockException { if (isPrivateOwned()) {// Must check ownership for uow and cascading. if (query.shouldCascadeOnlyDependentParts()) { // If the session is a unit of work if (query.getSession().isUnitOfWork()) { // ...and the object has not been explictly deleted in the unit of work if (!(((UnitOfWorkImpl)query.getSession()).getDeletedObjects().containsKey(objectDeleted))) { query.getSession().getCommitManager().addObjectToDelete(objectDeleted); } } else { query.getSession().getCommitManager().addObjectToDelete(objectDeleted); } } else { query.getSession().deleteObject(objectDeleted); } } } /** * INTERNAL: * An object is still in the collection, update it as it may have changed. */ protected void objectUnchangedDuringUpdate(ObjectLevelModifyQuery query, Object object) throws DatabaseException, OptimisticLockException { if (!shouldObjectModifyCascadeToParts(query)) {// Called always for M-M return; } // Only cascade dependents writes in uow. if (query.shouldCascadeOnlyDependentParts()) { return; } // Always write for updates, either private or in uow if calling this method. WriteObjectQuery writeQuery = new WriteObjectQuery(); writeQuery.setObject(object); writeQuery.setCascadePolicy(query.getCascadePolicy()); query.getSession().executeQuery(writeQuery); } /** * INTERNAL: * copies the non primary key information into the row currently used only in ManyToMany */ protected void prepareTranslationRow(AbstractRecord translationRow, Object object, AbstractSession session) { //Do nothing for the generic Collection Mapping } /** * INTERNAL: * An object is still in the collection, update it as it may have changed. */ protected void objectUnchangedDuringUpdate(ObjectLevelModifyQuery query, Object object, Hashtable backupclones, CacheKey keys) throws DatabaseException, OptimisticLockException { objectUnchangedDuringUpdate(query, object); } /** * INTERNAL: * All the privately owned parts are read */ protected Object readPrivateOwnedForObject(ObjectLevelModifyQuery modifyQuery) throws DatabaseException { if (modifyQuery.getSession().isUnitOfWork()) { return getRealCollectionAttributeValueFromObject(modifyQuery.getBackupClone(), modifyQuery.getSession()); } else { // cr 3819 prepareTranslationRow(modifyQuery.getTranslationRow(), modifyQuery.getObject(), modifyQuery.getSession()); return modifyQuery.getSession().executeQuery(getSelectionQuery(), modifyQuery.getTranslationRow()); } } /** * ADVANCED: * Configure the mapping to use a container policy. * The policy manages the access to the collection. */ public void setContainerPolicy(ContainerPolicy containerPolicy) { this.containerPolicy = containerPolicy; ((ReadAllQuery)getSelectionQuery()).setContainerPolicy(containerPolicy); } /** * PUBLIC: * The default delete all query for mapping can be overridden by specifying the new query. * This query is responsible for doing the deletion required by the mapping, * such as deletion of all the rows from join table for M-M, or optimized delete all of target objects for 1-M. */ public void setCustomDeleteAllQuery(ModifyQuery query) { setDeleteAllQuery(query); setHasCustomDeleteAllQuery(true); } protected void setDeleteAllQuery(ModifyQuery query) { deleteAllQuery = query; } /** * PUBLIC: * Set the receiver's delete all SQL string. This allows the user to override the SQL * generated by TopLink, with there own SQL or procedure call. The arguments are * translated from the fields of the source row, through replacing the field names * marked by '#' with the values for those fields. * This SQL is responsible for doing the deletion required by the mapping, * such as deletion of all the rows from join table for M-M, or optimized delete all of target objects for 1-M. * Example, 'delete from PROJ_EMP where EMP_ID = #EMP_ID'. */ public void setDeleteAllSQLString(String sqlString) { DataModifyQuery query = new DataModifyQuery(); query.setSQLString(sqlString); setCustomDeleteAllQuery(query); } /** * PUBLIC: * Set the receiver's delete all call. This allows the user to override the SQL * generated by TopLink, with there own SQL or procedure call. The arguments are * translated from the fields of the source row. * This call is responsible for doing the deletion required by the mapping, * such as deletion of all the rows from join table for M-M, or optimized delete all of target objects for 1-M. * Example, 'new SQLCall("delete from PROJ_EMP where EMP_ID = #EMP_ID")'. */ public void setDeleteAllCall(Call call) { DataModifyQuery query = new DataModifyQuery(); query.setCall(call); setCustomDeleteAllQuery(query); } protected void setHasCustomDeleteAllQuery(boolean bool) { hasCustomDeleteAllQuery = bool; } /** * PUBLIC: * Set the name of the session to execute the mapping's queries under. * This can be used by the session broker to override the default session * to be used for the target class. */ public void setSessionName(String name) { getDeleteAllQuery().setSessionName(name); getSelectionQuery().setSessionName(name); } /** * ADVANCED: * This method is used to have an object add to a collection once the * changeSet is applied. The referenceKey parameter should only be used for * direct Maps. */ public void simpleAddToCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) { CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); if (collectionChangeRecord == null) { collectionChangeRecord = new CollectionChangeRecord(changeSet); collectionChangeRecord.setAttribute(getAttributeName()); collectionChangeRecord.setMapping(this); collectionChangeRecord.getAddObjectList().put(changeSetToAdd, changeSetToAdd); collectionChangeRecord.getOrderedAddObjects().add(changeSetToAdd); changeSet.addChange(collectionChangeRecord); } else { getContainerPolicy().recordAddToCollectionInChangeRecord((ObjectChangeSet)changeSetToAdd, collectionChangeRecord); } if (referenceKey != null){ ((ObjectChangeSet)changeSetToAdd).setNewKey(referenceKey); } } /** * ADVANCED: * This method is used to have an object removed from a collection once the * changeSet is applied. The referenceKey parameter should only be used for * direct Maps. */ public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) { CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); if (collectionChangeRecord == null) { collectionChangeRecord = new CollectionChangeRecord(changeSet); collectionChangeRecord.setAttribute(getAttributeName()); collectionChangeRecord.setMapping(this); collectionChangeRecord.getRemoveObjectList().put(changeSetToRemove, changeSetToRemove); changeSet.addChange(collectionChangeRecord); } else { getContainerPolicy().recordRemoveFromCollectionInChangeRecord((ObjectChangeSet)changeSetToRemove, collectionChangeRecord); } if (referenceKey != null){ ((ObjectChangeSet)changeSetToRemove).setOldKey(referenceKey); } } /** * INTERNAL: * Either create a new change record or update with the new value. This is used * by attribute change tracking. * Specifically in a collection mapping this will be called when the customer * Set a new collection. In this case we will need to mark the change record * with the new and the old versions of the collection. * And mark the ObjectChangeSet with the attribute name then when the changes are calculated * force a compare on the collections to determine changes. */ public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) { CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); if (collectionChangeRecord == null) { collectionChangeRecord = new CollectionChangeRecord(objectChangeSet); collectionChangeRecord.setAttribute(getAttributeName()); collectionChangeRecord.setMapping(this); objectChangeSet.addChange(collectionChangeRecord); } if (collectionChangeRecord.getOriginalCollection() == null){ collectionChangeRecord.setOriginalCollection(oldValue); } collectionChangeRecord.setLatestCollection(newValue); objectChangeSet.deferredDetectionRequiredOn(getAttributeName()); } /** * PUBLIC: * Configure the mapping to use an instance of the specified container class * to hold the target objects. *

The container class must implement (directly or indirectly) the * java.util.Collection interface. */ public void useCollectionClass(Class concreteClass) { ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass, hasOrderBy()); setContainerPolicy(policy); } /** * PUBLIC: * Configure the mapping to use an instance of the specified container class * to hold the target objects. *

The container class must implement (directly or indirectly) the * java.util.SortedSet interface. */ public void useSortedSetClass(Class concreteClass, Comparator comparator) { try { SortedCollectionContainerPolicy policy = (SortedCollectionContainerPolicy)ContainerPolicy.buildPolicyFor(concreteClass); policy.setComparator(comparator); setContainerPolicy(policy); } catch (ClassCastException e) { useCollectionClass(concreteClass); } } /** * PUBLIC: * Configure the mapping to use an instance of the specified container * clas to hold the target objects. The key used to index a value in the * Map is the value returned by either a call to a * specified zero-argument method or the value of a field. *

To facilitate resolving the keyName to a method or field, * the mapping's referenceClass must set before calling this method. *

Note: If the keyName is for a method, that method must be implemented * by the class (or a superclass) of any value to be inserted into the * Map. *

The container class must implement (directly or indirectly) the * java.util.Map interface. */ public void useMapClass(Class concreteClass, String keyName) { // the reference class has to be specified before coming here if (getReferenceClassName() == null) { throw DescriptorException.referenceClassNotSpecified(this); } ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass); policy.setKeyName(keyName, getReferenceClassName()); setContainerPolicy(policy); } /** * PUBLIC: * Configure the mapping to use an instance of the specified container * class to hold the target objects. The key used to index a value in the * Map is an instance of the composite primary key class. *

To facilitate resolving the primary key class, the mapping's * referenceClass must set before calling this method. *

The container class must implement (directly or indirectly) the * java.util.Map interface. */ public void useMapClass(Class concreteClass) { useMapClass(concreteClass, null); } /** * PUBLIC: * If transparent indirection is used, a special collection will be placed in the source * object's attribute. * Fetching of the contents of the collection from the database will be delayed * until absolutely necessary. (Any message sent to the collection will cause * the contents to be faulted in from the database.) * This can result in rather significant performance gains, without having to change * the source object's attribute from Collection (or List or Vector) to * ValueHolderInterface. */ public void useTransparentCollection() { setIndirectionPolicy(new TransparentIndirectionPolicy()); useCollectionClass(ClassConstants.IndirectList_Class); } /** * PUBLIC: * If transparent indirection is used, a special collection will be placed in the source * object's attribute. * Fetching of the contents of the collection from the database will be delayed * until absolutely necessary. (Any message sent to the collection will cause * the contents to be faulted in from the database.) * This can result in rather significant performance gains, without having to change * the source object's attribute from Set to * ValueHolderInterface. */ public void useTransparentSet() { setIndirectionPolicy(new TransparentIndirectionPolicy()); useCollectionClass(IndirectSet.class); } /** * PUBLIC: * If transparent indirection is used, a special collection will be placed in the source * object's attribute. * Fetching of the contents of the collection from the database will be delayed * until absolutely necessary. (Any message sent to the collection will cause * the contents to be faulted in from the database.) * This can result in rather significant performance gains, without having to change * the source object's attribute from List to * ValueHolderInterface. */ public void useTransparentList() { setIndirectionPolicy(new TransparentIndirectionPolicy()); useCollectionClass(ClassConstants.IndirectList_Class); } /** * PUBLIC: * If transparent indirection is used, a special map will be placed in the source * object's attribute. * Fetching of the contents of the map from the database will be delayed * until absolutely necessary. (Any message sent to the map will cause * the contents to be faulted in from the database.) * This can result in rather significant performance gains, without having to change * the source object's attribute from Map (or Dictionary or Hashtable) to * ValueHolderInterface.

* The key used in the Map is the value returned by a call to the zero parameter * method named methodName. The method should be a zero argument method implemented (or * inherited) by the value to be inserted into the Map. */ public void useTransparentMap(String methodName) { setIndirectionPolicy(new TransparentIndirectionPolicy()); useMapClass(ClassConstants.IndirectMap_Class, methodName); } /** * INTERNAL: * To validate mappings declaration */ public void validateBeforeInitialization(AbstractSession session) throws DescriptorException { super.validateBeforeInitialization(session); getIndirectionPolicy().validateContainerPolicy(session.getIntegrityChecker()); if (getAttributeAccessor() instanceof InstanceVariableAttributeAccessor) { Class attributeType = ((InstanceVariableAttributeAccessor)getAttributeAccessor()).getAttributeType(); getIndirectionPolicy().validateDeclaredAttributeTypeForCollection(attributeType, session.getIntegrityChecker()); } else if (getAttributeAccessor() instanceof MethodAttributeAccessor) { Class returnType = ((MethodAttributeAccessor)getAttributeAccessor()).getGetMethodReturnType(); getIndirectionPolicy().validateGetMethodReturnTypeForCollection(returnType, session.getIntegrityChecker()); Class parameterType = ((MethodAttributeAccessor)getAttributeAccessor()).getSetMethodParameterType(); getIndirectionPolicy().validateSetMethodParameterTypeForCollection(parameterType, session.getIntegrityChecker()); } } /** * INTERNAL: * Checks if object is deleted from the database or not. */ public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException { // Row is built for translation if (isReadOnly()) { return true; } if (isPrivateOwned()) { Object objects = getRealCollectionAttributeValueFromObject(object, session); ContainerPolicy containerPolicy = getContainerPolicy(); for (Object iter = containerPolicy.iteratorFor(objects); containerPolicy.hasNext(iter);) { if (!session.verifyDelete(containerPolicy.next(iter, session))) { return false; } } } AbstractRecord row = getDescriptor().getObjectBuilder().buildRowForTranslation(object, session); //cr 3819 added the line below to fix the translationtable to ensure that it // contains the required values prepareTranslationRow(row, object, session); Object value = session.executeQuery(getSelectionQuery(), row); return getContainerPolicy().isEmpty(value); } /** * INTERNAL: * Add a new value and its change set to the collection change record. This is used by * attribute change tracking. */ public void addToCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) { if (newValue != null) { ClassDescriptor descriptor; //PERF: Use referenceDescriptor if it does not have inheritance if (!getReferenceDescriptor().hasInheritance()) { descriptor = getReferenceDescriptor(); } else { descriptor = uow.getDescriptor(newValue); } newValue = descriptor.getObjectBuilder().unwrapObject(newValue, uow); ObjectChangeSet newSet = descriptor.getObjectBuilder().createObjectChangeSet(newValue, (UnitOfWorkChangeSet)objectChangeSet.getUOWChangeSet(), uow); simpleAddToCollectionChangeRecord(newKey, newSet, objectChangeSet, uow); } } /** * INTERNAL: * Return if this mapping supports change tracking. */ public boolean isChangeTrackingSupported() { return true; } /** * INTERNAL: * Remove a value and its change set from the collection change record. This is used by * attribute change tracking. */ public void removeFromCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) { if (newValue != null) { ClassDescriptor descriptor; //PERF: Use referenceDescriptor if it does not have inheritance if (!getReferenceDescriptor().hasInheritance()) { descriptor = getReferenceDescriptor(); } else { descriptor = uow.getDescriptor(newValue); } newValue = descriptor.getObjectBuilder().unwrapObject(newValue, uow); ObjectChangeSet newSet = descriptor.getObjectBuilder().createObjectChangeSet(newValue, (UnitOfWorkChangeSet)objectChangeSet.getUOWChangeSet(), uow); simpleRemoveFromCollectionChangeRecord(newKey, newSet, objectChangeSet, uow); } } /** * INTERNAL: * Directly build a change record without comparison */ public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) { Object cloneAttribute = null; cloneAttribute = getAttributeValueFromObject(clone); if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) { return null; } // 2612538 - the default size of IdentityHashtable (32) is appropriate IdentityHashMap cloneKeyValues = new IdentityHashMap(); ContainerPolicy cp = getContainerPolicy(); Object cloneObjectCollection = null; if (cloneAttribute != null) { cloneObjectCollection = getRealCollectionAttributeValueFromObject(clone, session); } else { cloneObjectCollection = cp.containerInstance(1); } Object cloneIter = cp.iteratorFor(cloneObjectCollection); while (cp.hasNext(cloneIter)) { Object firstObject = cp.next(cloneIter, session); if (firstObject != null) { cloneKeyValues.put(firstObject, firstObject); } } CollectionChangeRecord changeRecord = new CollectionChangeRecord(owner); changeRecord.setAttribute(getAttributeName()); changeRecord.setMapping(this); changeRecord.addAdditionChange(cloneKeyValues, (UnitOfWorkChangeSet)owner.getUOWChangeSet(), session); if (changeRecord.hasChanges()) { return changeRecord; } return null; } /** * INTERNAL: * Indicates whether valueFromRow should call valueFromRowInternalWithJoin (true) * or valueFromRowInternal (false) */ protected boolean shouldUseValueFromRowWithJoin(JoinedAttributeManager joinManager) { return joinManager.getDataResults_()!=null && super.shouldUseValueFromRowWithJoin(joinManager); } /** * INTERNAL: * Return the value of the field from the row or a value holder on the query to obtain the object. * To get here the mapping's isJoiningSupported() should return true, * currently that's the case for only 1-m and m-m. */ protected Object valueFromRowInternalWithJoin(AbstractRecord row, JoinedAttributeManager joinManager, AbstractSession executionSession) throws DatabaseException { // If the query was using joining, all of the result rows will have been set. List rows = joinManager.getDataResults_(); Object value = getContainerPolicy().containerInstance(); // A nested query must be built to pass to the descriptor that looks like the real query execution would, // these should be cached on the query during prepare. ObjectLevelReadQuery nestedQuery = null; if (joinManager.getJoinedMappingQueries_() != null) { nestedQuery = (ObjectLevelReadQuery) joinManager.getJoinedMappingQueries_().get(this); } else { nestedQuery = prepareNestedJoins(joinManager, executionSession); } nestedQuery.setSession(executionSession); //CR #4365 - used to prevent infinite recursion on refresh object cascade all nestedQuery.setQueryId(joinManager.getBaseQuery().getQueryId()); // Extract the primary key of the source object, to filter only the joined rows for that object. Vector sourceKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(row, executionSession); CacheKey sourceCacheKey = new CacheKey(sourceKey); // A set of target cache keys must be maintained to avoid duplicates from multiple 1-m joins. Set targetCacheKeys = new HashSet(); // For each rows, extract the target row and build the target object and add to the collection. for (int index = 0; index < rows.size(); index++) { AbstractRecord sourceRow = (AbstractRecord)rows.get(index); AbstractRecord targetRow = sourceRow; // Row will be set to null if part of another object's join already processed. if (targetRow != null) { // CR #... the field for many objects may be in the row, // so build the subpartion of the row through the computed values in the query, // this also helps the field indexing match. targetRow = trimRowForJoin(targetRow, joinManager, executionSession); AbstractRecord pkRow = trimRowForJoin(sourceRow, new Integer(joinManager.getParentResultIndex()), executionSession); nestedQuery.setTranslationRow(targetRow); // Extract the primary key of the row to filter only the joined rows for the source object. Vector rowSourceKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(pkRow, executionSession); if(rowSourceKey != null) { CacheKey rowSourceCacheKey = new CacheKey(rowSourceKey); // Only build/add the object if the join row is for the object. if (sourceCacheKey.equals(rowSourceCacheKey)) { // Partial object queries must select the primary key of the source and related objects. // If the target joined rows in null (outerjoin) means an empty collection. Vector targetKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(targetRow, executionSession); if (targetKey == null) { // A null primary key means an empty collection returned as nulls from an outerjoin. return getIndirectionPolicy().valueFromRow(value); } CacheKey targetCacheKey = new CacheKey(targetKey); // Only build/add the taregt object once, skip duplicates from multiple 1-m joins. if (!targetCacheKeys.contains(targetCacheKey)) { targetCacheKeys.add(targetCacheKey); Object targetObject = getReferenceDescriptor().getObjectBuilder().buildObject(nestedQuery, targetRow, nestedQuery.getJoinedAttributeManager()); nestedQuery.setTranslationRow(null); getContainerPolicy().addInto(targetObject, value, executionSession); } } } else { // Clear an empty row rows.set(index, null); } } } return getIndirectionPolicy().valueFromRow(value); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy