Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 1998, 2019 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;
/**
*
Purpose: Abstract class for relationship mappings which store collection of objects
*
* @author Sati
* @since TOPLink/Java 1.0
*/
public abstract class CollectionMapping extends ForeignReferenceMapping implements ContainerMapping {
/** Used for delete all in m-m, dc and delete all optimization in 1-m. */
protected transient ModifyQuery deleteAllQuery;
protected transient boolean hasCustomDeleteAllQuery;
protected ContainerPolicy containerPolicy;
protected 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.
*/
public 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 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 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.
* @param classLoader
*/
@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 rowsIterator = ((List)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