/******************************************************************************* | |
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved. | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* Oracle - initial API and implementation from Oracle TopLink | |
* 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. | |
* | |
* @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 = ((Integer)conversionManager.convertObject(orderValue, Integer.class)).intValue(); | |
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((Integer)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.intValue() > 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.intValue(), 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 = Integer.valueOf(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(), Integer.valueOf(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)).intValue(); | |
oldListValueIndex.remove(currentObject); | |
if (index == oldIndex) { | |
indicesToRemove.remove(Integer.valueOf(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(Integer.valueOf(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(Integer.valueOf(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(Integer.valueOf(index)); | |
if (newListValueIndex.containsKey(oldObject)) { | |
if (((newListValueIndex.get(oldObject)).intValue() - 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(Integer.valueOf(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 objects =changeRecord.getOrderedChangeObjectList().iterator(); | |
while (objects.hasNext()){ | |
OrderedChangeObject changeObject = (OrderedChangeObject)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 removedObjects = changeRecord.getRemoveObjectList().keySet().iterator(); | |
while (removedObjects.hasNext()) { | |
objectChanges = (ObjectChangeSet) 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(Integer.valueOf(index), container, this); | |
} catch (IllegalArgumentException ex2) { | |
throw QueryException.cannotRemoveFromContainer(Integer.valueOf(index), container, this); | |
} catch (UnsupportedOperationException ex3) { | |
throw QueryException.cannotRemoveFromContainer(Integer.valueOf(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<DatabaseField>(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)((DataReadQuery)selectionQuery).getSQLStatement()).addField(baseExpression.getField(this.listOrderField)); | |
} | |
} | |
} | |
} |