| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 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 |
| // 08/15/2008-1.0.1 Chris Delahunt |
| // - 237545: List attribute types on OneToMany using @OrderBy does not work with attribute change tracking |
| package org.eclipse.persistence.internal.queries; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.ListIterator; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.expressions.SQLSelectStatement; |
| import org.eclipse.persistence.internal.helper.ConversionManager; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.DatabaseTable; |
| import org.eclipse.persistence.internal.helper.IndexedObject; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.ChangeRecord; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.OrderedChangeObject; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet; |
| import org.eclipse.persistence.internal.sessions.CollectionChangeRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.annotations.OrderCorrectionType; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent; |
| import org.eclipse.persistence.indirection.IndirectCollection; |
| import org.eclipse.persistence.indirection.IndirectList; |
| import org.eclipse.persistence.mappings.CollectionMapping; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| import org.eclipse.persistence.queries.DataReadQuery; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadQuery; |
| |
| /** |
| * <p><b>Purpose</b>: A OrderedListContainerPolicy is ContainerPolicy whose |
| * container class implements the List interface and is ordered by an @OrderBy. |
| * </p> |
| * <p><b>Responsibilities</b>: |
| * Provide the functionality to operate on an instance of a List.</p> |
| * |
| * @see ContainerPolicy |
| * @see CollectionContainerPolicy |
| * @see ListContainerPolicy |
| */ |
| public class OrderedListContainerPolicy extends ListContainerPolicy { |
| protected static final String NOT_SET = "NOT_SET"; |
| protected DatabaseField listOrderField; |
| protected OrderCorrectionType orderCorrectionType; |
| |
| /** |
| * INTERNAL: |
| * Construct a new policy. |
| */ |
| public OrderedListContainerPolicy() { |
| super(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Construct a new policy for the specified class. |
| */ |
| public OrderedListContainerPolicy(Class<?> containerClass) { |
| super(containerClass); |
| } |
| |
| /** |
| * INTERNAL: |
| * Construct a new policy for the specified class name. |
| */ |
| public OrderedListContainerPolicy(String containerClassName) { |
| super(containerClassName); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add a list of elements to container. |
| * This is used to add to a collection independent of JDK 1.1 and 1.2. |
| * The session may be required to wrap for the wrapper policy. |
| * The row may be required by subclasses |
| * Return whether the container changed |
| */ |
| @Override |
| public boolean addAll(List elements, Object container, AbstractSession session, List<AbstractRecord> dbRows, ObjectBuildingQuery query, CacheKey parentCacheKey, boolean isTargetProtected) { |
| if(this.listOrderField == null) { |
| return super.addAll(elements, container, session, dbRows, query, parentCacheKey, isTargetProtected); |
| } else { |
| return addAll(elements, container, session, dbRows, query, parentCacheKey); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Add a list of elements to container. |
| * This is used to add to a collection independent of JDK 1.1 and 1.2. |
| * The session may be required to wrap for the wrapper policy. |
| * The row may be required by subclasses |
| * Return whether the container changed |
| */ |
| @Override |
| public boolean addAll(List elements, Object container, AbstractSession session, List<AbstractRecord> dbRows, DataReadQuery query, CacheKey parentCacheKey, boolean isTargetProtected) { |
| if(this.listOrderField == null) { |
| return super.addAll(elements, container, session, dbRows, query, parentCacheKey, isTargetProtected); |
| } else { |
| return addAll(elements, container, session, dbRows, query, parentCacheKey); |
| } |
| } |
| |
| protected boolean addAll(List elements, Object container, AbstractSession session, List<AbstractRecord> dbRows, ReadQuery query, CacheKey parentCacheKey) { |
| int size = dbRows.size(); |
| |
| if(this.elementDescriptor != null && this.elementDescriptor.getObjectBuilder().hasWrapperPolicy()) { |
| ObjectBuilder objectBuilder = this.elementDescriptor.getObjectBuilder(); |
| List wrappedElements = new ArrayList(size); |
| for(int i=0; i < size; i++) { |
| wrappedElements.add(objectBuilder.wrapObject(elements.get(i), session)); |
| } |
| elements = wrappedElements; |
| } |
| |
| ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager(); |
| |
| // populate container with a dummy so the container.set(i, obj) could be used later. |
| for(int i=0; i < size; i++) { |
| ((List)container).add(NOT_SET); |
| } |
| // insert the elements into container |
| boolean failed = false; |
| for(int i=0; i < size; i++) { |
| AbstractRecord row = dbRows.get(i); |
| Object orderValue = row.get(this.listOrderField); |
| // order value is null |
| if(orderValue == null) { |
| failed = true; |
| break; |
| } |
| int intOrderValue = conversionManager.convertObject(orderValue, Integer.class); |
| try { |
| // one or more elements have the same order value |
| if(NOT_SET != ((List)container).set(intOrderValue, elements.get(i))) { |
| failed = true; |
| break; |
| } |
| } catch(IndexOutOfBoundsException indexException) { |
| // order value negative or greater/equal to size |
| failed = true; |
| break; |
| } |
| } |
| |
| if(failed) { |
| ((List)container).clear(); |
| |
| // extract order list - it will be set into exception or used by order correction. |
| List<Integer> orderList = new ArrayList(size); |
| for(int i=0; i < size; i++) { |
| AbstractRecord row = dbRows.get(i); |
| Object orderValue = row.get(this.listOrderField); |
| if(orderValue == null) { |
| orderList.add(null); |
| } else { |
| orderList.add(conversionManager.convertObject(orderValue, Integer.class)); |
| } |
| } |
| |
| if(this.orderCorrectionType == OrderCorrectionType.READ || this.orderCorrectionType == OrderCorrectionType.READ_WRITE) { |
| // pair each element with its order index |
| List<IndexedObject> indexedElements = new ArrayList(size); |
| for(int i=0; i < size; i++) { |
| indexedElements.add(new IndexedObject(orderList.get(i), elements.get(i))); |
| } |
| |
| // put elements in order and add to container |
| ((List)container).addAll(correctOrderList(indexedElements)); |
| |
| if(this.orderCorrectionType == OrderCorrectionType.READ_WRITE) { |
| // mark IndirectList to have broken order |
| ((IndirectList)container).setIsListOrderBrokenInDb(true); |
| } |
| } else { |
| // this.orderCorrectionType == OrderCorrectionType.EXCEPTION |
| throw QueryException.listOrderFieldWrongValue(query, this.listOrderField, orderList); |
| } |
| } |
| return size > 0; |
| } |
| |
| /** |
| * INTERNAL: |
| * Add element into a container which implements the List interface. |
| * Add at a particular index. |
| */ |
| protected void addIntoAtIndex(Integer index, Object object, Object container, AbstractSession session) { |
| if (hasElementDescriptor()) { |
| object = getElementDescriptor().getObjectBuilder().wrapObject(object, session); |
| } |
| |
| try { |
| if (index == null || (index > sizeFor(container))) { |
| // The index can be larger than the size on a merge, |
| // so should be added to the end, it may also be null if the |
| // index was unknown, such as an event using the add API. |
| ((List)container).add(object); |
| } else { |
| ((List)container).add(index, object); |
| } |
| } catch (ClassCastException ex1) { |
| throw QueryException.cannotAddElement(object, container, ex1); |
| } catch (IllegalArgumentException ex2) { |
| throw QueryException.cannotAddElement(object, container, ex2); |
| } catch (UnsupportedOperationException ex3) { |
| throw QueryException.cannotAddElement(object, container, ex3); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to calculate the differences between two collections. |
| * This algorithm is a work in progress. It works great and all, but like |
| * anything, you can always make it better. |
| */ |
| @Override |
| public void compareCollectionsForChange(Object oldList, Object newList, CollectionChangeRecord changeRecord, AbstractSession session, ClassDescriptor referenceDescriptor) { |
| List orderedObjectsToAdd = new ArrayList(); |
| Map indicesToRemove = new HashMap(); |
| Map oldListIndexValue = new HashMap(); |
| IdentityHashMap oldListValueIndex = new IdentityHashMap(); |
| IdentityHashMap objectsToAdd = new IdentityHashMap(); |
| Map<Object, Integer> newListValueIndex = new IdentityHashMap(); |
| |
| // Step 1 - Go through the old list. |
| if (oldList != null) { |
| ListIterator iterator = (ListIterator)iteratorFor(oldList); |
| |
| while (iterator.hasNext()) { |
| Integer index = iterator.nextIndex(); |
| Object value = iterator.next(); |
| oldListValueIndex.put(value, index); |
| oldListIndexValue.put(index, value); |
| indicesToRemove.put(index, index); |
| } |
| } |
| |
| // Step 2 - Go though the new list. |
| if (newList != null) { |
| // Step i - Gather the list info. |
| ListIterator iterator = (ListIterator)iteratorFor(newList); |
| while (iterator.hasNext()) { |
| newListValueIndex.put(iterator.next(), iterator.previousIndex()); |
| } |
| |
| // Step ii - Go through the new list again. |
| int index = 0; |
| int offset = 0; |
| iterator = (ListIterator)iteratorFor(newList); |
| while (iterator.hasNext()) { |
| index = iterator.nextIndex(); |
| Object currentObject = iterator.next(); |
| |
| // If value is null then nothing can be done with it. |
| if (currentObject != null) { |
| if (oldListValueIndex.containsKey(currentObject)) { |
| int oldIndex = (Integer) oldListValueIndex.get(currentObject); |
| oldListValueIndex.remove(currentObject); |
| |
| if (index == oldIndex) { |
| indicesToRemove.remove(oldIndex); |
| offset = 0; // Reset the offset, assume we're back on track. |
| } else if (index == (oldIndex + offset)) { |
| // We're in the right spot according to the offset. |
| indicesToRemove.remove(oldIndex); |
| } else { |
| // Time to be clever and figure out why we're not in the right spot! |
| int movedObjects = 0; |
| int deletedObjects = 0; |
| boolean moved = true; |
| |
| if (oldIndex < index) { |
| ++offset; |
| } else { |
| for (int i = oldIndex - 1; i >= index; i--) { |
| Object oldObject = oldListIndexValue.get(i); |
| if (newListValueIndex.containsKey(oldObject)) { |
| ++movedObjects; |
| } else { |
| ++deletedObjects; |
| } |
| } |
| |
| if (index == ((oldIndex + offset) - deletedObjects)) { |
| // We fell into place because of deleted objects. |
| offset = offset - deletedObjects; |
| moved = false; |
| } else if (movedObjects > 1) { |
| // Assume we moved down, bumping everyone by one. |
| ++offset; |
| } else { |
| // Assume we moved down unless the object that was |
| // here before is directly beside us. |
| Object oldObject = oldListIndexValue.get(index); |
| |
| if (newListValueIndex.containsKey(oldObject)) { |
| if ((newListValueIndex.get(oldObject) - index) > 1) { |
| moved = false; // Assume the old object moved up. |
| --offset; |
| } |
| } |
| } |
| } |
| |
| if (moved) { |
| // Add ourselves to the ordered add list. |
| orderedObjectsToAdd.add(currentObject); |
| } else { |
| // Take us off the removed list. |
| indicesToRemove.remove(oldIndex); |
| } |
| } |
| } else { |
| ++offset; |
| objectsToAdd.put(currentObject, currentObject); |
| orderedObjectsToAdd.add(currentObject); |
| } |
| } else { |
| // If we find nulls we need decrease our offset. |
| offset--; |
| } |
| } |
| } |
| |
| // Sort the remove indices that are left and set the data on the collection change |
| // record to be processed on the merge. |
| List orderedIndicesToRemove = new ArrayList(indicesToRemove.values()); |
| Collections.sort(orderedIndicesToRemove); |
| changeRecord.addAdditionChange(objectsToAdd, this, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session); |
| changeRecord.addRemoveChange(oldListValueIndex, this, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session); |
| changeRecord.addOrderedAdditionChange(orderedObjectsToAdd, newListValueIndex, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session); |
| changeRecord.addOrderedRemoveChange(orderedIndicesToRemove, oldListIndexValue, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session); |
| } |
| |
| /** |
| * PUBLIC: |
| * Correct object's order in the list. |
| * The method is called only in one case - when the list of order indexes read from db is invalid |
| * (contains nulls, duplicates, negative values, values greater/equal list size). |
| * Each element of the indexedObjects is a pair of the order index and the corresponding object. |
| * The goal of the method is to return back the list of objects (not indexed objects!) in the correct order. |
| * The objects should not be altered. |
| * |
| * The default implementation of the method sorts indexedObjects according to their indexes (null less than any non-null). |
| * For example: |
| * indexedObjects = {{2, objectA}, {5, ObjectB}} returns {objectA, objectB}; |
| * indexedObjects = {{2, objectA}, {-1, ObjectB}} returns {objectB, objectA}; |
| * indexedObjects = {{2, objectA}, {null, ObjectB}} returns {objectB, objectA}; |
| * |
| * This method could be overridden by the user. |
| */ |
| public List correctOrderList(List<IndexedObject> indexedObjects) { |
| Collections.sort(indexedObjects); |
| int size = indexedObjects.size(); |
| List objects = new ArrayList(size); |
| for(int i=0; i < size; i++) { |
| objects.add(indexedObjects.get(i).getObject()); |
| } |
| return objects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to create an iterator on a the Map object passed to CollectionChangeRecord.addRemoveChange() |
| * to access the values to be removed. In the case of some container policies the values will actually |
| * be the keys. |
| */ |
| @Override |
| public Iterator getChangeValuesFrom(Map map) { |
| return map.keySet().iterator(); |
| } |
| |
| public DatabaseField getListOrderField() { |
| return this.listOrderField; |
| } |
| |
| public void setListOrderField(DatabaseField field) { |
| this.listOrderField = field; |
| } |
| |
| public OrderCorrectionType getOrderCorrectionType() { |
| return this.orderCorrectionType; |
| } |
| |
| public void setOrderCorrectionType(OrderCorrectionType orderCorrectionType) { |
| if(this.orderCorrectionType == orderCorrectionType) { |
| return; |
| } |
| if(orderCorrectionType == OrderCorrectionType.READ_WRITE) { |
| if(getContainerClass() == null || !IndirectList.class.isAssignableFrom(getContainerClass())) { |
| setContainerClass(IndirectList.class); |
| } |
| } |
| this.orderCorrectionType = orderCorrectionType; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return an list iterator for the given container. |
| */ |
| @Override |
| public Object iteratorFor(Object container) { |
| return ((List)container).listIterator(); |
| } |
| |
| @Override |
| public boolean isOrderedListPolicy() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. Because this is a |
| * collection mapping, values are added to or removed from the collection |
| * based on the change set. |
| * Synchronize if system property is specified. If not, default to clone the |
| * target collection. No need to synchronize if the collection is new. |
| */ |
| @Override |
| public void mergeChanges(CollectionChangeRecord changeRecord, Object valueOfTarget, boolean shouldMergeCascadeParts, MergeManager mergeManager, AbstractSession targetSession, boolean isSynchronizeOnMerge) { |
| if (isSynchronizeOnMerge && !changeRecord.getOwner().isNew()) { |
| // Ensure the collection is synchronized while changes are being made, |
| // clone also synchronizes on collection (does not have cache key read-lock for indirection). |
| // Must synchronize of the real collection as the clone does so. |
| Object synchronizedValueOfTarget = valueOfTarget; |
| if (valueOfTarget instanceof IndirectCollection) { |
| synchronizedValueOfTarget = ((IndirectCollection)valueOfTarget).getDelegateObject(); |
| } |
| synchronized(synchronizedValueOfTarget) { |
| mergeChanges(changeRecord, valueOfTarget, shouldMergeCascadeParts, mergeManager, targetSession); |
| } |
| } else { |
| mergeChanges(changeRecord, valueOfTarget, shouldMergeCascadeParts, mergeManager, targetSession); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. Because this is a |
| * collection mapping, values are added to or removed from the collection |
| * based on the change set. |
| */ |
| @Override |
| protected void mergeChanges(CollectionChangeRecord changeRecord, Object valueOfTarget, boolean shouldMergeCascadeParts, MergeManager mergeManager, AbstractSession targetSession) { |
| ObjectChangeSet objectChanges; |
| if(changeRecord.orderHasBeenRepaired() && valueOfTarget instanceof IndirectList) { |
| ((IndirectList)valueOfTarget).setIsListOrderBrokenInDb(false); |
| } |
| if (!changeRecord.getOrderedChangeObjectList().isEmpty()) { |
| Iterator<OrderedChangeObject> objects =changeRecord.getOrderedChangeObjectList().iterator(); |
| while (objects.hasNext()){ |
| OrderedChangeObject changeObject = objects.next(); |
| objectChanges = changeObject.getChangeSet(); |
| if (changeObject.getChangeType() == CollectionChangeEvent.REMOVE){ |
| boolean objectRemoved = changeRecord.getRemoveObjectList().containsKey(objectChanges); |
| Object objectToRemove = objectChanges.getTargetVersionOfSourceObject(mergeManager, targetSession); |
| |
| //if objectToRemove is null, we can't look it up in the collection. |
| // This should not happen unless identity is lost. |
| if (objectToRemove != null) { |
| Integer index = changeObject.getIndex(); |
| if (index!=null){ |
| if (objectToRemove.equals(get(index, valueOfTarget, mergeManager.getSession()))) { |
| removeFromAtIndex(index, valueOfTarget); |
| } else { |
| // Object is in the cache, but the collection doesn't have it at the location we expect |
| // Collection is invalid with respect to these changes, so invalidate the parent and abort |
| Object key = changeRecord.getOwner().getId(); |
| targetSession.getIdentityMapAccessor().invalidateObject(key, changeRecord.getOwner().getClassType(targetSession)); |
| return; |
| } |
| } else { |
| removeFrom(objectToRemove, valueOfTarget, targetSession); |
| } |
| |
| if ((! mergeManager.shouldMergeChangesIntoDistributedCache()) && changeRecord.getMapping().isPrivateOwned()) { |
| // Check that the object was actually removed and not moved. |
| if (objectRemoved) { |
| mergeManager.registerRemovedNewObjectIfRequired(objectChanges.getUnitOfWorkClone()); |
| } |
| } |
| } |
| |
| |
| } else { //getChangeType == add |
| boolean objectAdded = changeRecord.getAddObjectList().containsKey(objectChanges); |
| Object object = null; |
| // The object was actually added and not moved. |
| if (objectAdded && shouldMergeCascadeParts) { |
| object = mergeCascadeParts(objectChanges, mergeManager, targetSession); |
| } |
| |
| if (object == null) { |
| // Retrieve the object to be added to the collection. |
| object = objectChanges.getTargetVersionOfSourceObject(mergeManager, targetSession); |
| } |
| |
| // Assume at this point the above merge will have created a new |
| // object if required and that the object was actually added and |
| // not moved. |
| if (objectAdded && mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| // Bugs 4458089 & 4454532 - check if collection contains new item before adding |
| // during merge into distributed cache |
| if (! contains(object, valueOfTarget, mergeManager.getSession())) { |
| addIntoAtIndex(changeObject.getIndex(), object, valueOfTarget, mergeManager.getSession()); |
| } |
| } else { |
| addIntoAtIndex(changeObject.getIndex(), object, valueOfTarget, targetSession); |
| } |
| } |
| } |
| } else { |
| //Deferred change tracking merge behavior |
| // Step 1 - iterate over the removed changes and remove them from the container. |
| List<Integer> removedIndices = changeRecord.getOrderedRemoveObjectIndices(); |
| |
| if (removedIndices.isEmpty()) { |
| // Check if we have removed objects via a |
| // simpleRemoveFromCollectionChangeRecord API call. |
| Iterator<ObjectChangeSet> removedObjects = changeRecord.getRemoveObjectList().keySet().iterator(); |
| |
| while (removedObjects.hasNext()) { |
| objectChanges = removedObjects.next(); |
| removeFrom(objectChanges.getOldKey(), objectChanges.getTargetVersionOfSourceObject(mergeManager, targetSession), valueOfTarget, targetSession); |
| registerRemoveNewObjectIfRequired(objectChanges, mergeManager); |
| } |
| } else { |
| for (int i = removedIndices.size() - 1; i >= 0; i--) { |
| Integer index = removedIndices.get(i); |
| objectChanges = (ObjectChangeSet) changeRecord.getOrderedRemoveObject(index); |
| Object objectToRemove = objectChanges.getTargetVersionOfSourceObject(mergeManager, targetSession); |
| if ( (objectToRemove!=null) && |
| (objectToRemove.equals(get(index, valueOfTarget, mergeManager.getSession()) )) ) { |
| removeFromAtIndex(index, valueOfTarget); |
| // The object was actually removed and not moved. |
| if (changeRecord.getRemoveObjectList().containsKey(objectChanges)) { |
| registerRemoveNewObjectIfRequired(objectChanges, mergeManager); |
| } |
| } else { |
| |
| //Object is either not in the cache, or not at the location we expect |
| // Collection is invalid with respect to these changes, so invalidate the parent and abort |
| Object key = changeRecord.getOwner().getId(); |
| targetSession.getIdentityMapAccessor().invalidateObject(key, changeRecord.getOwner().getClassType(targetSession)); |
| return; |
| } |
| } |
| } |
| |
| // Step 2 - iterate over the added changes and add them to the container. |
| for (ObjectChangeSet addChangeSet : changeRecord.getOrderedAddObjects()) { |
| objectChanges = addChangeSet; |
| boolean objectAdded = changeRecord.getAddObjectList().containsKey(objectChanges); |
| Object object = null; |
| |
| // The object was actually added and not moved. |
| if (objectAdded && shouldMergeCascadeParts) { |
| object = mergeCascadeParts(objectChanges, mergeManager, targetSession); |
| } |
| |
| if (object == null) { |
| // Retrieve the object to be added to the collection. |
| object = objectChanges.getTargetVersionOfSourceObject(mergeManager, targetSession); |
| } |
| |
| // Assume at this point the above merge will have created a new |
| // object if required and that the object was actually added and |
| // not moved. |
| if (objectAdded && mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| // Bugs 4458089 & 4454532 - check if collection contains new item before adding |
| // during merge into distributed cache |
| if (! contains(object, valueOfTarget, mergeManager.getSession())) { |
| addIntoAtIndex(changeRecord.getOrderedAddObjectIndex(objectChanges), object, valueOfTarget, mergeManager.getSession()); |
| } |
| } else { |
| addIntoAtIndex(changeRecord.getOrderedAddObjectIndex(objectChanges), object, valueOfTarget, targetSession); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void registerRemoveNewObjectIfRequired(ObjectChangeSet objectChanges, MergeManager mergeManager) { |
| if (! mergeManager.shouldMergeChangesIntoDistributedCache()) { |
| mergeManager.registerRemovedNewObjectIfRequired(objectChanges.getUnitOfWorkClone()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove the element at the specified index. |
| */ |
| protected void removeFromAtIndex(int index, Object container) { |
| try { |
| ((List) container).remove(index); |
| } catch (ClassCastException ex1) { |
| throw QueryException.cannotRemoveFromContainer(index, container, this); |
| } catch (IllegalArgumentException ex2) { |
| throw QueryException.cannotRemoveFromContainer(index, container, this); |
| } catch (UnsupportedOperationException ex3) { |
| throw QueryException.cannotRemoveFromContainer(index, container, this); |
| } |
| } |
| |
| /** |
| * This method is used to bridge the behavior between Attribute Change Tracking and |
| * deferred change tracking with respect to adding the same instance multiple times. |
| * Each ContainerPolicy type will implement specific behavior for the collection |
| * type it is wrapping. These methods are only valid for collections containing object references |
| */ |
| @Override |
| public void recordAddToCollectionInChangeRecord(ObjectChangeSet changeSetToAdd, CollectionChangeRecord collectionChangeRecord){ |
| OrderedChangeObject orderedChangeObject = new OrderedChangeObject(CollectionChangeEvent.ADD, null, changeSetToAdd); |
| collectionChangeRecord.getOrderedChangeObjectList().add(orderedChangeObject); |
| } |
| |
| @Override |
| public void recordRemoveFromCollectionInChangeRecord(ObjectChangeSet changeSetToRemove, CollectionChangeRecord collectionChangeRecord){ |
| OrderedChangeObject orderedChangeObject = new OrderedChangeObject(CollectionChangeEvent.REMOVE, null, changeSetToRemove); |
| collectionChangeRecord.getOrderedChangeObjectList().add(orderedChangeObject); |
| } |
| |
| @Override |
| public void recordUpdateToCollectionInChangeRecord(CollectionChangeEvent event, ObjectChangeSet changeSet, CollectionChangeRecord collectionChangeRecord){ |
| int changeType = event.getChangeType(); |
| if (changeType == CollectionChangeEvent.ADD) { |
| super.recordAddToCollectionInChangeRecord(changeSet, collectionChangeRecord); |
| } else if (changeType == CollectionChangeEvent.REMOVE) { |
| super.recordRemoveFromCollectionInChangeRecord(changeSet, collectionChangeRecord); |
| } else { |
| throw ValidationException.wrongCollectionChangeEventType(changeType); |
| } |
| |
| OrderedChangeObject orderedChangeObject = new OrderedChangeObject(changeType, event.getIndex(), changeSet, event.getNewValue()); |
| collectionChangeRecord.getOrderedChangeObjectList().add(orderedChangeObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether addAll method should be called to add entire collection, |
| * or it's possible to call addInto multiple times instead. |
| */ |
| @Override |
| public boolean shouldAddAll() { |
| return this.listOrderField != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any additional fields required by the policy for a fetch join. |
| */ |
| @Override |
| public List<DatabaseField> getAdditionalFieldsForJoin(CollectionMapping baseMapping) { |
| if (this.listOrderField != null) { |
| List<DatabaseField> fields = new ArrayList<>(1); |
| fields.add(this.listOrderField); |
| return fields; |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the index field count. |
| */ |
| @Override |
| public int updateJoinedMappingIndexesForMapKey(Map<DatabaseMapping, Object> indexList, int index) { |
| if (this.listOrderField != null) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| /** |
| * 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, ForeignReferenceMapping mapping, UnitOfWorkChangeSet parentUOWChangeSet, UnitOfWorkImpl unitOfWork){ |
| ObjectChangeSet sourceSet = parentUOWChangeSet.getCloneToObjectChangeSet().get(source); |
| for (OrderedChangeObject changeObject : ((CollectionChangeRecord)changeRecord).getOrderedChangeObjectList()){ |
| if (changeObject.getChangeSet() == sourceSet){ |
| changeObject.setChangeSet(((UnitOfWorkChangeSet)unitOfWork.getUnitOfWorkChangeSet()).findOrCreateLocalObjectChangeSet(target, mapping.getReferenceDescriptor(), unitOfWork.isCloneNewObject(target))); |
| } |
| return; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any tables that will be required when this mapping is used as part of a join query. |
| */ |
| @Override |
| public List<DatabaseTable> getAdditionalTablesForJoinQuery() { |
| if (this.listOrderField != null) { |
| List tables = new ArrayList(1); |
| tables.add(this.listOrderField.getTable()); |
| return tables; |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the index field to the query. |
| */ |
| @Override |
| public void addAdditionalFieldsToQuery(ReadQuery selectionQuery, Expression baseExpression) { |
| if (this.listOrderField != null) { |
| if (selectionQuery.isObjectLevelReadQuery()) { |
| ((ObjectLevelReadQuery)selectionQuery).addAdditionalField(baseExpression.getField(this.listOrderField)); |
| } else { |
| ((SQLSelectStatement) selectionQuery.getSQLStatement()).addField(baseExpression.getField(this.listOrderField)); |
| } |
| } |
| } |
| } |