org.eclipse.persistence.mappings.DirectMapMapping Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* 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
// 07/19/2011-2.2.1 Guy Pelletier
// - 338812: ManyToMany mapping in aggregate object violate integrity constraint on deletion
// // 30/05/2012-2.4 Guy Pelletier
// - 354678: Temp classloader is still being used during metadata processing
package org.eclipse.persistence.mappings;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.persistence.descriptors.changetracking.ChangeTracker;
import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent;
import org.eclipse.persistence.descriptors.changetracking.MapChangeEvent;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.indirection.IndirectCollection;
import org.eclipse.persistence.indirection.ValueHolder;
import org.eclipse.persistence.internal.descriptors.DescriptorIterator;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener;
import org.eclipse.persistence.internal.descriptors.changetracking.ObjectChangeListener;
import org.eclipse.persistence.internal.expressions.SQLDeleteStatement;
import org.eclipse.persistence.internal.expressions.SQLSelectStatement;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.indirection.TransparentIndirectionPolicy;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.DirectMapChangeRecord;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.mappings.converters.ObjectTypeConverter;
import org.eclipse.persistence.mappings.converters.SerializedObjectConverter;
import org.eclipse.persistence.mappings.converters.TypeConversionConverter;
import org.eclipse.persistence.mappings.foundation.MapComponentMapping;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.DeleteObjectQuery;
import org.eclipse.persistence.queries.DirectReadQuery;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.WriteObjectQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
/**
* Mapping for a collection of key-value pairs.
* The key and value must be simple types (String, Number, Date, etc.)
* and stored in a single table along with a foreign key to the source object.
* A converter can be used on the key and value if the desired object types
* do not match the data types.
*
* @see Converter
* @see ObjectTypeConverter
* @see TypeConversionConverter
* @see SerializedObjectConverter
*
* @author Steven Vo
* @since TopLink 3.5
*/
public class DirectMapMapping extends DirectCollectionMapping implements MapComponentMapping {
/**
* DirectMapCollectionMapping constructor
*/
public DirectMapMapping() {
super();
DataReadQuery query = new DataReadQuery();
this.selectionQuery = query;
MappedKeyMapContainerPolicy mapPolicy = new MappedKeyMapContainerPolicy(ClassConstants.Hashtable_Class);
mapPolicy.setValueMapping(this);
this.containerPolicy = mapPolicy;
this.isListOrderFieldSupported = false;
}
/**
* ADVANCED:
* Configure the mapping to use a container policy.
* This must be a MappedKeyMapContainerPolicy policy.
* Set the valueMapping for the policy.
*/
@Override
public void setContainerPolicy(ContainerPolicy containerPolicy) {
super.setContainerPolicy(containerPolicy);
((MappedKeyMapContainerPolicy)containerPolicy).setValueMapping(this);
}
private MappedKeyMapContainerPolicy getMappedKeyMapContainerPolicy(){
return (MappedKeyMapContainerPolicy)containerPolicy;
}
/**
* PUBLIC:
* Return the converter on the mapping.
* A converter can be used to convert between the key's object value and database value.
*/
public Converter getKeyConverter() {
return getMappedKeyMapContainerPolicy().getKeyConverter();
}
/**
* PUBLIC:
* Set the converter on the mapping.
* A converter can be used to convert between the key's object value and database value.
*/
public void setKeyConverter(Converter keyConverter) {
getMappedKeyMapContainerPolicy().setKeyConverter(keyConverter, this);
}
/**
* INTERNAL:
* Set the converter class name on the mapping. Initialized in
* convertClassNamesToClasses.
* A converter can be used to convert between the key's object value and database value.
*/
public void setKeyConverterClassName(String keyConverterClassName) {
getMappedKeyMapContainerPolicy().setKeyConverterClassName(keyConverterClassName, this);
}
/**
* INTERNAL:
* Add a new value and its change set to the collection change record. This is used by
* attribute change tracking. If a value has changed then issue a remove first with the key
* then an add.
*/
public void addToCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException {
DirectMapChangeRecord collectionChangeRecord = (DirectMapChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName());
if (collectionChangeRecord == null) {
collectionChangeRecord = new DirectMapChangeRecord(objectChangeSet);
collectionChangeRecord.setAttribute(getAttributeName());
collectionChangeRecord.setMapping(this);
objectChangeSet.addChange(collectionChangeRecord);
}
collectionChangeRecord.addAdditionChange(newKey, newValue);
}
/**
* 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) {
if (attributeValue == null) {
return containerPolicy.containerInstance(1);
}
Object clonedAttributeValue = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue));
// 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
Object temporaryCollection = null;
synchronized (attributeValue) {
temporaryCollection = containerPolicy.cloneFor(attributeValue);
}
for (Object keysIterator = containerPolicy.iteratorFor(temporaryCollection);
containerPolicy.hasNext(keysIterator);) {
Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(keysIterator, cloningSession);
Object cloneKey = containerPolicy.buildCloneForKey(entry.getKey(), clone, cacheKey, null, cloningSession, isExisting, isFromSharedCache);
Object cloneValue = buildElementClone(entry.getValue(), clone, cacheKey, refreshCascade, cloningSession, isExisting, isFromSharedCache);
containerPolicy.addInto(cloneKey, cloneValue, clonedAttributeValue, cloningSession);
}
return clonedAttributeValue;
}
/**
* INTERNAL:
* Used by AttributeLevelChangeTracking to update a changeRecord with calculated changes
* as opposed 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) {
DirectMapChangeRecord collectionRecord = (DirectMapChangeRecord)changeRecord;
// TODO: Handle events that fired after collection was replaced.
compareCollectionsForChange(collectionRecord.getOriginalCollection(), collectionRecord.getLatestCollection(), collectionRecord, session);
}
/**
* 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) {
if (containerPolicy.isMappedKeyMapPolicy()){
Object values = getAttributeValueFromObject(object);
if (values != null){
Object iterator = containerPolicy.iteratorFor(values);
while (containerPolicy.hasNext(iterator)){
Object wrappedObject = containerPolicy.nextEntry(iterator, uow);
containerPolicy.cascadeDiscoverAndPersistUnregisteredNewObjects(wrappedObject, newObjects, unregisteredExistingObjects, visitedObjects, uow, cascadeErrors);
}
}
}
}
/**
* INTERNAL:
* Cascade perform delete through mappings that require the cascade
*/
@Override
public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) {
if (containerPolicy.isMappedKeyMapPolicy()){
Object values = getAttributeValueFromObject(object);
if (values != null){
Object iterator = containerPolicy.iteratorFor(values);
while (containerPolicy.hasNext(iterator)){
Object wrappedObject = containerPolicy.nextEntry(iterator, uow);
containerPolicy.cascadePerformRemoveIfRequired(wrappedObject, uow, visitedObjects);
}
}
}
}
/**
* INTERNAL:
* Cascade registerNew for Create through mappings that require the cascade
*/
@Override
public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) {
if (containerPolicy.isMappedKeyMapPolicy()){
Object values = getAttributeValueFromObject(object);
if (values != null){
Object iterator = containerPolicy.iteratorFor(values);
while (containerPolicy.hasNext(iterator)){
Object wrappedObject = containerPolicy.nextEntry(iterator, uow);
containerPolicy.cascadeRegisterNewIfRequired(wrappedObject, uow, visitedObjects);
}
}
}
}
/**
* INTERNAL:
* This method is used to calculate the differences between two collections.
*/
@Override
public void compareCollectionsForChange(Object oldCollection, Object newCollection, ChangeRecord changeRecord, AbstractSession session) {
HashMap originalKeyValues = new HashMap(10);
HashMap cloneKeyValues = new HashMap(10);
if (oldCollection != null) {
Map backUpCollection = (Map)oldCollection;
Object backUpIter = containerPolicy.iteratorFor(backUpCollection);
while (containerPolicy.hasNext(backUpIter)) {// Make a lookup of the objects
Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(backUpIter, session);
originalKeyValues.put(entry.getKey(), backUpCollection.get(entry.getKey()));
}
}
Map cloneObjectCollection = (Map)newCollection;
Object cloneIter = containerPolicy.iteratorFor(cloneObjectCollection);
while (containerPolicy.hasNext(cloneIter)) {//Compare them with the objects from the clone
Map.Entry wrappedFirstObject = (Map.Entry)containerPolicy.nextEntry(cloneIter, session);
Object firstValue = wrappedFirstObject.getValue();
Object firstKey = wrappedFirstObject.getKey();
Object backupValue = originalKeyValues.get(firstKey);
if (!originalKeyValues.containsKey(firstKey)) {
cloneKeyValues.put(firstKey, cloneObjectCollection.get(firstKey));
} else if (((backupValue == null) && (firstValue != null)) || (!backupValue.equals(firstValue))) {//the object was not in the backup
cloneKeyValues.put(firstKey, cloneObjectCollection.get(firstKey));
} else {
originalKeyValues.remove(firstKey);
}
}
((DirectMapChangeRecord)changeRecord).clearChanges();
((DirectMapChangeRecord)changeRecord).addAdditionChange(cloneKeyValues);
((DirectMapChangeRecord)changeRecord).addRemoveChange(originalKeyValues);
((DirectMapChangeRecord)changeRecord).setIsDeferred(false);
((DirectMapChangeRecord)changeRecord).setLatestCollection(null);
}
/**
* INTERNAL:
* This method compares the changes between two direct collections. Comparisons are made on equality
* not identity.
*/
@Override
public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) {
Object cloneAttribute = null;
Object backUpAttribute = null;
cloneAttribute = getAttributeValueFromObject(clone);
if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
return null;
}
Map cloneObjectCollection = (Map)getRealCollectionAttributeValueFromObject(clone, session);
Map backUpCollection = null;
if (!owner.isNew()) {
backUpAttribute = getAttributeValueFromObject(backUp);
if ((backUpAttribute == null) && (cloneAttribute == null)) {
return null;
}
backUpCollection = (Map)getRealCollectionAttributeValueFromObject(backUp, session);
}
DirectMapChangeRecord changeRecord = new DirectMapChangeRecord(owner);
changeRecord.setAttribute(getAttributeName());
changeRecord.setMapping(this);
compareCollectionsForChange(backUpCollection, cloneObjectCollection, changeRecord, session);
if (changeRecord.hasChanges()) {
changeRecord.setOriginalCollection(backUpCollection);
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 firstObjectMap = getRealCollectionAttributeValueFromObject(firstObject, session);
Object secondObjectMap = getRealCollectionAttributeValueFromObject(secondObject, session);
return getMappedKeyMapContainerPolicy().compareContainers(firstObjectMap, secondObjectMap);
}
/*
* 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);
if (getDirectKeyField() != null) {
getDirectKeyField().convertClassNamesToClasses(classLoader);
}
}
/**
* INTERNAL
* Called when a DatabaseMapping is used to map the key in a collection. Returns the key.
*/
@Override
public Object createMapComponentFromRow(AbstractRecord dbRow, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){
Object key = dbRow.get(getDirectField());
if (getValueConverter() != null){
key = getValueConverter().convertDataValueToObjectValue(key, session);
}
return key;
}
/**
* INTERNAL:
*/
public DatabaseField getDirectKeyField() {
return getMappedKeyMapContainerPolicy().getDirectKeyField(null);
}
/**
* INTERNAL:
* Initialize and validate the mapping properties.
*/
@Override
public void initialize(AbstractSession session) throws DescriptorException {
getMappedKeyMapContainerPolicy().setDescriptorForKeyMapping(this.getDescriptor());
if (getKeyConverter() != null) {
getKeyConverter().initialize(this, session);
}
super.initialize(session);
}
@Override
protected void initializeDeleteQuery(AbstractSession session) {
if (!getDeleteQuery().hasSessionName()) {
getDeleteQuery().setSessionName(session.getName());
}
if (hasCustomDeleteQuery()) {
return;
}
Expression builder = new ExpressionBuilder();
Expression directKeyExp = null;
List identityFields = getContainerPolicy().getIdentityFieldsForMapKey();
Iterator i = identityFields.iterator();
while (i.hasNext()){
DatabaseField field = i.next();
Expression fieldExpression = builder.getField(field).equal(builder.getParameter(field));
if (directKeyExp == null){
directKeyExp = fieldExpression;
} else {
directKeyExp = directKeyExp.and(fieldExpression);
}
}
Expression expression = null;
SQLDeleteStatement statement = new SQLDeleteStatement();
// Construct an expression to delete from the relation table.
for (int index = 0; index < getReferenceKeyFields().size(); index++) {
DatabaseField referenceKey = getReferenceKeyFields().get(index);
DatabaseField sourceKey = getSourceKeyFields().get(index);
Expression subExp1 = builder.getField(referenceKey);
Expression subExp2 = builder.getParameter(sourceKey);
Expression subExpression = subExp1.equal(subExp2);
expression = subExpression.and(expression);
}
expression = expression.and(directKeyExp);
statement.setWhereClause(expression);
statement.setTable(getReferenceTable());
getDeleteQuery().setSQLStatement(statement);
}
/**
* Initialize insert query. This query is used to insert the collection of objects into the
* reference table.
*/
@Override
protected void initializeInsertQuery(AbstractSession session) {
super.initializeInsertQuery(session);
getContainerPolicy().addFieldsForMapKey(getInsertQuery().getModifyRow());
}
@Override
protected void initializeSelectionStatement(AbstractSession session) {
if (this.selectionQuery.isReadAllQuery()){
((ReadAllQuery)this.selectionQuery).addAdditionalField(getDirectField().clone());
} else {
SQLSelectStatement statement = (SQLSelectStatement)this.selectionQuery.getSQLStatement();
statement.addTable(getReferenceTable());
statement.addField(getDirectField().clone());
getContainerPolicy().addAdditionalFieldsToQuery(this.selectionQuery, getAdditionalFieldsBaseExpression(this.selectionQuery));
statement.normalize(session, null);
}
if (this.selectionQuery.isDirectReadQuery()){
((DirectReadQuery)this.selectionQuery).setResultType(DataReadQuery.MAP);
}
}
/**
* INTERNAL:
* Iterate on the attribute value.
* The value holder has already been processed.
* PERF: Avoid iteration if not required.
*/
@Override
public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) {
super.iterateOnRealAttributeValue(iterator, realAttributeValue);
ContainerPolicy cp = getContainerPolicy();
if (realAttributeValue != null && !iterator.shouldIterateOnPrimitives()) {
for (Object iter = cp.iteratorFor(realAttributeValue); cp.hasNext(iter);) {
Object wrappedObject = cp.nextEntry(iter, iterator.getSession());
cp.iterateOnMapKey(iterator, wrappedObject);
}
}
}
/**
* INTERNAL:
* Iterate on the specified element.
*/
@Override
public void iterateOnElement(DescriptorIterator iterator, Object element) {
super.iterateOnElement(iterator, element);
ContainerPolicy cp = getContainerPolicy();
for (Object iter = cp.iteratorFor(element); cp.hasNext(iter);) {
Object wrappedObject = cp.nextEntry(iter, iterator.getSession());
cp.iterateOnMapKey(iterator, wrappedObject);
}
}
/**
* INTERNAL:
* Related mapping should implement this method to return true.
*/
@Override
public boolean isDirectMapMapping() {
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 changeset.
*/
@Override
public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) {
if (this.descriptor.getCachePolicy().isProtectedIsolation()&& !this.isCacheable && !targetSession.isProtectedSession()){
setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder(null)));
return;
}
Map valueOfTarget = null;
AbstractSession session = mergeManager.getSession();
//collect the changes into a vector
HashMap addObjects = ((DirectMapChangeRecord)changeRecord).getAddObjects();
HashMap removeObjects = ((DirectMapChangeRecord)changeRecord).getRemoveObjects();
//Check to see if the target has an instantiated collection
if ((isAttributeValueInstantiated(target)) && (!changeRecord.getOwner().isNew())) {
valueOfTarget = (Map)getRealCollectionAttributeValueFromObject(target, session);
} else {
//if not create an instance of the map
valueOfTarget = (Map)containerPolicy.containerInstance(addObjects.size());
}
if (!isAttributeValueInstantiated(target)) {
if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
return;
}
Object valueOfSource = getRealCollectionAttributeValueFromObject(source, session);
for (Object iterator = containerPolicy.iteratorFor(valueOfSource);
containerPolicy.hasNext(iterator);) {
Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(iterator, session);
containerPolicy.addInto(entry.getKey(), entry.getValue(), valueOfTarget, session);
}
} else {
Object synchronizationTarget = valueOfTarget;
// For indirect containers the delegate must be synchronized on,
// not the wrapper as the clone synchs on the delegate, see bug#5685287.
if (valueOfTarget instanceof IndirectCollection) {
synchronizationTarget = ((IndirectCollection)valueOfTarget).getDelegateObject();
}
synchronized (synchronizationTarget) {
// Next iterate over the changes and add them to the container
for (Iterator i = removeObjects.keySet().iterator(); i.hasNext();) {
Object keyToRemove = i.next();
containerPolicy.removeFrom(keyToRemove, null, valueOfTarget, session);
}
for (Iterator i = addObjects.keySet().iterator(); i.hasNext();) {
Object keyToAdd = i.next();
Object nextItem = addObjects.get(keyToAdd);
if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
//bug#4458089 and 4454532- check if collection contains new item before adding during merge into distributed cache
if (!containerPolicy.contains(nextItem, valueOfTarget, session)) {
containerPolicy.addInto(keyToAdd, nextItem, valueOfTarget, session);
}
} else {
containerPolicy.addInto(keyToAdd, nextItem, valueOfTarget, session);
}
}
}
}
setRealAttributeValueInObject(target, valueOfTarget);
}
/**
* INTERNAL:
* Merge changes from the source to the target object.
*/
@Override
public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) {
if (this.descriptor.getCachePolicy().isProtectedIsolation()&& !this.isCacheable && !targetSession.isProtectedSession()){
setAttributeValueInObject(target, this.indirectionPolicy.buildIndirectObject(new ValueHolder(null)));
return;
}
if (isTargetUnInitialized) {
// This will happen if the target object was removed from the cache before the commit was attempted
if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) {
setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), targetSession));
return;
}
}
if (!shouldMergeCascadeReference(mergeManager)) {
// This is only going to happen on mergeClone, and we should not attempt to merge the reference
return;
}
if (mergeManager.shouldRefreshRemoteObject() && usesIndirection()) {
mergeRemoteValueHolder(target, source, mergeManager);
return;
}
if (mergeManager.isForRefresh()) {
if (!isAttributeValueInstantiated(target)) {
// This will occur when the clone's value has not been instantiated yet and we do not need
// the refresh that attribute
return;
}
} else if (!isAttributeValueInstantiated(source)) {
// I am merging from a clone into an original. No need to do merge if the attribute was never
// modified
return;
}
Map valueOfSource = (Map)getRealCollectionAttributeValueFromObject(source, mergeManager.getSession());
// trigger instantiation of target attribute
Object valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeManager.getSession());
Object newContainer = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource));
boolean fireChangeEvents = false;
if ((this.getDescriptor().getObjectChangePolicy().isObjectChangeTrackingPolicy()) && (target instanceof ChangeTracker) && (((ChangeTracker)target)._persistence_getPropertyChangeListener() != null)) {
fireChangeEvents = true;
//Collections may not be indirect list or may have been replaced with user collection.
Object iterator = containerPolicy.iteratorFor(valueOfTarget);
while (containerPolicy.hasNext(iterator)) {
Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(iterator, mergeManager.getSession());
((ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).internalPropertyChange(new MapChangeEvent(target, getAttributeName(), valueOfTarget, entry.getKey(), entry.getValue(), CollectionChangeEvent.REMOVE, false));// make the remove change event fire.
}
if (newContainer instanceof ChangeTracker) {
((ChangeTracker)newContainer)._persistence_setPropertyChangeListener(((ChangeTracker)target)._persistence_getPropertyChangeListener());
}
if (valueOfTarget instanceof ChangeTracker) {
((ChangeTracker)valueOfTarget)._persistence_setPropertyChangeListener(null);//remove listener
}
}
valueOfTarget = newContainer;
for (Object sourceValuesIterator = containerPolicy.iteratorFor(valueOfSource);
containerPolicy.hasNext(sourceValuesIterator);) {
Map.Entry entry = (Map.Entry)containerPolicy.nextEntry(sourceValuesIterator, mergeManager.getSession());
if (fireChangeEvents) {
//Collections may not be indirect list or may have been replaced with user collection.
((ObjectChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).internalPropertyChange(new MapChangeEvent(target, getAttributeName(), valueOfTarget, entry.getKey(), entry.getValue(), CollectionChangeEvent.ADD, false));// make the add change event fire.
}
containerPolicy.addInto(entry.getKey(), entry.getValue(), valueOfTarget, mergeManager.getSession());
}
if (fireChangeEvents && (getDescriptor().getObjectChangePolicy().isAttributeChangeTrackingPolicy())) {
// check that there were changes, if not then remove the record.
ObjectChangeSet changeSet = ((AttributeChangeListener)((ChangeTracker)target)._persistence_getPropertyChangeListener()).getObjectChangeSet();
if (changeSet != null) {
DirectMapChangeRecord changeRecord = (DirectMapChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName());
if (changeRecord != null) {
if (!changeRecord.isDeferred()) {
if (!changeRecord.hasChanges()) {
changeSet.removeChange(getAttributeName());
}
} else {
// Must reset the latest collection.
changeRecord.setLatestCollection(valueOfTarget);
}
}
}
}
// Must re-set variable to allow for set method to re-morph changes if the collection is not being stored directly.
setRealAttributeValueInObject(target, valueOfTarget);
}
/**
* INTERNAL:
* Perform the commit event.
* This is used in the uow to delay data modifications.
* This is mostly dealt with in the superclass. Private Owned deletes require extra functionality
*/
@Override
public void performDataModificationEvent(Object[] event, AbstractSession session) throws DatabaseException, DescriptorException {
super.performDataModificationEvent(event, session);
if (event[0] == Delete && containerPolicy.shouldIncludeKeyInDeleteEvent()) {
session.deleteObject(event[3]);
}
}
/**
* INTERNAL:
* Overridden by mappings that require additional processing of the change record after the record has been calculated.
*/
@Override
public void postCalculateChanges(org.eclipse.persistence.sessions.changesets.ChangeRecord changeRecord, UnitOfWorkImpl uow) {
// no need for private owned check. This code is only registered for private owned mappings.
// targets are added to and/or removed to/from the source.
DirectMapChangeRecord mapChangeRecord = (DirectMapChangeRecord)changeRecord;
Iterator it = mapChangeRecord.getRemoveObjects().entrySet().iterator();
while(it.hasNext()) {
Map.Entry