| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2018 IBM Corporation. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 02/26/2009-2.0 Guy Pelletier |
| // - 264001: dot notation for mapped-by and order-by |
| // 08/23/2010-2.2 Michael O'Brien |
| // - 323043: application.xml module ordering may cause weaving not to occur causing an NPE. |
| // warn if expected "_persistence_//_vh" method not found |
| // instead of throwing NPE during deploy validation. |
| // 07/19/2011-2.2.1 Guy Pelletier |
| // - 338812: ManyToMany mapping in aggregate object violate integrity constraint on deletion |
| // 04/09/2012-2.4 Guy Pelletier |
| // - 374377: OrderBy with ElementCollection doesn't work |
| // 14/05/2012-2.4 Guy Pelletier |
| // - 376603: Provide for table per tenant support for multitenant applications |
| // 08/29/2016 Jody Grassel |
| // - 500441: Eclipselink core has System.getProperty() calls that are not potentially executed under doPriv() |
| package org.eclipse.persistence.mappings; |
| |
| import java.beans.PropertyChangeListener; |
| import java.util.*; |
| |
| import org.eclipse.persistence.annotations.OrderCorrectionType; |
| import org.eclipse.persistence.config.SystemProperties; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.changetracking.*; |
| import org.eclipse.persistence.internal.descriptors.changetracking.*; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.expressions.*; |
| import org.eclipse.persistence.indirection.*; |
| import org.eclipse.persistence.internal.descriptors.*; |
| import org.eclipse.persistence.internal.expressions.*; |
| import org.eclipse.persistence.internal.helper.*; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.indirection.*; |
| import org.eclipse.persistence.internal.queries.*; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.eclipse.persistence.internal.sessions.remote.*; |
| import org.eclipse.persistence.internal.sessions.*; |
| import org.eclipse.persistence.queries.*; |
| import org.eclipse.persistence.sessions.remote.*; |
| import org.eclipse.persistence.sessions.CopyGroup; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.sessions.Project; |
| |
| /** |
| * <p><b>Purpose</b>: 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 boolean hasOrderBy; |
| |
| /** Field holds the order of elements in the list in the db, requires collection of type List, may be not null only in case isListOrderFieldSupported==true */ |
| protected DatabaseField listOrderField; |
| /** Indicates whether the mapping supports listOrderField, if it doesn't attempt to set listOrderField throws exception. */ |
| protected boolean isListOrderFieldSupported; |
| /** Query used when order of list members is changed. Used only if listOrderField!=null */ |
| protected transient DataModifyQuery changeOrderTargetQuery; |
| /** |
| * Specifies what should be done if the list of values read from listOrserField is invalid |
| * (there should be no nulls, no duplicates, no "holes"). |
| **/ |
| protected OrderCorrectionType orderCorrectionType; |
| |
| /** Store if the mapping can batch delete reference objects. */ |
| protected Boolean mustDeleteReferenceObjectsOneByOne = null; |
| |
| /** Flag to indicate if collection needs to be synchronized instead of cloning during merge. */ |
| protected static boolean isSynchronizeOnMerge = Boolean.getBoolean("eclipselink.synchronizeCollectionOnMerge"); |
| |
| /** |
| * PUBLIC: |
| * Default constructor. |
| */ |
| protected CollectionMapping() { |
| this.selectionQuery = new ReadAllQuery(); |
| this.hasCustomDeleteAllQuery = false; |
| this.containerPolicy = ContainerPolicy.buildDefaultPolicy(); |
| this.hasOrderBy = false; |
| this.isListOrderFieldSupported = false; |
| } |
| |
| /** |
| * PUBLIC: |
| * Provide order support for queryKeyName in ascending order |
| */ |
| public void addAscendingOrdering(String queryKeyName) { |
| this.hasOrderBy = true; |
| if (queryKeyName == null) { |
| return; |
| } |
| |
| ((ReadAllQuery)getSelectionQuery()).addAscendingOrdering(queryKeyName); |
| } |
| |
| /** |
| * PUBLIC: |
| * Provide order support for queryKeyName in descending order. |
| */ |
| public void addDescendingOrdering(String queryKeyName) { |
| this.hasOrderBy = true; |
| if (queryKeyName == null) { |
| return; |
| } |
| |
| ((ReadAllQuery)getSelectionQuery()).addDescendingOrdering(queryKeyName); |
| } |
| |
| /** |
| * PUBLIC: |
| * Provide order support for queryKeyName in descending or ascending order. |
| * Called from the jpa metadata processing of an order by value. |
| */ |
| public void addOrderBy(String queryKeyName, boolean isDescending) { |
| if (isDescending) { |
| addDescendingOrdering(queryKeyName); |
| } else { |
| addAscendingOrdering(queryKeyName); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Provide order support for queryKeyName in ascending or descending order. |
| * Called from the jpa metadata processing of an order by value. The |
| * aggregate name may be chained through the dot notation. |
| */ |
| public void addAggregateOrderBy(String aggregateName, String queryKeyName, boolean isDescending) { |
| this.hasOrderBy = true; |
| |
| ReadAllQuery readAllQuery = (ReadAllQuery) getSelectionQuery(); |
| ExpressionBuilder builder = readAllQuery.getExpressionBuilder(); |
| Expression expression = null; |
| |
| if (aggregateName.contains(".")) { |
| StringTokenizer st = new StringTokenizer(aggregateName, "."); |
| while (st.hasMoreTokens()) { |
| if (expression == null) { |
| expression = builder.get(st.nextToken()); |
| } else { |
| expression = expression.get(st.nextToken()); |
| } |
| } |
| |
| expression = expression.get(queryKeyName); |
| } else { |
| // Single level aggregate |
| if (aggregateName.equals("")) { |
| expression = builder.get(queryKeyName); |
| } else { |
| expression = builder.get(aggregateName).get(queryKeyName); |
| } |
| } |
| |
| 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. |
| */ |
| @Override |
| public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) { |
| // Check for null |
| if (attributeValue == null) { |
| return this.containerPolicy.containerInstance(1); |
| } else { |
| return this.containerPolicy.cloneFor(attributeValue); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Require for cloning, the part must be cloned. |
| * Ignore the objects, use the attribute value. |
| */ |
| @Override |
| public Object buildCloneForPartObject(Object attributeValue, Object original, CacheKey cacheKey, Object clone, AbstractSession cloningSession, Integer refreshCascade, boolean isExisting, boolean isFromSharedCache) { |
| ContainerPolicy containerPolicy = this.containerPolicy; |
| if (attributeValue == null) { |
| Object container = containerPolicy.containerInstance(1); |
| if (cloningSession.isUnitOfWork() && (this.getDescriptor().getObjectChangePolicy().isObjectChangeTrackingPolicy()) && ((clone != null) && (((ChangeTracker)clone)._persistence_getPropertyChangeListener() != null)) && (container instanceof CollectionChangeTracker)) { |
| ((CollectionChangeTracker)container).setTrackedAttributeName(this.getAttributeName()); |
| ((CollectionChangeTracker)container)._persistence_setPropertyChangeListener(((ChangeTracker)clone)._persistence_getPropertyChangeListener()); |
| } |
| return container; |
| } |
| Object clonedAttributeValue = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue)); |
| |
| Object temporaryCollection = null; |
| if (isSynchronizeOnMerge) { |
| // 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 |
| synchronized (attributeValue) { |
| temporaryCollection = containerPolicy.cloneFor(attributeValue); |
| } |
| } else { |
| // Clone is used while merging into cache. It can operate directly without synchronize/clone. |
| temporaryCollection = attributeValue; |
| } |
| for (Object valuesIterator = containerPolicy.iteratorFor(temporaryCollection);containerPolicy.hasNext(valuesIterator);){ |
| containerPolicy.addNextValueFromIteratorInto(valuesIterator, clone, cacheKey, clonedAttributeValue, this, refreshCascade, cloningSession, isExisting, isFromSharedCache); |
| } |
| if (cloningSession.isUnitOfWork() && (this.getDescriptor().getObjectChangePolicy().isObjectChangeTrackingPolicy()) && ((clone != null) && (((ChangeTracker)clone)._persistence_getPropertyChangeListener() != null)) && (clonedAttributeValue instanceof CollectionChangeTracker)) { |
| ((CollectionChangeTracker)clonedAttributeValue).setTrackedAttributeName(this.getAttributeName()); |
| ((CollectionChangeTracker)clonedAttributeValue)._persistence_setPropertyChangeListener(((ChangeTracker)clone)._persistence_getPropertyChangeListener()); |
| } |
| if(temporaryCollection instanceof IndirectList) { |
| ((IndirectList)clonedAttributeValue).setIsListOrderBrokenInDb(((IndirectList)temporaryCollection).isListOrderBrokenInDb()); |
| } |
| return clonedAttributeValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Performs a first level clone of the attribute. This generally means on the container will be cloned. |
| */ |
| @Override |
| public Object buildContainerClone(Object attributeValue, AbstractSession cloningSession){ |
| Object newContainer = this.containerPolicy.containerInstance(this.containerPolicy.sizeFor(attributeValue)); |
| Object valuesIterator = this.containerPolicy.iteratorFor(attributeValue); |
| while (this.containerPolicy.hasNext(valuesIterator)) { |
| Object originalValue = this.containerPolicy.next(valuesIterator, cloningSession); |
| this.containerPolicy.addInto(originalValue, newContainer, cloningSession); |
| } |
| return newContainer; |
| } |
| |
| /** |
| * INTERNAL: |
| * Copy of the attribute of the object. |
| * This is NOT used for unit of work but for templatizing an object. |
| */ |
| @Override |
| public void buildCopy(Object copy, Object original, CopyGroup group) { |
| Object attributeValue = getRealCollectionAttributeValueFromObject(original, group.getSession()); |
| Object valuesIterator = this.containerPolicy.iteratorFor(attributeValue); |
| attributeValue = this.containerPolicy.containerInstance(this.containerPolicy.sizeFor(attributeValue)); |
| while (this.containerPolicy.hasNext(valuesIterator)) { |
| Object originalValue = this.containerPolicy.next(valuesIterator, group.getSession()); |
| Object copyValue = originalValue; |
| Object originalKey = this.containerPolicy.keyFromIterator(valuesIterator); |
| Object copyKey = originalKey; |
| if (group.shouldCascadeAllParts() || (group.shouldCascadePrivateParts() && isPrivateOwned()) || group.shouldCascadeTree()) { |
| copyValue = copyElement(originalValue, group); |
| copyKey = group.getSession().copyInternal(originalKey, group); |
| } else { |
| // Check for backrefs to copies. |
| copyValue = group.getCopies().get(originalValue); |
| if (copyValue == null) { |
| copyValue = originalValue; |
| } |
| } |
| this.containerPolicy.addInto(copyKey, copyValue, attributeValue, group.getSession()); |
| } |
| // if value holder is used, then the value holder shared with original substituted for a new ValueHolder. |
| getIndirectionPolicy().reset(copy); |
| setRealAttributeValueInObject(copy, attributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Copies member's value |
| */ |
| protected Object copyElement(Object original, CopyGroup group) { |
| return group.getSession().copyInternal(original, group); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the element, if necessary. |
| */ |
| public Object buildElementUnitOfWorkClone(Object element, Object parent, Integer refreshCascade, UnitOfWorkImpl unitOfWork, boolean isExisting, boolean isFromSharedCache) { |
| // optimize registration to knowledge of existence |
| if (refreshCascade != null ){ |
| switch(refreshCascade){ |
| case ObjectBuildingQuery.CascadeAllParts : |
| return unitOfWork.mergeClone(element, MergeManager.CASCADE_ALL_PARTS, true); |
| case ObjectBuildingQuery.CascadePrivateParts : |
| return unitOfWork.mergeClone(element, MergeManager.CASCADE_PRIVATE_PARTS, true); |
| case ObjectBuildingQuery.CascadeByMapping : |
| return unitOfWork.mergeClone(element, MergeManager.CASCADE_BY_MAPPING, true); |
| default: |
| return unitOfWork.mergeClone(element, MergeManager.NO_CASCADE, true); |
| } |
| }else{ |
| if (isExisting) { |
| return unitOfWork.registerExistingObject(element, isFromSharedCache); |
| } else {// not known whether existing or not |
| return unitOfWork.registerObject(element); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the element, if necessary. |
| */ |
| public Object buildElementClone(Object element, Object parent, CacheKey parentCacheKey, Integer refreshCascade, AbstractSession cloningSession, boolean isExisting, boolean isFromSharedCache) { |
| if (cloningSession.isUnitOfWork()){ |
| return buildElementUnitOfWorkClone(element, parent, refreshCascade, (UnitOfWorkImpl)cloningSession, isExisting, isFromSharedCache); |
| } |
| if (referenceDescriptor.getCachePolicy().isProtectedIsolation()){ |
| return cloningSession.createProtectedInstanceFromCachedData(element, refreshCascade, referenceDescriptor); |
| } |
| return element; |
| } |
| |
| /** |
| * INTERNAL: |
| * In case Query By Example is used, this method generates an expression from a attribute value pair. Since |
| * this is an Aggregate mapping, a recursive call is made to the buildExpressionFromExample method of |
| * ObjectBuilder. |
| */ |
| @Override |
| public Expression buildExpression(Object queryObject, QueryByExamplePolicy policy, Expression expressionBuilder, Map processedObjects, AbstractSession session) { |
| String bypassProperty = PrivilegedAccessHelper.getSystemProperty(SystemProperties.DO_NOT_PROCESS_XTOMANY_FOR_QBE); |
| if (this.getContainerPolicy().isMapPolicy() || (bypassProperty != null && bypassProperty.toLowerCase().equals("true")) ){ |
| // not supported |
| return super.buildExpression(queryObject, policy, expressionBuilder, processedObjects, session); |
| } |
| String attributeName = this.getAttributeName(); |
| Object attributeValue = this.getRealAttributeValueFromObject(queryObject, session); |
| if (attributeValue != null && getContainerPolicy().isEmpty(attributeValue)){ |
| //empty is the same as null |
| attributeValue = null; |
| } |
| |
| if (!policy.shouldIncludeInQuery(queryObject.getClass(), attributeName, attributeValue)) { |
| //the attribute name and value pair is not to be included in the query. |
| return null; |
| } |
| |
| if (attributeValue == null) { |
| //even though it is null, it is to be always included in the query |
| Expression expression = expressionBuilder.anyOf(attributeName); |
| return policy.completeExpressionForNull(expression); |
| } |
| |
| Object iterator = getContainerPolicy().iteratorFor(attributeValue); |
| Expression exp = null; |
| ObjectBuilder objectBuilder = getReferenceDescriptor().getObjectBuilder(); |
| Expression base = expressionBuilder.anyOf(attributeName); |
| while(getContainerPolicy().hasNext(iterator)){ |
| Object element = getContainerPolicy().next(iterator, session); |
| if (exp == null){ |
| exp = objectBuilder.buildExpressionFromExample(element, policy, base, processedObjects, session); |
| }else{ |
| exp = exp.or(objectBuilder.buildExpressionFromExample(element, policy, base, processedObjects, session)); |
| } |
| } |
| return exp; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method will access the target relationship and create a list of information to rebuild the relationship. |
| * This method is used in combination with the CachedValueHolder to store references to PK's to be loaded |
| * from a cache instead of a query. |
| */ |
| @Override |
| public Object[] buildReferencesPKList(Object entity, Object attribute, AbstractSession session){ |
| Object container = indirectionPolicy.getRealAttributeValueFromObject(entity, attribute); |
| return containerPolicy.buildReferencesPKList(container, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade perform delete through mappings that require the cascade |
| */ |
| @Override |
| public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| if (!this.cascadeRemove) { |
| return; |
| } |
| Object cloneAttribute = getAttributeValueFromObject(object); |
| if (cloneAttribute == null) { |
| return; |
| } |
| // PERF: If private owned and not instantiated, then avoid instantiating, delete-all will handle deletion. |
| if ((this.isPrivateOwned) && usesIndirection() && (!mustDeleteReferenceObjectsOneByOne())) { |
| if (!this.indirectionPolicy.objectIsEasilyInstantiated(cloneAttribute)) { |
| return; |
| } |
| } |
| |
| ContainerPolicy cp = this.containerPolicy; |
| Object cloneObjectCollection = null; |
| cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow); |
| Object cloneIter = cp.iteratorFor(cloneObjectCollection); |
| while (cp.hasNext(cloneIter)) { |
| Object wrappedObject = cp.nextEntry(cloneIter, uow); |
| Object nextObject = cp.unwrapIteratorResult(wrappedObject); |
| if ((nextObject != null) && (!visitedObjects.containsKey(nextObject))) { |
| visitedObjects.put(nextObject, nextObject); |
| if (this.isCascadeOnDeleteSetOnDatabase && isOneToManyMapping()) { |
| uow.getCascadeDeleteObjects().add(nextObject); |
| } |
| uow.performRemove(nextObject, visitedObjects); |
| cp.cascadePerformRemoveIfRequired(wrappedObject, uow, visitedObjects); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade perform removal of orphaned private owned objects from the UnitOfWorkChangeSet |
| */ |
| @Override |
| public void cascadePerformRemovePrivateOwnedObjectFromChangeSetIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| // if the object is not instantiated, do not instantiate or cascade |
| Object attributeValue = getAttributeValueFromObject(object); |
| if (attributeValue != null && this.indirectionPolicy.objectIsInstantiated(attributeValue)) { |
| Object realObjectCollection = getRealCollectionAttributeValueFromObject(object, uow); |
| ContainerPolicy cp = this.containerPolicy; |
| for (Object cloneIter = cp.iteratorFor(realObjectCollection); cp.hasNext(cloneIter);) { |
| Object nextObject = cp.next(cloneIter, uow); |
| if (nextObject != null && !visitedObjects.containsKey(nextObject)) { |
| visitedObjects.put(nextObject, nextObject); |
| // remove the object from the UnitOfWork ChangeSet |
| uow.performRemovePrivateOwnedObjectFromChangeSet(nextObject, visitedObjects); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade discover and persist new objects during commit. |
| */ |
| @Override |
| public void cascadeDiscoverAndPersistUnregisteredNewObjects(Object object, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, UnitOfWorkImpl uow, Set cascadeErrors) { |
| Object cloneAttribute = getAttributeValueFromObject(object); |
| if ((cloneAttribute == null) || (!this.indirectionPolicy.objectIsInstantiated(cloneAttribute))) { |
| if (cloneAttribute instanceof IndirectCollection) { |
| IndirectCollection collection = (IndirectCollection)cloneAttribute; |
| if (collection.hasDeferredChanges()) { |
| Iterator iterator = collection.getAddedElements().iterator(); |
| boolean cascade = isCascadePersist(); |
| while (iterator.hasNext()) { |
| Object nextObject = iterator.next(); |
| // remove private owned object from uow list |
| if (isCandidateForPrivateOwnedRemoval()){ |
| uow.removePrivateOwnedObject(this, nextObject); |
| } |
| uow.discoverAndPersistUnregisteredNewObjects(nextObject, cascade, newObjects, unregisteredExistingObjects, visitedObjects, cascadeErrors); |
| } |
| } |
| } |
| return; |
| } |
| |
| ContainerPolicy containerPolicy = this.containerPolicy; |
| Object cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow); |
| Object iterator = containerPolicy.iteratorFor(cloneObjectCollection); |
| boolean cascade = isCascadePersist(); |
| while (containerPolicy.hasNext(iterator)) { |
| Object wrappedObject = containerPolicy.nextEntry(iterator, uow); |
| Object nextObject = containerPolicy.unwrapIteratorResult(wrappedObject); |
| // remove private owned object from uow list |
| if (isCandidateForPrivateOwnedRemoval()) { |
| uow.removePrivateOwnedObject(this, nextObject); |
| } |
| uow.discoverAndPersistUnregisteredNewObjects(nextObject, cascade, newObjects, unregisteredExistingObjects, visitedObjects, cascadeErrors); |
| containerPolicy.cascadeDiscoverAndPersistUnregisteredNewObjects(wrappedObject, newObjects, unregisteredExistingObjects, visitedObjects, uow, cascadeErrors); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade registerNew for Create through mappings that require the cascade |
| */ |
| @Override |
| public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| if (!this.cascadePersist) { |
| return; |
| } |
| Object attributeValue = getAttributeValueFromObject(object); |
| if ((attributeValue == null) |
| // Also check if the source is new, then must always cascade. |
| || (!this.indirectionPolicy.objectIsInstantiated(attributeValue) && !uow.isCloneNewObject(object))) { |
| return; |
| } |
| |
| ContainerPolicy cp = this.containerPolicy; |
| Object cloneObjectCollection = null; |
| cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow); |
| Object cloneIter = cp.iteratorFor(cloneObjectCollection); |
| // add private owned objects to uow list if mapping is a candidate and uow should discover new objects and the source object is new. |
| boolean shouldAddPrivateOwnedObject = isCandidateForPrivateOwnedRemoval() && uow.shouldDiscoverNewObjects() && uow.isCloneNewObject(object); |
| while (cp.hasNext(cloneIter)) { |
| Object wrappedObject = cp.nextEntry(cloneIter, uow); |
| Object nextObject = cp.unwrapIteratorResult(wrappedObject); |
| if (shouldAddPrivateOwnedObject && nextObject != null) { |
| uow.addPrivateOwnedObject(this, nextObject); |
| } |
| uow.registerNewObjectForPersist(nextObject, visitedObjects); |
| cp.cascadeRegisterNewIfRequired(wrappedObject, uow, visitedObjects); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to store the FK fields that can be cached that correspond to noncacheable mappings |
| * the FK field values will be used to re-issue the query when cloning the shared cache entity |
| */ |
| @Override |
| public void collectQueryParameters(Set<DatabaseField> record){ |
| //no-op for mappings that do not support PROTECTED cache isolation |
| } |
| |
| /** |
| * 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. |
| */ |
| @Override |
| public void calculateDeferredChanges(ChangeRecord changeRecord, AbstractSession session) { |
| CollectionChangeRecord collectionRecord = (CollectionChangeRecord)changeRecord; |
| // TODO: Handle events that fired after collection was replaced. |
| compareCollectionsForChange(collectionRecord.getOriginalCollection(), collectionRecord.getLatestCollection(), collectionRecord, session); |
| |
| if(this.isPrivateOwned()) { |
| postCalculateChanges(collectionRecord, (UnitOfWorkImpl)session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The mapping clones itself to create deep copy. |
| */ |
| @Override |
| public Object clone() { |
| CollectionMapping clone = (CollectionMapping)super.clone(); |
| clone.setDeleteAllQuery((ModifyQuery)getDeleteAllQuery().clone()); |
| if (this.listOrderField != null) { |
| clone.listOrderField = this.listOrderField.clone(); |
| } |
| if(this.changeOrderTargetQuery != null) { |
| clone.changeOrderTargetQuery = (DataModifyQuery)this.changeOrderTargetQuery.clone(); |
| } |
| |
| // Clone the container policy. |
| clone.containerPolicy = (ContainerPolicy) this.containerPolicy.clone(); |
| |
| return clone; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to calculate the differences between two collections. |
| */ |
| public void compareCollectionsForChange(Object oldCollection, Object newCollection, ChangeRecord changeRecord, AbstractSession session) { |
| this.containerPolicy.compareCollectionsForChange(oldCollection, newCollection, (CollectionChangeRecord) changeRecord, session, getReferenceDescriptor()); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to create a change record from comparing two collections. |
| */ |
| @Override |
| 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) && (!this.indirectionPolicy.objectIsInstantiated(cloneAttribute))) { |
| return null; |
| } |
| |
| if (!owner.isNew()) {// if the changeSet is for a new object then we must record all of 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 = this.containerPolicy.containerInstance(1); |
| } |
| |
| CollectionChangeRecord changeRecord = new CollectionChangeRecord(owner); |
| changeRecord.setAttribute(getAttributeName()); |
| changeRecord.setMapping(this); |
| compareCollectionsForChange(backUpObjectCollection, cloneObjectCollection, changeRecord, session); |
| if (changeRecord.hasChanges()) { |
| changeRecord.setOriginalCollection(backUpObjectCollection); |
| return changeRecord; |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| 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: |
| * Write the changes defined in the change set for the mapping. |
| * Mapping added or removed events are raised to allow the mapping to write the changes as required. |
| */ |
| public void writeChanges(ObjectChangeSet changeSet, WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| CollectionChangeRecord record = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (record != null) { |
| for (ObjectChangeSet removedChangeSet : record.getRemoveObjectList().values()) { |
| objectRemovedDuringUpdate(query, this.containerPolicy.getCloneDataFromChangeSet(removedChangeSet), null); |
| if (removedChangeSet.getOldKey() != null){ |
| this.containerPolicy.propogatePostUpdate(query, removedChangeSet.getOldKey()); |
| } |
| } |
| Map extraData = null; |
| Object currentObjects = null; |
| for (ObjectChangeSet addedChangeSet : record.getAddObjectList().values()) { |
| if (this.listOrderField != null) { |
| extraData = new HashMap(1); |
| Integer addedIndexInList = record.getOrderedAddObjectIndices().get(addedChangeSet); |
| if (addedIndexInList == null) { |
| if (currentObjects == null) { |
| currentObjects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| } |
| addedIndexInList = ((List)currentObjects).indexOf(addedChangeSet.getUnitOfWorkClone()); |
| } |
| extraData.put(this.listOrderField, addedIndexInList); |
| } |
| objectAddedDuringUpdate(query, this.containerPolicy.getCloneDataFromChangeSet(addedChangeSet), addedChangeSet, extraData); |
| if (addedChangeSet.getNewKey() != null){ |
| this.containerPolicy.propogatePostUpdate(query, addedChangeSet.getNewKey()); |
| } |
| } |
| if (this.listOrderField != null) { |
| // This is a hacky check for attribute change tracking, if the backup clone is different, then is using deferred. |
| List previousList = (List)getRealCollectionAttributeValueFromObject(query.getBackupClone(), query.getSession()); |
| int previousSize = previousList.size(); |
| if (currentObjects == null) { |
| currentObjects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| } |
| List currentList = (List)currentObjects; |
| int currentSize = currentList.size(); |
| boolean shouldRepairOrder = false; |
| if(currentList instanceof IndirectList) { |
| shouldRepairOrder = ((IndirectList)currentList).isListOrderBrokenInDb(); |
| } |
| if(previousList == currentList) { |
| // previousList is not available |
| |
| // The same size as previous list, |
| // at the i-th position holds the index of the i-th original object in the current list (-1 if the object was removed): |
| // for example: {0, -1, 1, -1, 3} means that: |
| // previous(0) == current(0); |
| // previous(1) was removed; |
| // previous(2) == current(1); |
| // previous(3) was removed; |
| // previous(4) == current(3); |
| // current(1) and current(3) were also on previous list, but with different indexes: they are the ones that should have their index changed. |
| List<Integer> currentIndexes = record.getCurrentIndexesOfOriginalObjects(currentList); |
| for(int i=0; i < currentIndexes.size(); i++) { |
| int currentIndex = currentIndexes.get(i); |
| if((currentIndex >= 0) && (currentIndex != i || shouldRepairOrder)) { |
| objectOrderChangedDuringUpdate(query, currentList.get(currentIndex), currentIndex); |
| } |
| } |
| } else { |
| for (int i=0; i < previousSize; i++) { |
| // TODO: should we check for previousObject != null? |
| Object prevObject = previousList.get(i); |
| Object currentObject = null; |
| if(i < currentSize) { |
| currentObject = currentList.get(i); |
| } |
| if(prevObject != currentObject || shouldRepairOrder) { |
| // object has either been removed or its index in the List has changed |
| int newIndex = currentList.indexOf(prevObject); |
| if(newIndex >= 0) { |
| objectOrderChangedDuringUpdate(query, prevObject, newIndex); |
| } |
| } |
| } |
| } |
| if (shouldRepairOrder) { |
| ((IndirectList)currentList).setIsListOrderBrokenInDb(false); |
| record.setOrderHasBeenRepaired(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The memory objects are compared and only the changes are written to the database. |
| */ |
| protected void compareObjectsAndWrite(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| Object currentObjects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession()); |
| Object previousObjects = readPrivateOwnedForObject(query); |
| if (previousObjects == null) { |
| previousObjects = getContainerPolicy().containerInstance(1); |
| } |
| if (this.listOrderField != null && this.isAggregateCollectionMapping()) { |
| compareListsAndWrite((List)previousObjects, (List)currentObjects, query); |
| return; |
| } |
| |
| ContainerPolicy cp = this.containerPolicy; |
| |
| Map previousObjectsByKey = new HashMap(cp.sizeFor(previousObjects)); // Read from db or from backup in uow. |
| Map currentObjectsByKey = new HashMap(cp.sizeFor(currentObjects)); // Current value of object's attribute (clone in uow). |
| |
| Map keysOfCurrentObjects = new IdentityHashMap(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 { |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(currentObject, query.getSession()); |
| currentObjectsByKey.put(primaryKey, currentObject); |
| keysOfCurrentObjects.put(currentObject, primaryKey); |
| } 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 wrappedObject = cp.nextEntry(previousObjectsIter, query.getSession()); |
| Map mapKeyFields = containerPolicy.getKeyMappingDataForWriteQuery(wrappedObject, query.getSession()); |
| Object previousObject = containerPolicy.unwrapIteratorResult(wrappedObject); |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(previousObject, query.getSession()); |
| previousObjectsByKey.put(primaryKey, previousObject); |
| // Delete must occur first, in case object with same pk is removed and added, |
| // (technically should not happen, but same applies to unique constraints) |
| if (!currentObjectsByKey.containsKey(primaryKey)) { |
| objectRemovedDuringUpdate(query, wrappedObject, mapKeyFields); |
| cp.propogatePostUpdate(query, wrappedObject); |
| } |
| } |
| |
| for (Object currentObjectsIter = cp.iteratorFor(currentObjects); |
| cp.hasNext(currentObjectsIter);) { |
| Object wrappedObject = cp.nextEntry(currentObjectsIter, query.getSession()); |
| Object currentObject = containerPolicy.unwrapIteratorResult(wrappedObject); |
| try { |
| Map mapKeyFields = containerPolicy.getKeyMappingDataForWriteQuery(wrappedObject, query.getSession()); |
| Object primaryKey = keysOfCurrentObjects.get(currentObject); |
| |
| if (!(previousObjectsByKey.containsKey(primaryKey))) { |
| objectAddedDuringUpdate(query, currentObject, null, mapKeyFields); |
| cp.propogatePostUpdate(query, wrappedObject); |
| } else { |
| objectUnchangedDuringUpdate(query, currentObject, previousObjectsByKey, primaryKey); |
| } |
| } catch (NullPointerException e) { |
| // For CR#2646 skip currentObject if it is null. |
| if (currentObject != null) { |
| throw e; |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Old and new lists are compared and only the changes are written to the database. |
| * Currently there's no support for listOrderField in CollectionMapping in case there's no change sets, |
| * so this method currently never called (currently only overriding method in AggregateCollectionMapping is called). |
| * This method should be implemented to support listOrderField functionality without change sets. |
| */ |
| protected void compareListsAndWrite(List previousList, List currentList, WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| } |
| |
| /** |
| * Compare two objects if their parts are not private owned |
| */ |
| @Override |
| protected boolean compareObjectsWithoutPrivateOwned(Object firstCollection, Object secondCollection, AbstractSession session) { |
| if(this.listOrderField != null) { |
| return compareLists((List)firstCollection, (List)secondCollection, session, false); |
| } |
| |
| ContainerPolicy cp = this.containerPolicy; |
| if (cp.sizeFor(firstCollection) != cp.sizeFor(secondCollection)) { |
| return false; |
| } |
| |
| Object firstIter = cp.iteratorFor(firstCollection); |
| Object secondIter = cp.iteratorFor(secondCollection); |
| |
| Map keyValues = new HashMap(); |
| |
| if (isMapKeyMapping()) { |
| while (cp.hasNext(secondIter)) { |
| Map.Entry secondObject = (Map.Entry)cp.nextEntry(secondIter, session); |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject.getValue(), session); |
| Object key = secondObject.getKey(); |
| keyValues.put(key, primaryKey); |
| } |
| while (cp.hasNext(firstIter)) { |
| Map.Entry firstObject = (Map.Entry)cp.nextEntry(firstIter, session); |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject.getValue(), session); |
| Object key = firstObject.getKey(); |
| if (!primaryKey.equals(keyValues.get(key))) { |
| return false; |
| } |
| } |
| } else { |
| while (cp.hasNext(secondIter)) { |
| Object secondObject = cp.next(secondIter, session); |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject, session); |
| keyValues.put(primaryKey, primaryKey); |
| } |
| while (cp.hasNext(firstIter)) { |
| Object firstObject = cp.next(firstIter, session); |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject, session); |
| if (!keyValues.containsKey(primaryKey)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Compare two objects if their parts are private owned |
| */ |
| @Override |
| protected boolean compareObjectsWithPrivateOwned(Object firstCollection, Object secondCollection, AbstractSession session) { |
| if(this.listOrderField != null) { |
| return compareLists((List)firstCollection, (List)secondCollection, session, true); |
| } |
| |
| ContainerPolicy cp = this.containerPolicy; |
| if (cp.sizeFor(firstCollection) != cp.sizeFor(secondCollection)) { |
| return false; |
| } |
| |
| Object firstIter = cp.iteratorFor(firstCollection); |
| Object secondIter = cp.iteratorFor(secondCollection); |
| |
| Map keyValueToObject = new HashMap(cp.sizeFor(firstCollection)); |
| |
| while (cp.hasNext(secondIter)) { |
| Object secondObject = cp.next(secondIter, session); |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject, session); |
| keyValueToObject.put(primaryKey, secondObject); |
| } |
| |
| while (cp.hasNext(firstIter)) { |
| Object firstObject = cp.next(firstIter, session); |
| Object primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject, session); |
| |
| if (keyValueToObject.containsKey(primaryKey)) { |
| Object object = keyValueToObject.get(primaryKey); |
| |
| if (!session.compareObjects(firstObject, object)) { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Compare two lists. For equality the order of the elements should be the same. |
| * Used only if listOrderField != null |
| */ |
| protected boolean compareLists(List firstList, List secondList, AbstractSession session, boolean withPrivateOwned) { |
| if (firstList.size() != secondList.size()) { |
| return false; |
| } |
| |
| int size = firstList.size(); |
| for(int i=0; i < size; i++) { |
| Object firstObject = firstList.get(i); |
| Object secondObject = secondList.get(i); |
| if(withPrivateOwned) { |
| if(!session.compareObjects(firstObject, secondObject)) { |
| return false; |
| } |
| } else { |
| Object firstKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject, session); |
| Object secondKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject, session); |
| if (!firstKey.equals(secondKey)) { |
| 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. |
| */ |
| @Override |
| public void convertClassNamesToClasses(ClassLoader classLoader){ |
| super.convertClassNamesToClasses(classLoader); |
| containerPolicy.convertClassNamesToClasses(classLoader); |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the value from the batch optimized query, this should be supported by most query types. |
| */ |
| @Override |
| public Object extractResultFromBatchQuery(ReadQuery batchQuery, CacheKey parentCacheKey, AbstractRecord sourceRow, AbstractSession session, ObjectLevelReadQuery originalQuery) throws QueryException { |
| Object result = super.extractResultFromBatchQuery(batchQuery, parentCacheKey, sourceRow, session, originalQuery); |
| // The source object might not have any target objects. |
| if (result == null) { |
| return this.containerPolicy.containerInstance(); |
| } else { |
| return result; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare and execute the batch query and store the |
| * results for each source object in a map keyed by the |
| * mappings source keys of the source objects. |
| */ |
| @Override |
| protected void executeBatchQuery(DatabaseQuery query, CacheKey parentCacheKey, Map referenceObjectsByKey, AbstractSession session, AbstractRecord translationRow) { |
| // Execute query and index resulting object sets by key. |
| ReadAllQuery batchQuery = (ReadAllQuery)query; |
| ComplexQueryResult complexResult = (ComplexQueryResult)session.executeQuery(batchQuery, translationRow); |
| Object results = complexResult.getResult(); |
| Iterator<AbstractRecord> rowsIterator = ((List<AbstractRecord>)complexResult.getData()).iterator(); |
| ContainerPolicy queryContainerPolicy = batchQuery.getContainerPolicy(); |
| if (this.containerPolicy.shouldAddAll()) { |
| // Indexed list mappings require special add that include the row data with the index. |
| Map<Object, List[]> referenceObjectsAndRowsByKey = new HashMap(); |
| for (Object objectsIterator = queryContainerPolicy.iteratorFor(results); queryContainerPolicy.hasNext(objectsIterator);) { |
| Object eachReferenceObject = queryContainerPolicy.next(objectsIterator, session); |
| AbstractRecord row = rowsIterator.next(); |
| Object eachReferenceKey = extractKeyFromTargetRow(row, session); |
| List[] objectsAndRows = referenceObjectsAndRowsByKey.get(eachReferenceKey); |
| if (objectsAndRows == null) { |
| objectsAndRows = new List[]{new ArrayList(), new ArrayList()}; |
| referenceObjectsAndRowsByKey.put(eachReferenceKey, objectsAndRows); |
| } |
| objectsAndRows[0].add(eachReferenceObject); |
| objectsAndRows[1].add(row); |
| } |
| |
| Iterator<Map.Entry<Object, List[]>> iterator = referenceObjectsAndRowsByKey.entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<Object, List[]> entry = iterator.next(); |
| Object eachReferenceKey = entry.getKey(); |
| List objects = entry.getValue()[0]; |
| List<AbstractRecord> rows = entry.getValue()[1]; |
| Object container = this.containerPolicy.containerInstance(objects.size()); |
| this.containerPolicy.addAll(objects, container, query.getSession(), rows, batchQuery, parentCacheKey, true); |
| referenceObjectsByKey.put(eachReferenceKey, container); |
| } |
| } else { |
| // Non-indexed list, either normal collection, or a map key. |
| for (Object objectsIterator = queryContainerPolicy.iteratorFor(results); queryContainerPolicy.hasNext(objectsIterator);) { |
| Object eachReferenceObject = queryContainerPolicy.next(objectsIterator, session); |
| AbstractRecord row = rowsIterator.next(); |
| // Handle duplicate rows in the ComplexQueryResult being replaced with null, as a |
| // result of duplicate filtering being true for constructing the ComplexQueryResult |
| while (row == null && rowsIterator.hasNext()) { |
| row = rowsIterator.next(); |
| } |
| Object eachReferenceKey = extractKeyFromTargetRow(row, session); |
| |
| Object container = referenceObjectsByKey.get(eachReferenceKey); |
| if ((container == null) || (container == Helper.NULL_VALUE)) { |
| container = this.containerPolicy.containerInstance(); |
| referenceObjectsByKey.put(eachReferenceKey, container); |
| } |
| this.containerPolicy.addInto(eachReferenceObject, container, session, row, batchQuery, parentCacheKey, true); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the source primary key value from the target row. |
| * Used for batch reading, most following same order and fields as in the mapping. |
| * The method should be overridden by classes that support batch reading. |
| */ |
| protected Object extractKeyFromTargetRow(AbstractRecord row, AbstractSession session) { |
| throw QueryException.batchReadingNotSupported(this, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * We are not using a remote valueholder |
| * so we need to replace the reference object(s) with |
| * the corresponding object(s) from the remote session. |
| */ |
| @Override |
| public void fixRealObjectReferences(Object object, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, DistributedSession session) { |
| //bug 4147755 getRealAttribute... / setReal |
| Object attributeValue = getRealAttributeValueFromObject(object, session); |
| |
| // the object collection could be null, check here to avoid NPE |
| if (attributeValue == null) { |
| setAttributeValueInObject(object, null); |
| return; |
| } |
| |
| ObjectLevelReadQuery tempQuery = query; |
| if (!tempQuery.shouldMaintainCache()) { |
| if ((!tempQuery.shouldCascadeParts()) || (tempQuery.shouldCascadePrivateParts() && (!isPrivateOwned()))) { |
| tempQuery = null; |
| } |
| } |
| |
| Object remoteAttributeValue = session.getObjectsCorrespondingToAll(attributeValue, objectDescriptors, processedObjects, tempQuery, this.containerPolicy); |
| setRealAttributeValueInObject(object, remoteAttributeValue); |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Returns the receiver's containerPolicy. |
| */ |
| @Override |
| public ContainerPolicy getContainerPolicy() { |
| return containerPolicy; |
| } |
| |
| protected ModifyQuery getDeleteAllQuery() { |
| if (deleteAllQuery == null) { |
| deleteAllQuery = new DataModifyQuery(); |
| } |
| return deleteAllQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the join criteria stored in the mapping selection query. This criteria |
| * is used to read reference objects across the tables from the database. |
| */ |
| @Override |
| public Expression getJoinCriteria(ObjectExpression context, Expression base) { |
| Expression selectionCriteria = getSelectionCriteria(); |
| Expression keySelectionCriteria = this.containerPolicy.getKeySelectionCriteria(); |
| if (keySelectionCriteria != null) { |
| selectionCriteria = selectionCriteria.and(keySelectionCriteria); |
| } |
| return context.getBaseExpression().twist(selectionCriteria, base); |
| } |
| |
| /** |
| * INTERNAL: |
| * return the object on the client corresponding to the specified object. |
| * CollectionMappings have to worry about |
| * maintaining object identity. |
| */ |
| @Override |
| public Object getObjectCorrespondingTo(Object object, DistributedSession session, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query) { |
| return session.getObjectsCorrespondingToAll(object, objectDescriptors, processedObjects, query, this.containerPolicy); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the ordering query keys. |
| * Used for Workbench integration. |
| */ |
| public List<Expression> getOrderByQueryKeyExpressions() { |
| List<Expression> expressions = new ArrayList<> (); |
| |
| if ((getSelectionQuery() != null) && getSelectionQuery().isReadAllQuery()) { |
| for (Expression orderExpression : ((ReadAllQuery)getSelectionQuery()).getOrderByExpressions()) { |
| if (orderExpression.isFunctionExpression() && ((FunctionExpression)orderExpression).getBaseExpression().isQueryKeyExpression()) { |
| expressions.add(orderExpression); |
| } |
| } |
| } |
| |
| return expressions; |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the container policy from the selection query for this mapping. This |
| * method is overridden in DirectCollectionMapping since its selection |
| * query is a DataReadQuery. |
| */ |
| protected ContainerPolicy getSelectionQueryContainerPolicy() { |
| return ((ReadAllQuery) getSelectionQuery()).getContainerPolicy(); |
| } |
| |
| /** |
| * Convenience method. |
| * Return the value of an attribute, unwrapping value holders if necessary. |
| * If the value is null, build a new container. |
| */ |
| @Override |
| public Object getRealCollectionAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException { |
| Object value = getRealAttributeValueFromObject(object, session); |
| if (value == null) { |
| value = this.containerPolicy.containerInstance(1); |
| } |
| return value; |
| } |
| |
| /** |
| * PUBLIC: |
| * Field holds the order of elements in the list in the db, requires collection of type List; |
| * may be not null only in case isListOrderFieldSupported==true. |
| */ |
| public DatabaseField getListOrderField() { |
| return listOrderField; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns list of primary key fields from the reference descriptor. |
| */ |
| public List<DatabaseField> getTargetPrimaryKeyFields() { |
| return getReferenceDescriptor().getPrimaryKeyFields(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Specifies what should be done if the list of values read from listOrserField is invalid |
| * (there should be no nulls, no duplicates, no "holes"). |
| */ |
| public OrderCorrectionType getOrderCorrectionType() { |
| return this.orderCorrectionType; |
| } |
| |
| 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. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| super.initialize(session); |
| setFields(collectFields()); |
| this.containerPolicy.prepare(getSelectionQuery(), session); |
| |
| // Check that the container policy is correct for the collection type. |
| if ((!usesIndirection()) && (!getAttributeAccessor().getAttributeClass().isAssignableFrom(this.containerPolicy.getContainerClass()))) { |
| throw DescriptorException.incorrectCollectionPolicy(this, getAttributeAccessor().getAttributeClass(), this.containerPolicy.getContainerClass()); |
| } |
| |
| if(listOrderField != null) { |
| initializeListOrderField(session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Initializes listOrderField. |
| * Precondition: listOrderField != null. |
| */ |
| protected void initializeListOrderField(AbstractSession session) { |
| if(!List.class.isAssignableFrom(getAttributeAccessor().getAttributeClass())) { |
| throw DescriptorException.listOrderFieldRequiersList(getDescriptor(), this); |
| } |
| |
| boolean isAttributeAssignableFromIndirectList = getAttributeAccessor().getAttributeClass().isAssignableFrom(IndirectList.class); |
| |
| if(this.orderCorrectionType == null) { |
| // set default validation mode |
| if(isAttributeAssignableFromIndirectList) { |
| this.orderCorrectionType = OrderCorrectionType.READ_WRITE; |
| } else { |
| this.orderCorrectionType = OrderCorrectionType.READ; |
| } |
| } else if(this.orderCorrectionType == OrderCorrectionType.READ_WRITE) { |
| //OrderValidationMode.CORRECTION sets container class to IndirectList, make sure the attribute is of compatible type. |
| if(!isAttributeAssignableFromIndirectList) { |
| throw DescriptorException.listOrderFieldRequiersIndirectList(getDescriptor(), this); |
| } |
| } |
| |
| ContainerPolicy originalQueryContainerPolicy = getSelectionQueryContainerPolicy(); |
| |
| if(!this.containerPolicy.isOrderedListPolicy()) { |
| setContainerPolicy(new OrderedListContainerPolicy(this.containerPolicy.getContainerClass())); |
| // re-prepare replaced container policy as we are initializing |
| getContainerPolicy().prepare(getSelectionQuery(), session); |
| } |
| OrderedListContainerPolicy orderedListContainerPolicy = (OrderedListContainerPolicy)this.containerPolicy; |
| orderedListContainerPolicy.setListOrderField(this.listOrderField); |
| orderedListContainerPolicy.setOrderCorrectionType(this.orderCorrectionType); |
| |
| // If ContainerPolicy's container class is IndirectList, originalQueryContainerPolicy's container class is not (likely Vector) |
| // and orderCorrectionType doesn't require query to use IndirectList - then query will keep a separate container policy |
| // that uses its original container class (likely Vector) - this is the same optimization as used in useTransparentList method. |
| if(this.containerPolicy.getContainerClass().isAssignableFrom(IndirectList.class) && |
| !IndirectList.class.isAssignableFrom(originalQueryContainerPolicy.getContainerClass()) && |
| this.orderCorrectionType != OrderCorrectionType.READ_WRITE || |
| originalQueryContainerPolicy == this.getSelectionQueryContainerPolicy()) |
| { |
| OrderedListContainerPolicy queryOrderedListContainerPolicy; |
| if(originalQueryContainerPolicy.getClass().equals(orderedListContainerPolicy.getClass())) { |
| // original query container policy |
| queryOrderedListContainerPolicy = (OrderedListContainerPolicy)originalQueryContainerPolicy; |
| queryOrderedListContainerPolicy.setListOrderField(this.listOrderField); |
| queryOrderedListContainerPolicy.setOrderCorrectionType(this.orderCorrectionType); |
| } else { |
| // clone mapping's container policy |
| queryOrderedListContainerPolicy = (OrderedListContainerPolicy)orderedListContainerPolicy.clone(); |
| queryOrderedListContainerPolicy.setContainerClass(originalQueryContainerPolicy.getContainerClass()); |
| setSelectionQueryContainerPolicy(queryOrderedListContainerPolicy); |
| } |
| } |
| |
| if(this.listOrderField.getType() == null) { |
| this.listOrderField.setType(Integer.class); |
| } |
| |
| buildListOrderField(); |
| |
| // DirectCollectMap - that uses DataReadQuery - adds listOrderField to selection query in initializeSelectionStatement method. |
| if (getSelectionQuery().isReadAllQuery()) { |
| if(shouldUseListOrderFieldTableExpression()) { |
| initializeListOrderFieldTable(session); |
| } |
| } |
| |
| initializeChangeOrderTargetQuery(session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Initializes listOrderField's table, does nothing by default. |
| * Precondition: listOrderField != null. |
| */ |
| protected void initializeListOrderFieldTable(AbstractSession session) { |
| } |
| |
| /** |
| * INTERNAL: |
| * Verifies listOrderField's table, if none found sets the default one. |
| * Precondition: listOrderField != null. |
| */ |
| protected void buildListOrderField() { |
| if(this.listOrderField.hasTableName()) { |
| if(!this.getReferenceDescriptor().getDefaultTable().equals(this.listOrderField.getTable())) { |
| throw DescriptorException.listOrderFieldTableIsWrong(this.getDescriptor(), this, this.listOrderField.getTable(), this.getReferenceDescriptor().getDefaultTable()); |
| } |
| } else { |
| this.listOrderField.setTable(this.getReferenceDescriptor().getDefaultTable()); |
| } |
| this.listOrderField = this.getReferenceDescriptor().buildField(this.listOrderField); |
| } |
| |
| |
| /** |
| * ADVANCED: |
| * This method should only be called after this mapping's indirection policy has been set |
| * |
| * IndirectList and IndirectSet can be configured not to instantiate the list from the |
| * database when you add and remove from them. IndirectList defaults to this behavior. When |
| * Set to true, the collection associated with this TransparentIndirection will be setup so as |
| * not to instantiate for adds and removes. The weakness of this setting for an IndirectSet is |
| * that when the set is not instantiated, if a duplicate element is added, it will not be |
| * detected until commit time. |
| */ |
| public Boolean shouldUseLazyInstantiationForIndirectCollection() { |
| if (getIndirectionPolicy() == null){ |
| return null; |
| } |
| return getIndirectionPolicy().shouldUseLazyInstantiation(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether getListOrderFieldExpression method should create field expression based on table expression. |
| */ |
| public boolean shouldUseListOrderFieldTableExpression() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize changeOrderTargetQuery. |
| */ |
| protected void initializeChangeOrderTargetQuery(AbstractSession session) { |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether this mapping is a Collection type. |
| */ |
| @Override |
| public boolean isCollectionMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this mapping has a mapped key that uses a OneToOne (object). |
| */ |
| public boolean isMapKeyObjectRelationship() { |
| return this.containerPolicy.isMapKeyObject(); |
| } |
| |
| /** |
| * INTERNAL: |
| * The referenced object is checked if it is instantiated or not, |
| * also check if it has been changed (as indirect collections avoid instantiation on add/remove. |
| */ |
| public boolean isAttributeValueInstantiatedOrChanged(Object object) { |
| return this.indirectionPolicy.objectIsInstantiatedOrChanged(getAttributeValueFromObject(object)); |
| } |
| |
| /** |
| * 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. |
| */ |
| @Override |
| public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) { |
| if (realAttributeValue == null) { |
| return; |
| } |
| ContainerPolicy cp = this.containerPolicy; |
| for (Object iter = cp.iteratorFor(realAttributeValue); cp.hasNext(iter);) { |
| Object wrappedObject = cp.nextEntry(iter, iterator.getSession()); |
| Object object = cp.unwrapIteratorResult(wrappedObject); |
| iterateOnElement(iterator, object); |
| cp.iterateOnMapKey(iterator, wrappedObject); |
| } |
| } |
| |
| /** |
| * Force instantiation of the load group. |
| */ |
| @Override |
| public void load(final Object object, AttributeItem item, final AbstractSession session, final boolean fromFetchGroup) { |
| instantiateAttribute(object, session); |
| if (item.getGroup() != null && (!fromFetchGroup || session.isUnitOfWork()) ){ |
| //if UOW make sure the nested attributes are loaded as the clones will not be instantiated |
| Object value = getRealAttributeValueFromObject(object, session); |
| ContainerPolicy cp = this.containerPolicy; |
| for (Object iterator = cp.iteratorFor(value); cp.hasNext(iterator);) { |
| Object wrappedObject = cp.nextEntry(iterator, session); |
| Object nestedObject = cp.unwrapIteratorResult(wrappedObject); |
| session.load(nestedObject, item.getGroup(nestedObject.getClass()), getReferenceDescriptor(), fromFetchGroup); |
| } |
| } |
| } |
| |
| /** |
| * Force instantiation of all indirections. |
| */ |
| @Override |
| public void loadAll(Object object, AbstractSession session, IdentityHashSet loaded) { |
| instantiateAttribute(object, session); |
| ClassDescriptor referenceDescriptor = getReferenceDescriptor(); |
| if (referenceDescriptor != null) { |
| boolean hasInheritance = referenceDescriptor.hasInheritance() || referenceDescriptor.hasTablePerClassPolicy(); |
| Object value = getRealAttributeValueFromObject(object, session); |
| ContainerPolicy cp = this.containerPolicy; |
| for (Object iterator = cp.iteratorFor(value); cp.hasNext(iterator);) { |
| Object wrappedObject = cp.nextEntry(iterator, session); |
| Object nestedObject = cp.unwrapIteratorResult(wrappedObject); |
| if (hasInheritance && !nestedObject.getClass().equals(referenceDescriptor.getJavaClass())){ |
| ClassDescriptor concreteReferenceDescriptor = referenceDescriptor.getInheritancePolicy().getDescriptor(nestedObject.getClass()); |
| concreteReferenceDescriptor.getObjectBuilder().loadAll(nestedObject, session, loaded); |
| } else { |
| referenceDescriptor.getObjectBuilder().loadAll(nestedObject, session, loaded); |
| } |
| } |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * Return whether the reference objects must be deleted |
| * one by one, as opposed to with a single DELETE statement. |
| */ |
| public boolean mustDeleteReferenceObjectsOneByOne() { |
| return this.mustDeleteReferenceObjectsOneByOne == null || this.mustDeleteReferenceObjectsOneByOne; |
| } |
| |
| /** |
| * 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 changeset |
| */ |
| @Override |
| public void mergeChangesIntoObject(Object target, ChangeRecord chgRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (this.descriptor.getCachePolicy().isProtectedIsolation()&& !this.isCacheable && !targetSession.isProtectedSession()){ |
| setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder(null))); |
| return; |
| } |
| Object valueOfTarget = null; |
| Object valueOfSource = null; |
| ContainerPolicy containerPolicy = this.containerPolicy; |
| 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 { |
| if (isSynchronizeOnMerge) { |
| valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeManager.getSession()); |
| } else { |
| // Clone instead of synchronization to avoid possible deadlocks. |
| valueOfTarget = containerPolicy.cloneFor(getRealCollectionAttributeValueFromObject(target, mergeManager.getSession())); |
| } |
| } |
| |
| containerPolicy.mergeChanges(changeRecord, valueOfTarget, shouldMergeCascadeParts(mergeManager), mergeManager, targetSession, isSynchronizeOnMerge); |
| } else { |
| // The valueholder has not been instantiated |
| if (mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| return; // do nothing |
| } |
| // PERF: Also avoid merge if source has not been instantiated for indirect collection adds. |
| if (!isAttributeValueInstantiated(source)) { |
| return; |
| } |
| // 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)) { |
| // CR#2195 Problem with merging Collection mapping in unit of work and inheritance. |
| Object objectToMerge = containerPolicy.next(iterator, mergeManager.getSession()); |
| if (shouldMergeCascadeParts(mergeManager) && (valueOfSource != null)) { |
| ObjectChangeSet changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(objectToMerge); |
| mergeManager.mergeChanges(objectToMerge, changeSet, targetSession); |
| } |
| |
| // Let the mergemanager get it because I don't have the change for the object. |
| // CR#2188 Problem with merging Collection mapping in unit of work and transparent indirection. |
| containerPolicy.addInto(mergeManager.getTargetVersionOfSourceObject(objectToMerge, referenceDescriptor, targetSession), 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 |
| */ |
| @Override |
| public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (this.descriptor.getCachePolicy().isProtectedIsolation() && !this.isCacheable && !targetSession.isProtectedSession()){ |
| setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder(null))); |
| return; |
| } |
| 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, this.indirectionPolicy.getOriginalIndirectionObject(getAttributeValueFromObject(source), targetSession)); |
| return; |
| } |
| } |
| if (!shouldMergeCascadeReference(mergeManager)) { |
| // This is only going to happen on mergeClone, and we should not attempt to merge the reference |
| return; |
| } |
| if (mergeManager.shouldRefreshRemoteObject() && usesIndirection()) { |
| mergeRemoteValueHolder(target, source, mergeManager); |
| return; |
| } |
| if (mergeManager.isForRefresh()) { |
| if (!isAttributeValueInstantiated(target)) { |
| if(shouldRefreshCascadeParts(mergeManager)){ |
| // We must clone and set the value holder from the source to the target. |
| // This ensures any cascaded refresh will be applied to the UOW backup valueholder |
| Object attributeValue = getAttributeValueFromObject(source); |
| Object clonedAttributeValue = this.indirectionPolicy.cloneAttribute(attributeValue, source, null, target, null, mergeManager.getSession(), false); // building clone from an original not a row. |
| setAttributeValueInObject(target, clonedAttributeValue); |
| } |
| |
| // 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 (!isAttributeValueInstantiatedOrChanged(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 = this.containerPolicy; |
| // BUG#5190470 Must force instantiation of indirection collections. |
| containerPolicy.sizeFor(valueOfTarget); |
| boolean fireChangeEvents = false; |
| ObjectChangeListener listener = null; |
| Object valueOfSourceCloned = null; |
| if (!mergeManager.isForRefresh()) { |
| // EL Bug 338504 - No Need to clone in this case. |
| valueOfSourceCloned = valueOfSource; |
| // 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(valueOfSourceCloned)); |
| if ((this.descriptor.getObjectChangePolicy().isObjectChangeTrackingPolicy()) && (target instanceof ChangeTracker) && (((ChangeTracker)target)._persistence_getPropertyChangeListener() != null)) { |
| // Avoid triggering events if we are dealing with the same list. |
| // We rebuild the new container though since any cascade merge |
| // activity such as lifecycle methods etc will be captured on |
| // newly registered objects and not the clones and we need to |
| // make sure the target has these updates once we are done. |
| fireChangeEvents = (valueOfSourceCloned != valueOfTarget); |
| // Collections may not be indirect list or may have been replaced with user collection. |
| Object iterator = containerPolicy.iteratorFor(valueOfTarget); |
| listener = (ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener(); |
| if (fireChangeEvents) { |
| // Objects removed from the first position in the list, so the index of the removed object is always 0. |
| // When event is processed the index is used only in listOrderField case, ignored otherwise. |
| Integer zero = 0; |
| while (containerPolicy.hasNext(iterator)) { |
| CollectionChangeEvent event = containerPolicy.createChangeEvent(target, getAttributeName(), valueOfTarget, containerPolicy.next(iterator, mergeSession), CollectionChangeEvent.REMOVE, zero, false); |
| listener.internalPropertyChange(event); |
| } |
| } |
| if (newContainer instanceof ChangeTracker) { |
| ((CollectionChangeTracker)newContainer).setTrackedAttributeName(getAttributeName()); |
| ((CollectionChangeTracker)newContainer)._persistence_setPropertyChangeListener(listener); |
| } |
| if (valueOfTarget instanceof ChangeTracker) { |
| ((ChangeTracker)valueOfTarget)._persistence_setPropertyChangeListener(null);//remove listener |
| } |
| } |
| valueOfTarget = newContainer; |
| } else { |
| if (isSynchronizeOnMerge) { |
| // EL Bug 338504 - It needs to iterate on object which can possibly |
| // cause a deadlock scenario while merging changes from original |
| // to the working copy during rollback of the transaction. So, clone |
| // the original object instead of synchronizing on it and use cloned |
| // object to iterate and merge changes to the working copy. |
| synchronized(valueOfSource) { |
| valueOfSourceCloned = containerPolicy.cloneFor(valueOfSource); |
| } |
| } else { |
| valueOfSourceCloned = valueOfSource; |
| } |
| //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(valueOfSourceCloned))); |
| containerPolicy.clear(valueOfTarget); |
| } |
| |
| Object sourceIterator = containerPolicy.iteratorFor(valueOfSourceCloned); |
| // Index of the added object - objects are added to the end of the list. |
| // When event is processed the index is used only in listOrderField case, ignored otherwise. |
| int i = 0; |
| while (containerPolicy.hasNext(sourceIterator)) { |
| Object wrappedObject = containerPolicy.nextEntry(sourceIterator, mergeManager.getSession()); |
| Object object = containerPolicy.unwrapIteratorResult(wrappedObject); |
| if (object == null) { |
| continue;// skip the null |
| } |
| if (shouldMergeCascadeParts(mergeManager)) { |
| Object mergedObject = null; |
| 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 for this object |
| mergedObject = mergeManager.mergeChanges(mergeManager.getObjectToMerge(object, referenceDescriptor, targetSession), (ObjectChangeSet)((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object), targetSession); |
| if (listener != null && !fireChangeEvents && mergedObject != object){ |
| // we are merging a collection into itself that contained detached or new Entities. make sure to remove the |
| // old change records // bug 302293 |
| this.descriptor.getObjectChangePolicy().updateListenerForSelfMerge(listener, this, object, mergedObject, (UnitOfWorkImpl) mergeManager.getSession()); |
| } |
| } else { |
| mergedObject = mergeManager.mergeChanges(mergeManager.getObjectToMerge(object, referenceDescriptor, targetSession), null, targetSession); |
| } |
| } |
| wrappedObject = containerPolicy.createWrappedObjectFromExistingWrappedObject(wrappedObject, source, referenceDescriptor, mergeManager, targetSession); |
| if (isSynchronizeOnMerge) { |
| synchronized (valueOfTarget) { |
| if (fireChangeEvents) { |
| //Collections may not be indirect list or may have been replaced with user collection. |
| //bug 304251: let the ContainerPolicy decide what changeevent object to create |
| CollectionChangeEvent event = containerPolicy.createChangeEvent(target, getAttributeName(), valueOfTarget, wrappedObject, CollectionChangeEvent.ADD, i++, false); |
| listener.internalPropertyChange(event); |
| } |
| containerPolicy.addInto(wrappedObject, valueOfTarget, mergeManager.getSession()); |
| } |
| } else { |
| if (fireChangeEvents) { |
| //Collections may not be indirect list or may have been replaced with user collection. |
| //bug 304251: let the ContainerPolicy decide what changeevent object to create |
| CollectionChangeEvent event = containerPolicy.createChangeEvent(target, getAttributeName(), valueOfTarget, wrappedObject, CollectionChangeEvent.ADD, i++, false); |
| listener.internalPropertyChange(event); |
| } |
| containerPolicy.addInto(wrappedObject, valueOfTarget, mergeManager.getSession()); |
| } |
| } |
| if (fireChangeEvents && (this.descriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy())) { |
| // check that there were changes, if not then remove the record. |
| ObjectChangeSet changeSet = ((AttributeChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).getObjectChangeSet(); |
| //Bug4910642 Add NullPointer check |
| if (changeSet != null) { |
| CollectionChangeRecord changeRecord = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName()); |
| if (changeRecord != null) { |
| if (!changeRecord.isDeferred()) { |
| if (!changeRecord.hasChanges()) { |
| changeSet.removeChange(getAttributeName()); |
| } |
| } else { |
| // Must reset the latest collection. |
| changeRecord.setLatestCollection(valueOfTarget); |
| } |
| } |
| } |
| } |
| |
| // 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, Map extraData) 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 distinguish between insert and write (optimization/paraniod). |
| if (isPrivateOwned()) { |
| InsertObjectQuery insertQuery = new InsertObjectQuery(); |
| insertQuery.setIsExecutionClone(true); |
| insertQuery.setObject(containerPolicy.unwrapIteratorResult(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.setIsExecutionClone(true); |
| writeQuery.setObject(containerPolicy.unwrapIteratorResult(objectAdded)); |
| writeQuery.setObjectChangeSet(changeSet); |
| writeQuery.setCascadePolicy(query.getCascadePolicy()); |
| query.getSession().executeQuery(writeQuery); |
| } |
| } |
| |
| protected void objectOrderChangedDuringUpdate(WriteObjectQuery query, Object orderChangedObject, int orderIndex) { |
| prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getDescriptor(), query.getSession()); |
| AbstractRecord databaseRow = new DatabaseRecord(); |
| |
| // Extract target field and its value. Construct insert statement and execute it |
| List<DatabaseField> targetPrimaryKeyFields = getTargetPrimaryKeyFields(); |
| int size = targetPrimaryKeyFields.size(); |
| for (int index = 0; index < size; index++) { |
| DatabaseField targetPrimaryKey = targetPrimaryKeyFields.get(index); |
| Object targetKeyValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(orderChangedObject, targetPrimaryKey, query.getSession()); |
| databaseRow.put(targetPrimaryKey, targetKeyValue); |
| } |
| databaseRow.put(listOrderField, orderIndex); |
| |
| query.getSession().executeQuery(changeOrderTargetQuery, databaseRow); |
| } |
| |
| /** |
| * INTERNAL: |
| * An object was removed to the collection during an update, delete it if private. |
| */ |
| protected void objectRemovedDuringUpdate(ObjectLevelModifyQuery query, Object objectDeleted, Map extraData) throws DatabaseException, OptimisticLockException { |
| if (isPrivateOwned()) {// Must check ownership for uow and cascading. |
| if (!query.shouldCascadeOnlyDependentParts()) { |
| containerPolicy.deleteWrappedObject(objectDeleted, query.getSession()); |
| } |
| } |
| } |
| |
| /** |
| * 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.setIsExecutionClone(true); |
| writeQuery.setObject(object); |
| writeQuery.setCascadePolicy(query.getCascadePolicy()); |
| query.getSession().executeQuery(writeQuery); |
| } |
| |
| /** |
| * INTERNAL: |
| * Overridden by mappings that require additional processing of the change record after the record has been calculated. |
| */ |
| @Override |
| public void postCalculateChanges(org.eclipse.persistence.sessions.changesets.ChangeRecord changeRecord, UnitOfWorkImpl uow) { |
| // no need for private owned check. This code is only registered for private owned mappings. |
| // targets are added to and/or removed to/from the source. |
| CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)changeRecord; |
| Iterator<ObjectChangeSet> it = collectionChangeRecord.getRemoveObjectList().values().iterator(); |
| while(it.hasNext()) { |
| ObjectChangeSet ocs = it.next(); |
| containerPolicy.postCalculateChanges(ocs, referenceDescriptor, this, uow); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Overridden by mappings that require additional processing of the change record after the record has been calculated. |
| */ |
| @Override |
| public void recordPrivateOwnedRemovals(Object object, UnitOfWorkImpl uow) { |
| // no need for private owned check. This code is only registered for private owned mappings. |
| // targets are added to and/or removed to/from the source. |
| if (mustDeleteReferenceObjectsOneByOne()) { |
| Iterator it = (Iterator) containerPolicy.iteratorFor(getRealAttributeValueFromObject(object, uow)); |
| while (it.hasNext()) { |
| Object clone = it.next(); |
| containerPolicy.recordPrivateOwnedRemovals(clone, referenceDescriptor, uow); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Add additional fields |
| */ |
| @Override |
| protected void postPrepareNestedBatchQuery(ReadQuery batchQuery, ObjectLevelReadQuery query) { |
| super.postPrepareNestedBatchQuery(batchQuery, query); |
| ReadAllQuery mappingBatchQuery = (ReadAllQuery)batchQuery; |
| mappingBatchQuery.setShouldIncludeData(true); |
| this.containerPolicy.addAdditionalFieldsToQuery(mappingBatchQuery, getAdditionalFieldsBaseExpression(mappingBatchQuery)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the base expression to use for adding fields to the query. |
| * Normally this is the query's builder, but may be the join table for m-m. |
| */ |
| protected Expression getAdditionalFieldsBaseExpression(ReadQuery query) { |
| return ((ReadAllQuery)query).getExpressionBuilder(); |
| } |
| |
| /** |
| * INTERNAL: |
| * copies the non primary key information into the row currently used only in ManyToMany |
| */ |
| protected void prepareTranslationRow(AbstractRecord translationRow, Object object, ClassDescriptor descriptor, AbstractSession session) { |
| //Do nothing for the generic Collection Mapping |
| } |
| |
| /** |
| * INTERNAL: |
| * A subclass should implement this method if it wants different behavior. |
| * Recurse thru the parts to delete the reference objects after the actual object is deleted. |
| */ |
| @Override |
| public void postDelete(DeleteObjectQuery query) throws DatabaseException { |
| if (this.containerPolicy.propagatesEventsToCollection()){ |
| Object queryObject = query.getObject(); |
| Object values = getAttributeValueFromObject(queryObject); |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, query.getSession()); |
| containerPolicy.propogatePostDelete(query, wrappedObject); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Ensure the container policy is post initialized |
| */ |
| @Override |
| public void postInitialize(AbstractSession session) { |
| super.postInitialize(session); |
| this.containerPolicy.postInitialize(session); |
| if (this.referenceDescriptor != null && this.mustDeleteReferenceObjectsOneByOne == null) { |
| this.mustDeleteReferenceObjectsOneByOne = this.referenceDescriptor.hasDependencyOnParts() |
| || this.referenceDescriptor.usesOptimisticLocking() |
| || (this.referenceDescriptor.hasInheritance() && this.referenceDescriptor.getInheritancePolicy().shouldReadSubclasses()) |
| || this.referenceDescriptor.hasMultipleTables() || this.containerPolicy.propagatesEventsToCollection() |
| || this.referenceDescriptor.hasRelationshipsExceptBackpointer(descriptor); |
| } else if (this.mustDeleteReferenceObjectsOneByOne == null) { |
| this.mustDeleteReferenceObjectsOneByOne = false; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * A subclass should implement this method if it wants different behavior. |
| * Recurse thru the parts to delete the reference objects after the actual object is deleted. |
| */ |
| @Override |
| public void postInsert(WriteObjectQuery query) throws DatabaseException { |
| if (this.containerPolicy.propagatesEventsToCollection()){ |
| Object queryObject = query.getObject(); |
| Object values = getAttributeValueFromObject(queryObject); |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, query.getSession()); |
| containerPolicy.propogatePostInsert(query, wrappedObject); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Propagate preInsert event to container policy if necessary |
| */ |
| @Override |
| public void preInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException { |
| if (this.containerPolicy.propagatesEventsToCollection()){ |
| Object queryObject = query.getObject(); |
| Object values = getAttributeValueFromObject(queryObject); |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, query.getSession()); |
| containerPolicy.propogatePreInsert(query, wrappedObject); |
| } |
| } |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Propagate preUpdate event to container policy if necessary |
| */ |
| @Override |
| public void preUpdate(WriteObjectQuery query) throws DatabaseException { |
| if (this.containerPolicy.propagatesEventsToCollection()){ |
| Object queryObject = query.getObject(); |
| Object values = getAttributeValueFromObject(queryObject); |
| Object iterator = containerPolicy.iteratorFor(values); |
| while (containerPolicy.hasNext(iterator)){ |
| Object wrappedObject = containerPolicy.nextEntry(iterator, query.getSession()); |
| containerPolicy.propogatePreUpdate(query, wrappedObject); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * An object is still in the collection, update it as it may have changed. |
| */ |
| protected void objectUnchangedDuringUpdate(ObjectLevelModifyQuery query, Object object, Map backupclones, Object key) 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.getDescriptor(), modifyQuery.getSession()); |
| return modifyQuery.getSession().executeQuery(getSelectionQuery(), modifyQuery.getTranslationRow()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * replace the value holders in the specified reference object(s) |
| */ |
| @Override |
| public Map replaceValueHoldersIn(Object object, RemoteSessionController controller) { |
| return controller.replaceValueHoldersInAll(object, this.containerPolicy); |
| } |
| |
| /** |
| * ADVANCED: |
| * Configure the mapping to use a container policy. |
| * The policy manages the access to the collection. |
| */ |
| @Override |
| 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; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the container policy on the selection query for this mapping. This |
| * method is overridden in DirectCollectionMapping since its selection |
| * query is a DataReadQuery. |
| */ |
| protected void setSelectionQueryContainerPolicy(ContainerPolicy containerPolicy) { |
| ((ReadAllQuery) getSelectionQuery()).setContainerPolicy(containerPolicy); |
| } |
| |
| /** |
| * 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: |
| * Calling this method will only affect behavior of mappings using transparent indirection |
| * This method should only be called after this mapping's indirection policy has been set |
| * |
| * IndirectList and IndirectSet can be configured not to instantiate the list from the |
| * database when you add and remove from them. IndirectList defaults to this behavior. When |
| * Set to true, the collection associated with this TransparentIndirection will be setup so as |
| * not to instantiate for adds and removes. The weakness of this setting for an IndirectSet is |
| * that when the set is not instantiated, if a duplicate element is added, it will not be |
| * detected until commit time. |
| */ |
| public void setUseLazyInstantiationForIndirectCollection(Boolean useLazyInstantiation) { |
| if (getIndirectionPolicy() != null){ |
| getIndirectionPolicy().setUseLazyInstantiation(useLazyInstantiation); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| @Override |
| 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); |
| changeSet.addChange(collectionChangeRecord); |
| } |
| this.containerPolicy.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. |
| */ |
| @Override |
| 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); |
| changeSet.addChange(collectionChangeRecord); |
| } |
| this.containerPolicy.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. |
| */ |
| @Override |
| 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); |
| } |
| // the order is essential - the record should be set to deferred before recreateOriginalCollection is called - |
| // otherwise will keep altering the change record while adding/removing each element into/from the original collection. |
| collectionChangeRecord.setIsDeferred(true); |
| objectChangeSet.deferredDetectionRequiredOn(getAttributeName()); |
| if (collectionChangeRecord.getOriginalCollection() == null) { |
| collectionChangeRecord.recreateOriginalCollection(oldValue, uow); |
| } |
| collectionChangeRecord.setLatestCollection(newValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Update a ChangeRecord to replace the ChangeSet for the old entity with the changeSet for the new Entity. This is |
| * used when an Entity is merged into itself and the Entity reference new or detached entities. |
| */ |
| @Override |
| public void updateChangeRecordForSelfMerge(ChangeRecord changeRecord, Object source, Object target, UnitOfWorkChangeSet parentUOWChangeSet, UnitOfWorkImpl unitOfWork){ |
| getContainerPolicy().updateChangeRecordForSelfMerge(changeRecord, source, target, this, parentUOWChangeSet, unitOfWork); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add or removes a new value and its change set to the collection change record based on the event passed in. This is used by |
| * attribute change tracking. |
| */ |
| @Override |
| public void updateCollectionChangeRecord(CollectionChangeEvent event, ObjectChangeSet changeSet, UnitOfWorkImpl uow) { |
| if (event !=null && event.getNewValue() != null) { |
| Object newValue = event.getNewValue(); |
| ClassDescriptor descriptor; |
| |
| //PERF: Use referenceDescriptor if it does not have inheritance |
| if (!getReferenceDescriptor().hasInheritance() && !getReferenceDescriptor().hasTablePerClassPolicy()) { |
| descriptor = getReferenceDescriptor(); |
| } else { |
| descriptor = uow.getDescriptor(newValue); |
| } |
| newValue = descriptor.getObjectBuilder().unwrapObject(newValue, uow); |
| ObjectChangeSet changeSetToAdd = descriptor.getObjectBuilder().createObjectChangeSet(newValue, (UnitOfWorkChangeSet)changeSet.getUOWChangeSet(), uow); |
| |
| CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| if (collectionChangeRecord == null) { |
| collectionChangeRecord = new CollectionChangeRecord(changeSet); |
| collectionChangeRecord.setAttribute(getAttributeName()); |
| collectionChangeRecord.setMapping(this); |
| changeSet.addChange(collectionChangeRecord); |
| } |
| if(!collectionChangeRecord.isDeferred()) { |
| this.containerPolicy.recordUpdateToCollectionInChangeRecord(event, changeSetToAdd, collectionChangeRecord); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the change listener in the collection. |
| * If the collection is not indirect it must be re-built. |
| * This is used for resuming or flushing units of work. |
| */ |
| @Override |
| public void setChangeListener(Object clone, PropertyChangeListener listener, UnitOfWorkImpl uow) { |
| if (this.indirectionPolicy.usesTransparentIndirection() && isAttributeValueInstantiated(clone)) { |
| Object attributeValue = getRealAttributeValueFromObject(clone, uow); |
| if (!(attributeValue instanceof CollectionChangeTracker)) { |
| Object container = attributeValue; |
| ContainerPolicy containerPolicy = this.containerPolicy; |
| if (attributeValue == null) { |
| container = containerPolicy.containerInstance(1); |
| } else { |
| container = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue)); |
| for (Object iterator = containerPolicy.iteratorFor(attributeValue); containerPolicy.hasNext(iterator);) { |
| containerPolicy.addInto(containerPolicy.nextEntry(iterator, uow), container, uow); |
| } |
| } |
| setRealAttributeValueInObject(clone, container); |
| ((CollectionChangeTracker)container).setTrackedAttributeName(getAttributeName()); |
| ((CollectionChangeTracker)container)._persistence_setPropertyChangeListener(listener); |
| } else { |
| ((CollectionChangeTracker)attributeValue).setTrackedAttributeName(getAttributeName()); |
| ((CollectionChangeTracker)attributeValue)._persistence_setPropertyChangeListener(listener); |
| } |
| } |
| if (this.indirectionPolicy.usesTransparentIndirection()){ |
| ((IndirectCollection)getRealAttributeValueFromObject(clone, uow)).clearDeferredChanges(); |
| } |
| |
| } |
| |
| /** |
| * PUBLIC: |
| * indicates whether the mapping supports listOrderField, if it doesn't attempt to set listOrderField throws exception. |
| */ |
| public boolean isListOrderFieldSupported() { |
| return isListOrderFieldSupported; |
| } |
| |
| /** |
| * PUBLIC: |
| * Field holds the order of elements in the list in the db, requires collection of type List. |
| * Throws exception if the mapping doesn't support listOrderField. |
| */ |
| public void setListOrderField(DatabaseField field) { |
| if(field != null) { |
| if(isListOrderFieldSupported) { |
| this.listOrderField = field; |
| } else { |
| throw ValidationException.listOrderFieldNotSupported(this); |
| } |
| } else { |
| this.listOrderField = null; |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Field holds the order of elements in the list in the db, requires collection of type List. |
| * Throws exception if the mapping doesn't support listOrderField. |
| */ |
| public void setListOrderFieldName(String fieldName) { |
| setListOrderField(new DatabaseField(fieldName)); |
| } |
| |
| |
| /** |
| * ADVANCED:: |
| * Return whether the reference objects must be deleted |
| * one by one, as opposed to with a single DELETE statement. |
| * Note: Calling this method disables an optimization of the delete |
| * behavior |
| */ |
| public void setMustDeleteReferenceObjectsOneByOne(Boolean deleteOneByOne) { |
| this.mustDeleteReferenceObjectsOneByOne = deleteOneByOne; |
| } |
| |
| /** |
| * PUBLIC: |
| * Specifies what should be done if the list of values read from listOrserField is invalid |
| * (there should be no nulls, no duplicates, no "holes"). |
| */ |
| public void setOrderCorrectionType(OrderCorrectionType orderCorrectionType) { |
| this.orderCorrectionType = orderCorrectionType; |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the mapping to use an instance of the specified container class |
| * to hold the target objects. |
| * Note that if listOrderField is used then setListOrderField method |
| * should be called before this method. |
| * <p>The container class must implement (directly or indirectly) the |
| * <code>java.util.Collection</code> interface. |
| */ |
| @Override |
| public void useCollectionClass(Class concreteClass) { |
| ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass, hasOrderBy() || listOrderField != null); |
| setContainerPolicy(policy); |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the mapping to use an instance of the specified container class |
| * to hold the target objects. |
| * <p>The container class must implement (directly or indirectly) the |
| * <code>java.util.SortedSet</code> 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); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Configure the mapping to use an instance of the specified container class name |
| * to hold the target objects. This method is used by MW. |
| * <p>The container class must implement (directly or indirectly) the |
| * <code>java.util.SortedSet</code> interface. |
| */ |
| public void useSortedSetClassName(String className) { |
| this.useSortedSetClassName(className, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Configure the mapping to use an instance of the specified container class name |
| * to hold the target objects. This method is used by MW. |
| * <p>The container class must implement (directly or indirectly) the |
| * <code>java.util.SortedSet</code> interface. |
| */ |
| public void useSortedSetClassName(String className, String comparatorClassName) { |
| SortedCollectionContainerPolicy policy = new SortedCollectionContainerPolicy(className); |
| policy.setComparatorClassName(comparatorClassName); |
| setContainerPolicy(policy); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to set the collection class by name. |
| * This is required when building from metadata to allow the correct class loader to be used. |
| */ |
| @Override |
| public void useCollectionClassName(String concreteClassName) { |
| setContainerPolicy(new CollectionContainerPolicy(concreteClassName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to set the collection class by name. |
| * This is required when building from metadata to allow the correct class loader to be used. |
| */ |
| @Override |
| public void useListClassName(String concreteClassName) { |
| setContainerPolicy(new ListContainerPolicy(concreteClassName)); |
| } |
| |
| /** |
| * 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 |
| * <code>Map</code> is the value returned by a call to the specified |
| * zero-argument method. |
| * The method must be implemented by the class (or a superclass) of any |
| * value to be inserted into the <code>Map</code>. |
| * <p>The container class must implement (directly or indirectly) the |
| * <code>java.util.Map</code> interface. |
| * <p>To facilitate resolving the method, the mapping's referenceClass |
| * must set before calling this method. |
| */ |
| @Override |
| 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 |
| * <code>Map</code> is an instance of the composite primary key class. |
| * <p> To facilitate resolving the primary key class, the mapping's |
| * referenceClass must set before calling this method. |
| * <p> The container class must implement (directly or indirectly) the |
| * <code>java.util.Map</code> interface. |
| */ |
| public void useMapClass(Class concreteClass) { |
| useMapClass(concreteClass, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Not sure were this is used, MW? |
| */ |
| @Override |
| public void useMapClassName(String concreteClassName, String methodName) { |
| // the reference class has to be specified before coming here |
| if (getReferenceClassName() == null) { |
| throw DescriptorException.referenceClassNotSpecified(this); |
| } |
| MapContainerPolicy policy = new MapContainerPolicy(concreteClassName); |
| policy.setKeyName(methodName, getReferenceClass().getName()); |
| |
| setContainerPolicy(policy); |
| } |
| |
| /** |
| * 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); |
| setSelectionQueryContainerPolicy(ContainerPolicy.buildPolicyFor(HashSet.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); |
| setSelectionQueryContainerPolicy(ContainerPolicy.buildPolicyFor(Vector.class, hasOrderBy() || listOrderField != null)); |
| } |
| |
| /** |
| * 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 Map or Hashtable) to |
| * ValueHolderInterface.<p> |
| * 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); |
| ContainerPolicy policy = ContainerPolicy.buildPolicyFor(Hashtable.class); |
| policy.setKeyName(methodName, getReferenceClass()); |
| setSelectionQueryContainerPolicy(policy); |
| } |
| |
| /** |
| * INTERNAL: |
| * To validate mappings declaration |
| */ |
| @Override |
| public void validateBeforeInitialization(AbstractSession session) throws DescriptorException { |
| super.validateBeforeInitialization(session); |
| |
| this.indirectionPolicy.validateContainerPolicy(session.getIntegrityChecker()); |
| |
| if (getAttributeAccessor() instanceof InstanceVariableAttributeAccessor) { |
| Class attributeType = ((InstanceVariableAttributeAccessor)getAttributeAccessor()).getAttributeType(); |
| this.indirectionPolicy.validateDeclaredAttributeTypeForCollection(attributeType, session.getIntegrityChecker()); |
| } else if (getAttributeAccessor().isMethodAttributeAccessor()) { |
| // 323403 |
| Class returnType = ((MethodAttributeAccessor)getAttributeAccessor()).getGetMethodReturnType(); |
| this.indirectionPolicy.validateGetMethodReturnTypeForCollection(returnType, session.getIntegrityChecker()); |
| |
| Class parameterType = ((MethodAttributeAccessor)getAttributeAccessor()).getSetMethodParameterType(); |
| this.indirectionPolicy.validateSetMethodParameterTypeForCollection(parameterType, session.getIntegrityChecker()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Checks if object is deleted from the database or not. |
| */ |
| @Override |
| public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException { |
| // Row is built for translation |
| if (isReadOnly()) { |
| return true; |
| } |
| |
| if (isPrivateOwned() || isCascadeRemove()) { |
| Object objects = getRealCollectionAttributeValueFromObject(object, session); |
| |
| ContainerPolicy containerPolicy = this.containerPolicy; |
| 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, getDescriptor(), session); |
| Object value = session.executeQuery(getSelectionQuery(), row); |
| |
| return this.containerPolicy.isEmpty(value); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this mapping supports change tracking. |
| */ |
| @Override |
| public boolean isChangeTrackingSupported(Project project) { |
| return this.indirectionPolicy.usesTransparentIndirection(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Directly build a change record without comparison |
| */ |
| @Override |
| public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) { |
| Object cloneAttribute = null; |
| cloneAttribute = getAttributeValueFromObject(clone); |
| if ((cloneAttribute != null) && (!this.indirectionPolicy.objectIsInstantiated(cloneAttribute))) { |
| return null; |
| } |
| |
| // 2612538 - the default size of Map (32) is appropriate |
| IdentityHashMap cloneKeyValues = new IdentityHashMap(); |
| ContainerPolicy cp = this.containerPolicy; |
| 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, cp, (UnitOfWorkChangeSet)owner.getUOWChangeSet(), session); |
| if (changeRecord.hasChanges()) { |
| return changeRecord; |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to load a relationship from a list of PKs. This list |
| * may be available if the relationship has been cached. |
| */ |
| @Override |
| public Object valueFromPKList(Object[] pks, AbstractRecord foreignKeys, AbstractSession session){ |
| ContainerPolicy cp = this.containerPolicy; |
| return cp.valueFromPKList(pks, foreignKeys, this, session); |
| } |
| |
| /** |
| * 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. |
| */ |
| @Override |
| protected Object valueFromRowInternalWithJoin(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey parentCacheKey, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException { |
| |
| Object value = this.containerPolicy.containerInstance(); |
| // Extract the primary key of the source object, to filter only the joined rows for that object. |
| Object sourceKey = this.descriptor.getObjectBuilder().extractPrimaryKeyFromRow(row, executionSession); |
| // If the query was using joining, all of the result rows by primary key will have been computed. |
| List<AbstractRecord> rows = joinManager.getDataResultsByPrimaryKey().get(sourceKey); |
| // If no 1-m rows were fetch joined, then get the value normally, |
| // this can occur with pagination where the last row may not be complete. |
| if (rows == null) { |
| return valueFromRowInternal(row, joinManager, sourceQuery, executionSession); |
| } |
| int size = rows.size(); |
| if (size > 0) { |
| // 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 = prepareNestedJoinQueryClone(row, rows, joinManager, sourceQuery, executionSession); |
| |
| // A set of target cache keys must be maintained to avoid duplicates from multiple 1-m joins. |
| Set targetPrimaryKeys = new HashSet(); |
| |
| ArrayList targetObjects = null; |
| ArrayList<AbstractRecord> targetRows = null; |
| boolean shouldAddAll = this.containerPolicy.shouldAddAll(); |
| if (shouldAddAll) { |
| targetObjects = new ArrayList(size); |
| targetRows = new ArrayList(size); |
| } |
| |
| // For each rows, extract the target row and build the target object and add to the collection. |
| ObjectBuilder referenceBuilder = getReferenceDescriptor().getObjectBuilder(); |
| JoinedAttributeManager referenceJoinManager = null; |
| if (nestedQuery.hasJoining()) { |
| referenceJoinManager = nestedQuery.getJoinedAttributeManager(); |
| } |
| for (int index = 0; index < size; index++) { |
| AbstractRecord sourceRow = rows.get(index); |
| AbstractRecord targetRow = sourceRow; |
| // 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); |
| |
| // 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. |
| Object targetKey = referenceBuilder.extractPrimaryKeyFromRow(targetRow, executionSession); |
| if (targetKey == null) { |
| // A null primary key means an empty collection returned as nulls from an outerjoin. |
| return this.indirectionPolicy.valueFromRow(value); |
| } |
| |
| // Only build/add the target object once, skip duplicates from multiple 1-m joins. |
| if (!targetPrimaryKeys.contains(targetKey)) { |
| nestedQuery.setTranslationRow(targetRow); |
| targetPrimaryKeys.add(targetKey); |
| Object targetObject = referenceBuilder.buildObject(nestedQuery, targetRow, referenceJoinManager); |
| Object targetMapKey = this.containerPolicy.buildKeyFromJoinedRow(targetRow, joinManager, nestedQuery, parentCacheKey, executionSession, isTargetProtected); |
| nestedQuery.setTranslationRow(null); |
| if (targetMapKey == null){ |
| if (shouldAddAll) { |
| targetObjects.add(targetObject); |
| targetRows.add(targetRow); |
| } else { |
| this.containerPolicy.addInto(targetObject, value, executionSession); |
| } |
| } else { |
| this.containerPolicy.addInto(targetMapKey, targetObject, value, executionSession); |
| } |
| } |
| } |
| if (shouldAddAll) { |
| this.containerPolicy.addAll(targetObjects, value, executionSession, targetRows, nestedQuery, parentCacheKey, isTargetProtected); |
| } |
| } |
| return this.indirectionPolicy.valueFromRow(value); |
| } |
| } |