blob: 2fde388539c77870155633ba83799735549dfbf8 [file] [log] [blame]
/*
* 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));
}
}
}
}