blob: 5c955e3dbf4bdf6da3ffe617a93a3c8e821f1aea [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
// 02/11/2009-1.1 Michael O'Brien
// - 259993: As part 2) During mergeClonesAfterCompletion()
// If the the acquire and release threads are different
// switch back to the stored acquire thread stored on the mergeManager.
// 09 Jan 2013-2.5 Gordon Yorke
// - 397772: JPA 2.1 Entity Graph Support
package org.eclipse.persistence.internal.sessions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.VersionLockingPolicy;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.descriptors.PersistenceEntity;
import org.eclipse.persistence.internal.helper.linkedlist.LinkedNode;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.sessions.remote.ObjectDescriptor;
import org.eclipse.persistence.internal.sessions.remote.RemoteUnitOfWork;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.queries.DoesExistQuery;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.sessions.remote.DistributedSession;
/**
* <p><b>Purpose</b>:
* Used to manage the merge of two objects in a unit of work.
*
* @author James Sutherland
* @since TOPLink/Java 1.1
*/
public class MergeManager {
/** The unit of work merging for. */
protected AbstractSession session;
/** Used only while refreshing objects on remote session */
protected Map objectDescriptors;
/** Used to unravel recursion. */
protected Map<AbstractSession, Map<Object, Object>> objectsAlreadyMerged;
/** Used to keep track of merged new objects. */
protected IdentityHashMap mergedNewObjects;
/** Used to store the list of locks that this merge manager has acquired for this merge */
protected ArrayList<CacheKey> acquiredLocks;
/** If this variable is not null then the mergemanager is waiting on a particular primary key */
protected Object writeLockQueued;
/** Stores the node that holds this mergemanager within the WriteLocksManager queue */
protected LinkedNode queueNode;
/** Policy that determines merge type (i.e. merge is used for several usages). */
protected int mergePolicy;
protected static final int WORKING_COPY_INTO_ORIGINAL = 1;
protected static final int ORIGINAL_INTO_WORKING_COPY = 2;
protected static final int CLONE_INTO_WORKING_COPY = 3;
protected static final int WORKING_COPY_INTO_REMOTE = 4;
protected static final int REFRESH_REMOTE_OBJECT = 5;
protected static final int CHANGES_INTO_DISTRIBUTED_CACHE = 6;
protected static final int CLONE_WITH_REFS_INTO_WORKING_COPY = 7;
/** Policy that determines how the merge will cascade to its object's parts. */
protected int cascadePolicy;
public static final int NO_CASCADE = 1;
public static final int CASCADE_PRIVATE_PARTS = 2;
public static final int CASCADE_ALL_PARTS = 3;
public static final int CASCADE_BY_MAPPING = 4;
/** Backdoor to disable merge locks. */
public static boolean LOCK_ON_MERGE = true;
/** Stored so that all objects merged by a merge manager can have the same readTime. */
protected long systemTime = 0;
/** Force cascade merge even if a clone is already registered */
// GF#1139 Cascade doesn't work when merging managed entity
protected boolean forceCascade;
/** records that deferred locks have been employed for the merge process */
protected boolean isTransitionedToDeferredLocks = false;
/** save the currentThread for later comparison to the activeThread in case they don't match */
protected Thread lockThread;
/** records that this merge process is for a refresh */
protected boolean isForRefresh;
public MergeManager(AbstractSession session) {
this.session = session;
this.mergedNewObjects = new IdentityHashMap();
this.objectsAlreadyMerged = new IdentityHashMap();
this.cascadePolicy = CASCADE_ALL_PARTS;
this.mergePolicy = WORKING_COPY_INTO_ORIGINAL;
this.acquiredLocks = new ArrayList<>();
}
/**
* Cascade all parts, this is the default for the merge.
*/
public void cascadeAllParts() {
setCascadePolicy(CASCADE_ALL_PARTS);
}
/**
* Cascade private parts, this can be used to merge clone when using RMI.
*/
public void cascadePrivateParts() {
setCascadePolicy(CASCADE_PRIVATE_PARTS);
}
/**
* Merge only direct parts, this can be used to merge clone when using RMI.
*/
public void dontCascadeParts() {
setCascadePolicy(NO_CASCADE);
}
public ArrayList<CacheKey> getAcquiredLocks() {
return this.acquiredLocks;
}
public int getCascadePolicy() {
return cascadePolicy;
}
protected int getMergePolicy() {
return mergePolicy;
}
public Map getObjectDescriptors() {
if (this.objectDescriptors == null) {
this.objectDescriptors = new IdentityHashMap();
}
return this.objectDescriptors;
}
public Map getObjectsAlreadyMerged() {
return objectsAlreadyMerged;
}
public Object getObjectToMerge(Object sourceValue, ClassDescriptor descriptor, AbstractSession targetSession) {
if (shouldMergeOriginalIntoWorkingCopy()) {
return getTargetVersionOfSourceObject(sourceValue, descriptor, targetSession);
}
return sourceValue;
}
/**
* INTENRAL:
* Used to get the node that this merge manager is stored in, within the WriteLocksManager write lockers queue
*/
public LinkedNode getQueueNode() {
return this.queueNode;
}
public AbstractSession getSession() {
return session;
}
/**
* Get the stored value of the current time. This method lazily initializes
* so that read times for the same merge manager can all be set to the same read time
*/
public long getSystemTime() {
if (systemTime == 0) {
systemTime = System.currentTimeMillis();
}
return systemTime;
}
/**
* Return the corresponding value that should be assigned to the target object for the source object.
* This value must be local to the targets object space.
*/
public Object getTargetVersionOfSourceObject(Object source, ClassDescriptor descriptor, AbstractSession targetSession) {
if (shouldMergeWorkingCopyIntoOriginal()){
Object original = null;
CacheKey cacheKey = targetSession.getCacheKeyFromTargetSessionForMerge(source, descriptor.getObjectBuilder(), descriptor, this);
if (cacheKey != null){
original = cacheKey.getObject();
}
if (original == null){
original = ((UnitOfWorkImpl) this.session).getOriginalVersionOfObjectOrNull(source, null, descriptor, targetSession);
if (original == source) {
Object registeredObject = registerExistingObjectOfReadOnlyClassInNestedTransaction(source, descriptor, targetSession);
if (registeredObject != null) {
original = registeredObject;
}
}
}
// If original does not exist then we must merge the entire object.
if (original == null){
original = ((UnitOfWorkImpl) this.session).buildOriginal(source);
//ensure new original has PKs populated as they may be needed later.
if (descriptor.getCopyPolicy().buildsNewInstance()){
List<DatabaseMapping> pkMappings = descriptor.getObjectBuilder().getPrimaryKeyMappings();
for (DatabaseMapping mapping : pkMappings){
mapping.buildClone(source, null, original, null, targetSession);
}
}
}
return original;
}else if (shouldMergeWorkingCopyIntoRemote()) {
// Target is in uow parent, or original instance for new object.
return ((UnitOfWorkImpl)this.session).getOriginalVersionOfObject(source);
} else if (shouldMergeCloneIntoWorkingCopy() || shouldMergeOriginalIntoWorkingCopy() || shouldMergeCloneWithReferencesIntoWorkingCopy()) {
// Target is clone from uow.
//make sure we use the register for merge
//bug 3584343
return registerObjectForMergeCloneIntoWorkingCopy(source, false);
} else if (shouldRefreshRemoteObject()) {
// Target is in session's cache.
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(source, this.session);
return this.session.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, source.getClass(), descriptor);
}
throw ValidationException.invalidMergePolicy();
}
/**
* INTERNAL:
* Used to register an existing object used in nested unit of work with
* read-only class in root unit of work.
*/
public Object registerExistingObjectOfReadOnlyClassInNestedTransaction(Object source, ClassDescriptor descriptor, AbstractSession targetSession) {
if (session.isUnitOfWork() && targetSession.isUnitOfWork()) {
UnitOfWorkImpl uow = (UnitOfWorkImpl) session;
if (uow.isNestedUnitOfWork && uow.isClassReadOnly(descriptor.getJavaClass(), descriptor)) {
return ((UnitOfWorkImpl)targetSession).registerExistingObject(source);
}
}
return null;
}
/**
* INTENRAL:
* Used to get the object that the merge manager is waiting on, in order to acquire locks
*/
public Object getWriteLockQueued() {
return this.writeLockQueued;
}
/**
* @return the isForMerge
*/
public boolean isForRefresh() {
return isForRefresh;
}
/**
* @param isforRefresh the isForMerge to set
*/
public void setForRefresh(boolean isforRefresh) {
this.isForRefresh = isforRefresh;
}
/**
* INTERNAL:
* Will return if the merge process has transitioned the active merge locks to deferred locks for
* readlock deadlock avoidance.
*/
public boolean isTransitionedToDeferredLocks(){
return this.isTransitionedToDeferredLocks;
}
/**
* Recursively merge changes in the object dependent on the merge policy.
* The map is used to resolve recursion.
*/
public Object mergeChanges(Object object, ObjectChangeSet objectChangeSet, AbstractSession targetSession) throws ValidationException {
if (object == null) {
return object;
}
// Do not merge read-only objects in a unit of work.
if (this.session.isClassReadOnly(object.getClass())) {
return object;
}
// Means that object is either already merged or in the process of being merged.
if (isAlreadyMerged(object, targetSession)) {
return object;
}
// Put the object to be merged in the set.
recordMerge(object, object, targetSession);
Object mergedObject;
if (shouldMergeWorkingCopyIntoOriginal()) {
mergedObject = mergeChangesOfWorkingCopyIntoOriginal(object, objectChangeSet);
} else if (shouldMergeChangesIntoDistributedCache()) {
mergedObject = mergeChangesIntoDistributedCache(object, objectChangeSet);
} else if (shouldMergeCloneIntoWorkingCopy() || shouldMergeCloneWithReferencesIntoWorkingCopy()) {
mergedObject = mergeChangesOfCloneIntoWorkingCopy(object);
} else if (shouldMergeOriginalIntoWorkingCopy()) {
mergedObject = mergeChangesOfOriginalIntoWorkingCopy(object);
} else if (shouldMergeWorkingCopyIntoRemote()) {
mergedObject = mergeChangesOfWorkingCopyIntoRemote(object);
} else if (shouldRefreshRemoteObject()) {
mergedObject = mergeChangesForRefreshingRemoteObject(object);
} else {
throw ValidationException.invalidMergePolicy();
}
return mergedObject;
}
public void recordMerge(Object key, Object value, AbstractSession targetSession) {
Map sessionMap = this.objectsAlreadyMerged.get(targetSession);
if (sessionMap == null){
sessionMap = new IdentityHashMap();
this.objectsAlreadyMerged.put(targetSession, sessionMap);
}
sessionMap.put(key, value);
}
public boolean isAlreadyMerged(Object object, AbstractSession targetSession) {
Map sessionMap = this.objectsAlreadyMerged.get(targetSession);
if (sessionMap == null){
return false;
}
return sessionMap.containsKey(object);
}
public Object getMergedObject(Object key, AbstractSession targetSession){
Map sessionMap = this.objectsAlreadyMerged.get(targetSession);
if (sessionMap == null){
return null;
}
return sessionMap.get(key);
}
/**
* Recursively merge the RMI clone from the server
* into the client unit of work working copy.
* This will only be called if the working copy exists.
*/
protected Object mergeChangesForRefreshingRemoteObject(Object serverSideDomainObject) {
ClassDescriptor descriptor = this.session.getDescriptor(serverSideDomainObject);
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(serverSideDomainObject, this.session);
Object clientSideDomainObject = this.session.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, serverSideDomainObject.getClass(), descriptor);
if (clientSideDomainObject == null) {
//the referenced object came back as null from the cache.
ObjectDescriptor objectDescriptor = (ObjectDescriptor)getObjectDescriptors().get(serverSideDomainObject);
if (objectDescriptor == null){
//the object must have been added concurently before serialize generate a new ObjectDescriptor on this side
objectDescriptor = new ObjectDescriptor();
objectDescriptor.setKey(primaryKey);
objectDescriptor.setObject(serverSideDomainObject);
OptimisticLockingPolicy policy = descriptor.getOptimisticLockingPolicy();
if (policy == null){
objectDescriptor.setWriteLockValue(null);
}else{
objectDescriptor.setWriteLockValue(policy.getBaseValue());
}
}
//query is used for the cascade policy only
org.eclipse.persistence.queries.ObjectLevelReadQuery query = new org.eclipse.persistence.queries.ReadObjectQuery();
query.setCascadePolicy(this.getCascadePolicy());
this.session.getIdentityMapAccessorInstance().putInIdentityMap(serverSideDomainObject, primaryKey, objectDescriptor.getWriteLockValue(), objectDescriptor.getReadTime(), descriptor);
descriptor.getObjectBuilder().fixObjectReferences(serverSideDomainObject, getObjectDescriptors(), this.objectsAlreadyMerged.get(this.session), query, (DistributedSession)this.session);
clientSideDomainObject = serverSideDomainObject;
} else {
// merge into the clientSideDomainObject from the serverSideDomainObject;
// use clientSideDomainObject as the backup, as anything different should be merged
descriptor.getObjectBuilder().mergeIntoObject(clientSideDomainObject, false, serverSideDomainObject, this, getSession());
ObjectDescriptor objectDescriptor = (ObjectDescriptor)getObjectDescriptors().get(serverSideDomainObject);
if (objectDescriptor == null){
//the object must have been added concurently before serialize generate a new ObjectDescriptor on this side
objectDescriptor = new ObjectDescriptor();
objectDescriptor.setKey(primaryKey);
objectDescriptor.setObject(serverSideDomainObject);
OptimisticLockingPolicy policy = descriptor.getOptimisticLockingPolicy();
if (policy == null){
objectDescriptor.setWriteLockValue(null);
}else{
objectDescriptor.setWriteLockValue(policy.getBaseValue());
}
}
CacheKey key = this.session.getIdentityMapAccessorInstance().getCacheKeyForObjectForLock(primaryKey, clientSideDomainObject.getClass(), descriptor);
// Check for null because when there is NoIdentityMap, CacheKey will be null
if (key != null) {
key.setReadTime(objectDescriptor.getReadTime());
}
if (descriptor.usesOptimisticLocking()) {
this.session.getIdentityMapAccessor().updateWriteLockValue(primaryKey, clientSideDomainObject.getClass(), objectDescriptor.getWriteLockValue());
}
}
return clientSideDomainObject;
}
/**
* INTERNAL:
* Merge the changes to all objects to session's cache.
*/
public void mergeChangesFromChangeSet(UnitOfWorkChangeSet uowChangeSet) {
this.session.startOperationProfile(SessionProfiler.DistributedMerge);
try {
// Ensure concurrency if cache isolation requires.
this.session.getIdentityMapAccessorInstance().acquireWriteLock();
this.session.log(SessionLog.FINER, SessionLog.PROPAGATION, "received_updates_from_remote_server");
if (this.session.hasEventManager()) {
this.session.getEventManager().preDistributedMergeUnitOfWorkChangeSet(uowChangeSet);
}
// Iterate over each clone and let the object build merge to clones into the originals.
this.session.getIdentityMapAccessorInstance().getWriteLockManager().acquireRequiredLocks(this, uowChangeSet);
Iterator objectChangeEnum = uowChangeSet.getAllChangeSets().keySet().iterator();
Set<Class> classesChanged = new HashSet<>();
while (objectChangeEnum.hasNext()) {
ObjectChangeSet objectChangeSet = (ObjectChangeSet)objectChangeEnum.next();
// Don't read the object here. If it is null then we won't merge it at this stage, unless it
// is being referenced which will force the load later.
Object object = objectChangeSet.getTargetVersionOfSourceObject(this, this.session, false);
if (object != null) {
mergeChanges(object, objectChangeSet, this.session);
this.session.incrementProfile(SessionProfiler.ChangeSetsProcessed);
} else if (objectChangeSet.isNew()) {
mergeNewObjectIntoCache(objectChangeSet);
this.session.incrementProfile(SessionProfiler.ChangeSetsProcessed);
} else {
this.session.incrementProfile(SessionProfiler.ChangeSetsNotProcessed);
}
classesChanged.add(objectChangeSet.getClassType(this.session));
}
if (uowChangeSet.hasDeletedObjects()) {
Iterator deletedObjects = uowChangeSet.getDeletedObjects().values().iterator();
while (deletedObjects.hasNext()) {
ObjectChangeSet changeSet = (ObjectChangeSet)deletedObjects.next();
changeSet.removeFromIdentityMap(this.session);
classesChanged.add(changeSet.getClassType(this.session));
}
}
// Clear the query cache as well.
for (Class changedClass : classesChanged) {
this.session.getIdentityMapAccessorInstance().invalidateQueryCache(changedClass);
}
} catch (RuntimeException exception) {
this.session.handleException(exception);
} finally {
this.session.getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(this);
this.session.getIdentityMapAccessorInstance().releaseWriteLock();
this.session.endOperationProfile(SessionProfiler.DistributedMerge);
if (this.session.hasEventManager()) {
this.session.getEventManager().postDistributedMergeUnitOfWorkChangeSet(uowChangeSet);
}
}
}
/**
* Merge the changes specified within the changeSet into the cache.
* The object passed in is the original object from the cache.
*/
protected Object mergeChangesIntoDistributedCache(Object original, ObjectChangeSet changeSet) {
AbstractSession session = this.session;
// Determine if the object needs to be registered in the parent's clone mapping,
// This is required for registered new objects in a nested unit of work.
Class localClassType = changeSet.getClassType(session);
ClassDescriptor descriptor = session.getDescriptor(localClassType);
// Perform invalidation of a cached object (when set on the ChangeSet) to avoid refreshing or merging
if (changeSet.getSynchronizationType() == ClassDescriptor.INVALIDATE_CHANGED_OBJECTS) {
session.getIdentityMapAccessorInstance().invalidateObject(changeSet.getId(), localClassType);
return original;
}
// If version locking was used, check if the cache version is the correct version, otherwise invalidate,
// Don't know for no locking, or field locking, so always merge.
if ((!changeSet.isNew()) && descriptor.usesVersionLocking()) {
if ((session.getCommandManager() != null) && (session.getCommandManager().getCommandConverter() != null)) {
// Rebuild the version value from user format i.e the change set was converted to XML
changeSet.rebuildWriteLockValueFromUserFormat(descriptor, session);
}
int difference = descriptor.getOptimisticLockingPolicy().getVersionDifference(changeSet.getInitialWriteLockValue(), original, changeSet.getId(), session);
// Should be = 0 if was a good update, otherwise was already refreshed, or a version change was lost.
if (difference < 0) {
// The current version is newer than the one on the remote system, was refreshed already, ignore change.
session.log(SessionLog.FINEST, SessionLog.PROPAGATION, "change_from_remote_server_older_than_current_version", changeSet.getClassName(), changeSet.getId());
return original;
} else if (difference > 0) {
// If the current version is much older than the remote system, so invalidate the object as a change was missed.
session.log(SessionLog.FINEST, SessionLog.PROPAGATION, "current_version_much_older_than_change_from_remote_server", changeSet.getClassName(), changeSet.getId());
session.getIdentityMapAccessorInstance().invalidateObject(changeSet.getId(), localClassType);
return original;
}
}
// Always merge into the original.
session.log(SessionLog.FINEST, SessionLog.PROPAGATION, "Merging_from_remote_server", changeSet.getClassName(), changeSet.getId());
if (changeSet.isNew() || (changeSet.getSynchronizationType() != ClassDescriptor.DO_NOT_SEND_CHANGES)) {
Object primaryKey = changeSet.getId();
// PERF: Get the cached cache-key from the change-set.
CacheKey cacheKey = changeSet.getActiveCacheKey();
// The cache key should never be null for the new commit locks, but may be depending on the cache isolation level may not be locked,
// so needs to be re-acquired.
if (cacheKey == null || !cacheKey.isAcquired()) {
// ELBug 355610 - Use appendLock() instead of acquireLock() for transitioning
// to deferred locks for new objects in order to avoid the possibility of a deadlock.
cacheKey = session.getIdentityMapAccessorInstance().getWriteLockManager().appendLock(primaryKey, original, descriptor, this, session);
}
descriptor.getObjectBuilder().mergeChangesIntoObject(original, changeSet, null, this, session, false, false);
if (descriptor.usesOptimisticLocking() && descriptor.getOptimisticLockingPolicy().isStoredInCache()) {
cacheKey.setWriteLockValue(changeSet.getWriteLockValue());
}
// Bug 486845 - ensure that any protected foreign keys from the ChangeSet
// for an object with protected isolation are set on the object's CacheKey
if (descriptor.isProtectedIsolation() && changeSet.hasProtectedForeignKeys()) {
descriptor.getObjectBuilder().cacheForeignKeyValues(changeSet.getProtectedForeignKeys(), cacheKey, session);
}
cacheKey.setObject(original);
if (descriptor.getCacheInvalidationPolicy().shouldUpdateReadTimeOnUpdate() || changeSet.isNew()) {
cacheKey.setReadTime(getSystemTime());
}
cacheKey.updateAccess();
}
return original;
}
/**
* Recursively merge to rmi clone into the unit of work working copy.
* The map is used to resolve recursion.
*/
protected Object mergeChangesOfCloneIntoWorkingCopy(Object rmiClone) {
Object registeredObject = null;
if (isForRefresh){
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)this.session;
//refreshing UOW instance so only merge if already registered
ClassDescriptor descriptor = unitOfWork.getDescriptor(rmiClone.getClass());
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(rmiClone, unitOfWork, true);
if (primaryKey != null) {
registeredObject = unitOfWork.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, null, descriptor.getJavaClass(), false, descriptor);
}
if (registeredObject == null){
return unitOfWork.internalRegisterObject(rmiClone, descriptor, false);
}
}else{
registeredObject = registerObjectForMergeCloneIntoWorkingCopy(rmiClone, shouldForceCascade());
}
//adding isAlreadyMerged/recoredMerge check to prevent the uow clone from being merged into twice from the same tree
//bug 404171
if ((registeredObject == rmiClone || isAlreadyMerged(registeredObject, this.session)) && !shouldForceCascade()) {
//need to find better better fix. prevents merging into itself.
return registeredObject;
}
recordMerge(registeredObject, registeredObject, this.session);
ClassDescriptor descriptor = this.session.getDescriptor(rmiClone);
try {
ObjectBuilder builder = descriptor.getObjectBuilder();
if (!isForRefresh && registeredObject != rmiClone && descriptor.usesVersionLocking() && ! mergedNewObjects.containsKey(registeredObject)) {
VersionLockingPolicy policy = (VersionLockingPolicy) descriptor.getOptimisticLockingPolicy();
if (policy.isStoredInObject()) {
Object currentValue = builder.extractValueFromObjectForField(registeredObject, policy.getWriteLockField(), session);
if (policy.isNewerVersion(currentValue, rmiClone, session.keyFromObject(rmiClone, descriptor), session)) {
throw OptimisticLockException.objectChangedSinceLastMerge(rmiClone);
}
}
}
// Toggle change tracking during the merge.
descriptor.getObjectChangePolicy().dissableEventProcessing(registeredObject);
boolean cascadeOnly = false;
if (registeredObject == rmiClone || mergedNewObjects.containsKey(registeredObject)) {
// GF#1139 Cascade merge operations to relationship mappings even if already registered
cascadeOnly = true;
}
// Merge into the clone from the original and use the clone as
// backup as anything different should be merged.
builder.mergeIntoObject(registeredObject, null, false, rmiClone, this, this.session, cascadeOnly, false, false);
if (isForRefresh){
Object primaryKey = builder.extractPrimaryKeyFromObject(registeredObject, session);
descriptor.getObjectChangePolicy().revertChanges(registeredObject, descriptor, (UnitOfWorkImpl)this.session, ((UnitOfWorkImpl)this.session).getCloneMapping(), true);
CacheKey uowCacheKey = this.session.getIdentityMapAccessorInstance().getCacheKeyForObjectForLock(primaryKey, registeredObject.getClass(), descriptor);
CacheKey parentCacheKey = session.getParentIdentityMapSession(descriptor, false, false).getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, registeredObject.getClass(), descriptor, false);
if (descriptor.usesOptimisticLocking()) {
descriptor.getOptimisticLockingPolicy().mergeIntoParentCache(uowCacheKey, parentCacheKey);
}
// Check for null because when there is NoIdentityMap, CacheKey will be null
if ((parentCacheKey != null) && (uowCacheKey != null)) {
uowCacheKey.setReadTime(parentCacheKey.getReadTime());
}
}
} finally {
descriptor.getObjectChangePolicy().enableEventProcessing(registeredObject);
}
return registeredObject;
}
/**
* Recursively merge to original from its parent into the clone.
* The map is used to resolve recursion.
*/
protected Object mergeChangesOfOriginalIntoWorkingCopy(Object clone) {
ClassDescriptor descriptor = this.session.getDescriptor(clone);
// Find the original object, if it is not there then do nothing.
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, this.session, true);
CacheKey parentCacheKey = null;
if (primaryKey != null){
parentCacheKey = this.session.getParentIdentityMapSession(descriptor, false, false).getIdentityMapAccessorInstance().getCacheKeyForObjectForLock(primaryKey, clone.getClass(), descriptor);
}
Object original = null;
if (parentCacheKey != null){
original = parentCacheKey.getObject();
}else{
if (descriptor.getCachePolicy().isProtectedIsolation() && descriptor.hasNoncacheableMappings()){
this.session.refreshObject(clone);
return clone;
}
original = ((UnitOfWorkImpl)this.session).getOriginalVersionOfObjectOrNull(clone, descriptor);
}
if (original == null) {
return clone;
}
// Toggle change tracking during the merge.
descriptor.getObjectChangePolicy().dissableEventProcessing(clone);
try {
// This section of code will only be entered if the protected object is being cached in the IsolatedClientSession
// so there is a fully populated original.
// Merge into the clone from the original, use clone as backup as anything different should be merged.
descriptor.getObjectBuilder().mergeIntoObject(clone, false, original, this, session);
} finally {
descriptor.getObjectChangePolicy().enableEventProcessing(clone);
}
//update the change policies with the refresh
descriptor.getObjectChangePolicy().revertChanges(clone, descriptor, (UnitOfWorkImpl)this.session, ((UnitOfWorkImpl)this.session).getCloneMapping(), true);
if (primaryKey == null) {
return clone;
}
if (descriptor.usesOptimisticLocking()) {
descriptor.getOptimisticLockingPolicy().mergeIntoParentCache((UnitOfWorkImpl)this.session, primaryKey, clone);
}
CacheKey uowCacheKey = this.session.getIdentityMapAccessorInstance().getCacheKeyForObjectForLock(primaryKey, clone.getClass(), descriptor);
// Check for null because when there is NoIdentityMap, CacheKey will be null
if ((parentCacheKey != null) && (uowCacheKey != null)) {
uowCacheKey.setReadTime(parentCacheKey.getReadTime());
}
return clone;
}
/**
* Recursively merge to clone into the original in its parent.
* The map is used to resolve recursion.
*/
protected Object mergeChangesOfWorkingCopyIntoOriginal(Object clone, ObjectChangeSet objectChangeSet) {
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)this.session;
AbstractSession parent = unitOfWork.getParent();
// If the clone is deleted, avoid this merge and simply return the clone.
if (!unitOfWork.isNestedUnitOfWork() && unitOfWork.isObjectDeleted(clone)) {
return clone;
}
ClassDescriptor descriptor = unitOfWork.getDescriptor(clone.getClass());
// Determine if the object needs to be registered in the parent's clone mapping,
// This is required for registered new objects in a nested unit of work.
boolean requiresToRegisterInParent = false;
Object originalNewObject = null;
if (unitOfWork.isNestedUnitOfWork()) {
originalNewObject = unitOfWork.getOriginalVersionOfNewObject(clone);
if ((originalNewObject != null) // Check that the object is new.
&& (!((UnitOfWorkImpl)parent).isCloneNewObject(originalNewObject)) && (!unitOfWork.isUnregisteredNewObjectInParent(originalNewObject))) {
requiresToRegisterInParent = true;
}
}
AbstractSession parentSession = unitOfWork.getParentIdentityMapSession(descriptor, false, false);
CacheKey cacheKey = mergeChangesOfWorkingCopyIntoOriginal(clone, objectChangeSet, descriptor, parentSession, unitOfWork);
AbstractSession sharedSession = parentSession;
if (descriptor.getCachePolicy().isProtectedIsolation()) {
if (parentSession.isIsolatedClientSession()){
//merge into the shared cache as well.
sharedSession = parentSession.getParent();
cacheKey = mergeChangesOfWorkingCopyIntoOriginal(clone, objectChangeSet, descriptor, sharedSession, unitOfWork);
}
// Must always merge the foreign keys in the shared session.
if (!sharedSession.isProtectedSession()) {
descriptor.getObjectBuilder().cacheForeignKeyValues(clone, cacheKey, descriptor, sharedSession);
}
}
if (requiresToRegisterInParent) {
// Can use a new instance as backup and original.
Object backupClone = descriptor.getObjectBuilder().buildNewInstance();
Object newInstance = descriptor.getObjectBuilder().buildNewInstance();
// EL bug 378512 - use original object from nested uow for registration in parent uow
((UnitOfWorkImpl)parent).registerOriginalNewObjectFromNestedUnitOfWork(originalNewObject, backupClone, newInstance, descriptor);
}
return clone;
}
/**
* Recursively merge to clone into the original in its parent.
* The map is used to resolve recursion.
* This is used to merge objects from the unit of work into the shared (or isolated) cache.
*/
protected CacheKey mergeChangesOfWorkingCopyIntoOriginal(Object clone, ObjectChangeSet objectChangeSet, ClassDescriptor descriptor, AbstractSession targetSession, UnitOfWorkImpl unitOfWork) {
/** This is the merge used by the unit of work on commit.
*** This is a very complex method that handles several different use cases of the unit of work. ***
These include:
#1 - normal merge of new and changed objects with pre-acquired merge locks
#2 - old merge without merge locks
#3 - nested units of work
#4 - no identity map, cache identity map, cleared identity map
#5 - objects read or merged into uow not in shared cache
#6 - merging references to objects not in shared cache
#7 - merging references to detached objects
#8 - merging into protected cache
#9 - grid cache
*/
ObjectBuilder objectBuilder = descriptor.getObjectBuilder();
// This always finds an original different from the clone, even if it has to create one.
// This must be done after special cases have been computed because it registers unregistered new objects.
// First check the cache key.
Object original = null;
CacheKey cacheKey = null;
// First check the change set, all new or changed object should have a change set with an active cache key.
// The client session check is for the protected cache support, the active cache key is from the server session, so protected cache cannot use it
if ((!targetSession.isClientSession() || !descriptor.getCachePolicy().isProtectedIsolation()) && (objectChangeSet != null)) {
// #1 - Normal case, cache key is already locked.
cacheKey = objectChangeSet.getActiveCacheKey();
if (cacheKey != null) {
original = cacheKey.getObject();
}
}
ObjectBuilder builder = descriptor.getObjectBuilder();
Object implementation = builder.unwrapObject(clone, unitOfWork);
// If the cache key was missing check the cache.
// This occurs in the old merge, or if a new or changed object references an existing object that needs to be merged.
if (cacheKey == null) {
// #2 - old merge, #3 - nested, #6 referenced objects, #7 detached objects, #8 protected cache
cacheKey = targetSession.getCacheKeyFromTargetSessionForMerge(implementation, builder, descriptor, this);
if (cacheKey != null){
original = cacheKey.getObject();
}
}
// FullyMergeEntity is a special grid cache flag.
// The original will be null for new objects, objects merged or read into the uow, or referenced objects that are not in the cache.
if (original == null) {
// #1, #2, #3 new objects
original = unitOfWork.getOriginalVersionOfObjectOrNull(clone, objectChangeSet, descriptor, targetSession);
// Original was not in cache. Make sure it is placed in the cache.
if (original != null) {
if (cacheKey == null) {
// #2, 4, 6, 7 - This occurs if the object was removed from the cache, or No/CacheIdentityMap, and using old merge, or a referenced object.
cacheKey = targetSession.getIdentityMapAccessorInstance().getWriteLockManager().appendLock(descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, targetSession), original, descriptor, this, targetSession);
} else {
if (cacheKey.getObject() != null){
original = cacheKey.getObject();
} else {
cacheKey.setObject(original);
}
}
} else {
// #1 No original, there is only an original if registerObject is used, registerNewObject or JPA never have an original.
}
}
// Always merge into the original.
try {
if (original == null) {
// #1, 2, 3, 9, 4, 5, 6
// If original does not exist then we must merge the entire object.
// This occurs when an object is new, was merged or read into the unit of work and, or referenced objects is not in the shared cache.
original = unitOfWork.buildOriginal(clone);
if (objectChangeSet == null) {
// #6 - references to uncached objects
// No changeset so this would not have been locked as part of the unit of work acquireLocks, so must append a lock.
// This should only occur when a changed or new object references another object that is not in the cache.
cacheKey = targetSession.getIdentityMapAccessorInstance().getWriteLockManager().appendLock(descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, targetSession), original, descriptor, this, targetSession);
if (cacheKey.getObject() != null){
original = cacheKey.getObject();
} else {
cacheKey.setObject(original);
}
objectBuilder.mergeIntoObject(original, null, true, clone, this, targetSession, false, !descriptor.getCopyPolicy().buildsNewInstance(), true);
if (!unitOfWork.isObjectRegistered(clone)){
// mark the instance in the cache as invalid as we may have just merged a stub if
// a detached stub was referenced by a managed entity
cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID);
}
} else {
// #1, 2, 3, new objects, #9 grid
if (cacheKey == null) {
// #2 - The cache key should only be null for the old merge.
cacheKey = targetSession.getIdentityMapAccessorInstance().getWriteLockManager().appendLock(objectChangeSet.getId(), original, descriptor, this, targetSession);
}
if (cacheKey.getObject() != null){
original = cacheKey.getObject();
} else {
// #1, 2, 3, new objects
cacheKey.setObject(original);
}
if (!objectChangeSet.isNew()) {
// #5 read in uow, #9 grid
//Bug#465051 : fetchGroupManager needs to be set with fetchGroup so that subsequent access can determine if lazy basics were fetched
objectBuilder.mergeIntoObject(original, objectChangeSet, true, clone, this, targetSession, false, !descriptor.getCopyPolicy().buildsNewInstance(), true);
if (!unitOfWork.isObjectRegistered(clone)){
// mark the instance in the cache as invalid as we may have just merged a stub if
// a detached stub was referenced by a managed entity
cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID);
}
} else {
objectBuilder.mergeChangesIntoObject(original, objectChangeSet, clone, this, targetSession, !descriptor.getCopyPolicy().buildsNewInstance(), true);
// PERF: If PersistenceEntity is caching the primary key this must be cleared as the primary key may have changed in new objects.
}
}
if (original instanceof PersistenceEntity) {
Object pk = cacheKey.getKey();
objectBuilder.updateCachedAttributes((PersistenceEntity) original, cacheKey, pk);
}
updateCacheKeyProperties(unitOfWork, cacheKey, original, clone, objectChangeSet, descriptor);
} else if (objectChangeSet == null) {
// #6, 7 - referenced objects
// PERF: If we have no change set and it has an original, then no merging is required, just use the original object.
} else if (descriptor.getFullyMergeEntity() && objectChangeSet.hasChanges()){
objectBuilder.mergeIntoObject(original, objectChangeSet, false, clone, this, targetSession, false, false, true);
} else {
// #1, 2, 3 existing objects, new objects with originals
// Regardless if the object is new, old, valid or invalid, merging will ensure there is a stub of an object in the
// shared cache for filling in foreign reference relationships. If merge did not occur in some cases (new objects, garbage
// collection objects, object read in a transaction) then no object would be in the shared cache and foreign reference
// mappings would be set to null when they should be set to an object.
if (objectChangeSet.hasChanges()) {
// #1, 2, 3 existing objects, new objects with originals
// Only attempt to invalidate if we would have merged. This saves us from a potential deadlock on get
// writeLockValue when we do not own the lock.
if (!objectChangeSet.isNew()) {
if (objectChangeSet.shouldInvalidateObject(original, targetSession) && (!unitOfWork.isNestedUnitOfWork())) {
// Invalidate any object that was marked invalid during the change calculation, even if it was new as multiple flushes
// and custom SQL could still produce invalid new objects. ? This seems to contradict the new check?
targetSession.getIdentityMapAccessor().invalidateObject(original);
// no need to update cacheKey properties here
}
} else {
// PERF: If PersistenceEntity is caching the primary key this must be cleared as the primary key may have changed in new objects.
if (original instanceof PersistenceEntity) {
Object pk = null;
if (cacheKey == null){
pk = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(original, unitOfWork);
}else{
pk = cacheKey.getKey();
}
objectBuilder.updateCachedAttributes((PersistenceEntity) original, cacheKey, pk);
}
}
// #1, 2, 3, merge from the change set into the existing cached object, or new original
// Note for new objects the change set may be empty, and object builder may merge from the clone into the original.
objectBuilder.mergeChangesIntoObject(original, objectChangeSet, clone, this, targetSession, false, objectChangeSet.isNew());
updateCacheKeyProperties(unitOfWork, cacheKey, original, clone, objectChangeSet, descriptor);
} else {
// #6, 7 - reference object, but object was in shared cache, and had no changed, so just use the reference.
// What if the original object feel out of the cache? i.e. #4 case, the object being put back in may have detached references.
// If there are no changes then we just need a reference to the object so skip the merge
// saves trying to lock related objects after the fact producing deadlocks
}
}
} catch (QueryException exception) {
// Ignore validation errors if unit of work validation is suppressed.
// Also there is a very specific case under EJB wrappering where
// a related object may have never been accessed in the unit of work context
// but is still valid, so this error must be ignored.
if (unitOfWork.shouldPerformNoValidation() || (descriptor.hasWrapperPolicy())) {
if ((exception.getErrorCode() != QueryException.BACKUP_CLONE_DELETED) && (exception.getErrorCode() != QueryException.BACKUP_CLONE_IS_ORIGINAL_FROM_PARENT) && (exception.getErrorCode() != QueryException.BACKUP_CLONE_IS_ORIGINAL_FROM_SELF)) {
throw exception;
}
return cacheKey;
} else {
throw exception;
}
}
return cacheKey;
}
/**
* Recursively merge changes in the object dependent on the merge policy.
* This merges changes from a remote unit of work back from the server into
* the original remote unit of work. This is meant to merge server-side changes
* such as sequence numbers, version numbers or events triggered changes.
*/
public Object mergeChangesOfWorkingCopyIntoRemote(Object clone) throws ValidationException {
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)this.session;
// This will return the object from the parent unit of work (original unit of work).
Object original = unitOfWork.getOriginalVersionOfObject(clone);
// The original is used as the backup to merge everything different from it.
// This makes this type of merge quite different than the normal unit of work merge.
ClassDescriptor descriptor = unitOfWork.getDescriptor(clone);
// Toggle change tracking during the merge.
descriptor.getObjectChangePolicy().dissableEventProcessing(original);
try {
descriptor.getObjectBuilder().mergeIntoObject(original, false, clone, this, this.session);
} finally {
descriptor.getObjectChangePolicy().enableEventProcessing(original);
}
if (((RemoteUnitOfWork)unitOfWork.getParent()).getUnregisteredNewObjectsCache().contains(original)) {
// Can use a new instance as backup and original.
Object backupClone = descriptor.getObjectBuilder().buildNewInstance();
Object newInstance = descriptor.getObjectBuilder().buildNewInstance();
((UnitOfWorkImpl)unitOfWork.getParent()).registerOriginalNewObjectFromNestedUnitOfWork(original, backupClone, newInstance, descriptor);
}
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, unitOfWork);
// Must ensure the get and put of the cache occur as a single operation.
// Cache key hold a reference to a concurrency manager which is used for the lock/release operation
CacheKey cacheKey = unitOfWork.getParent().getIdentityMapAccessorInstance().acquireLock(primaryKey, original.getClass(), descriptor, false);
try {
if (descriptor.usesOptimisticLocking()) {
cacheKey.setObject(original);
cacheKey.setWriteLockValue(unitOfWork.getIdentityMapAccessor().getWriteLockValue(original));
} else {
// Always put in the parent im for root because it must now be persistent.
cacheKey.setObject(original);
}
} finally {
cacheKey.updateAccess();
cacheKey.release();
}
return clone;
}
/**
* This can be used by the user for merging clones from RMI into the unit of work.
*/
public void mergeCloneIntoWorkingCopy() {
setMergePolicy(CLONE_INTO_WORKING_COPY);
}
/**
* This is used during the merge of dependent objects referencing independent objects, where you want
* the independent objects merged as well.
*/
public void mergeCloneWithReferencesIntoWorkingCopy() {
setMergePolicy(CLONE_WITH_REFS_INTO_WORKING_COPY);
}
/**
* This is used during cache synchronization to merge the changes into the distributed cache.
*/
public void mergeIntoDistributedCache() {
setMergePolicy(CHANGES_INTO_DISTRIBUTED_CACHE);
}
/**
* Merge a change set for a new object into the cache. This method will create a
* shell for the new object and then merge the changes from the change set into the object.
* The newly merged object will then be added to the cache.
*/
public Object mergeNewObjectIntoCache(ObjectChangeSet changeSet) {
Class localClassType = changeSet.getClassType(session);
ClassDescriptor descriptor = this.session.getDescriptor(localClassType);
Object newObject = null;
if (!isAlreadyMerged(changeSet, this.session)) {
// if we haven't merged this object already then build a new object
// otherwise leave it as null which will stop the recursion
newObject = descriptor.getObjectBuilder().buildNewInstance();
// store the changeset to prevent us from creating this new object again
recordMerge(changeSet, newObject, this.session);
} else {
//we have all ready created the object, must be in a cyclic
//merge on a new object so get it out of the alreadymerged collection
newObject = this.objectsAlreadyMerged.get(this.session).get(changeSet);
}
mergeChanges(newObject, changeSet, this.session);
return newObject;
}
/**
* This is used to revert changes to objects, or during refreshes.
*/
public void mergeOriginalIntoWorkingCopy() {
setMergePolicy(ORIGINAL_INTO_WORKING_COPY);
}
/**
* This is used during the unit of work commit to merge changes into the parent.
*/
public void mergeWorkingCopyIntoOriginal() {
setMergePolicy(WORKING_COPY_INTO_ORIGINAL);
}
/**
* This is used during the unit of work commit to merge changes into the parent.
*/
public void mergeWorkingCopyIntoRemote() {
setMergePolicy(WORKING_COPY_INTO_REMOTE);
}
/**
* INTERNAL:
* This is used to refresh remote session object
*/
public void refreshRemoteObject() {
setMergePolicy(REFRESH_REMOTE_OBJECT);
}
/**
* INTERNAL:
* When merging from a clone when the cache cannot be guaranteed the object must be first read if it is existing
* and not in the cache. Otherwise no changes will be detected as the original state is missing.
*/
protected Object registerObjectForMergeCloneIntoWorkingCopy(Object clone, boolean shouldForceCascade) {
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)this.session;
ClassDescriptor descriptor = unitOfWork.getDescriptor(clone.getClass());
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, unitOfWork, true);
// Must use the java class as this may be a bean that we are merging and it may not have the same class as the
// objects in the cache. As of EJB 2.0.
Object objectFromCache = null;
if (primaryKey != null) {
objectFromCache = unitOfWork.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, null, descriptor.getJavaClass(), false, descriptor);
}
if (objectFromCache == null) {
// Ensure we return the working copy if this has already been registered.
objectFromCache = unitOfWork.checkIfAlreadyRegistered(clone, descriptor);
}
if (objectFromCache != null) {
// gf830 - merging a removed entity should throw exception.
if (!isForRefresh && unitOfWork.isObjectDeleted(objectFromCache)) {
if (shouldMergeCloneIntoWorkingCopy() || shouldMergeCloneWithReferencesIntoWorkingCopy()) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new Object[] { clone }));
}
}
return objectFromCache;
}
DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery();
// Optimize cache option to avoid executing the does exist query.
if (existQuery.shouldCheckCacheForDoesExist()) {
checkNewObjectLockVersion(clone, primaryKey, descriptor, unitOfWork);
Object registeredObject = unitOfWork.internalRegisterObject(clone, descriptor, false);
if (unitOfWork.hasNewObjects() && unitOfWork.getNewObjectsOriginalToClone().containsKey(clone)) {
this.mergedNewObjects.put(registeredObject, registeredObject);
}
return registeredObject;
}
// Check early return to check if it is a new object, i.e. null primary key.
Boolean doesExist = Boolean.FALSE;
if (primaryKey != null) {
doesExist = (Boolean)existQuery.checkEarlyReturn(clone, primaryKey, unitOfWork, null);
}
if (doesExist == Boolean.FALSE) {
checkNewObjectLockVersion(clone, primaryKey, descriptor, unitOfWork);
Object registeredObject = unitOfWork.internalRegisterObject(clone, descriptor, shouldForceCascade);//should use cloneAndRegisterNewObject to avoid the exist check
this.mergedNewObjects.put(registeredObject, registeredObject);
return registeredObject;
}
// Otherwise it is existing and not in the cache so it must be read.
Object object = unitOfWork.readObject(clone);
if (object == null) {
checkNewObjectLockVersion(clone, primaryKey, descriptor, unitOfWork);
//bug6180972: avoid internal register's existence check and be sure to put the new object in the mergedNewObjects collection
object = unitOfWork.cloneAndRegisterNewObject(clone, shouldForceCascade);
this.mergedNewObjects.put(object, object);
}
return object;
}
/**
* Check if the new object's version has been set, if so, then it was an existing object that was deleted.
* Raise an error instead of reincarnating the object.
*/
public void checkNewObjectLockVersion(Object clone, Object primaryKey, ClassDescriptor descriptor, UnitOfWorkImpl unitOfWork) {
//bug272704: throw an exception if this object is new yet has a version set to avoid merging in deleted objects
if (descriptor.usesVersionLocking()){
VersionLockingPolicy policy = (VersionLockingPolicy)descriptor.getOptimisticLockingPolicy();
Object baseValue = policy.getBaseValue();
Object objectLockValue = policy.getWriteLockValue(clone, primaryKey, unitOfWork);
if (policy.isNewerVersion(objectLockValue, baseValue)) {
throw OptimisticLockException.objectChangedSinceLastMerge(clone);
}
}
}
/**
* Determine if the object is a registered new object, and that this is a nested unit of work
* merge into the parent. In this case private mappings will register the object as being removed.
*/
public void registerRemovedNewObjectIfRequired(Object removedObject) {
if (this.session.isUnitOfWork()) {
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)this.session;
if (shouldMergeWorkingCopyIntoOriginal() && unitOfWork.getParent().isUnitOfWork() && unitOfWork.isCloneNewObject(removedObject)) {
Object originalVersionOfRemovedObject = unitOfWork.getOriginalVersionOfObject(removedObject);
unitOfWork.addRemovedObject(originalVersionOfRemovedObject);
}
}
}
public void setCascadePolicy(int cascadePolicy) {
this.cascadePolicy = cascadePolicy;
}
protected void setMergePolicy(int mergePolicy) {
this.mergePolicy = mergePolicy;
}
public void setForceCascade(boolean forceCascade) {
this.forceCascade = forceCascade;
}
public void setObjectDescriptors(Map objectDescriptors) {
this.objectDescriptors = objectDescriptors;
}
protected void setObjectsAlreadyMerged(Map objectsAlreadyMerged) {
this.objectsAlreadyMerged = objectsAlreadyMerged;
}
/**
* INTENRAL:
* Used to set the node that this merge manager is stored in, within the WriteLocksManager write lockers queue
*/
public void setQueueNode(LinkedNode node) {
this.queueNode = node;
}
protected void setSession(AbstractSession session) {
this.session = session;
}
/**
* INTENRAL:
* Used to set the object that the merge manager is waiting on, in order to acquire locks
* If this value is null then the merge manager is not waiting on any locks.
*/
public void setWriteLockQueued(Object primaryKey) {
this.writeLockQueued = primaryKey;
}
/**
* Flag used to determine that the mappings should be checked for
* cascade requirements.
*/
public boolean shouldCascadeByMapping() {
return getCascadePolicy() == CASCADE_BY_MAPPING;
}
/**
* Flag used to determine if all parts should be cascaded
*/
public boolean shouldCascadeAllParts() {
return getCascadePolicy() == CASCADE_ALL_PARTS;
}
/**
* Flag used to determine if any parts should be cascaded
*/
public boolean shouldCascadeParts() {
return getCascadePolicy() != NO_CASCADE;
}
/**
* Flag used to determine if any private parts should be cascaded
*/
public boolean shouldCascadePrivateParts() {
return (getCascadePolicy() == CASCADE_PRIVATE_PARTS) || (getCascadePolicy() == CASCADE_ALL_PARTS);
}
/**
* Refreshes are based on the objects row, so all attributes of the object must be refreshed.
* However merging from RMI, normally reference are made transient, so should not be merge unless
* specified.
*/
public boolean shouldCascadeReferences() {
return !shouldMergeCloneIntoWorkingCopy() || isForRefresh;
}
/**
* INTERNAL:
* This happens when changes from an UnitOfWork is propagated to a distributed class.
*/
public boolean shouldMergeChangesIntoDistributedCache() {
return getMergePolicy() == CHANGES_INTO_DISTRIBUTED_CACHE;
}
/**
* This can be used by the user for merging clones from RMI into the unit of work.
*/
public boolean shouldMergeCloneIntoWorkingCopy() {
return getMergePolicy() == CLONE_INTO_WORKING_COPY;
}
/**
* This can be used by the user for merging remote EJB objects into the unit of work.
*/
public boolean shouldMergeCloneWithReferencesIntoWorkingCopy() {
return getMergePolicy() == CLONE_WITH_REFS_INTO_WORKING_COPY;
}
/**
* This is used to revert changes to objects, or during refreshes.
*/
public boolean shouldMergeOriginalIntoWorkingCopy() {
return getMergePolicy() == ORIGINAL_INTO_WORKING_COPY;
}
/**
* This is used during the unit of work commit to merge changes into the parent.
*/
public boolean shouldMergeWorkingCopyIntoOriginal() {
return getMergePolicy() == WORKING_COPY_INTO_ORIGINAL;
}
/**
* INTERNAL:
* This happens when serialized remote unit of work has to be merged with local remote unit of work.
*/
public boolean shouldMergeWorkingCopyIntoRemote() {
return getMergePolicy() == WORKING_COPY_INTO_REMOTE;
}
/**
* INTERNAL:
* This is used to refresh objects on the remote session
*/
public boolean shouldRefreshRemoteObject() {
return getMergePolicy() == REFRESH_REMOTE_OBJECT;
}
/**
* This is used to cascade merge even if a clone is already registered.
*/
public boolean shouldForceCascade() {
return forceCascade;
}
/**
* INTERNAL:
* Used to return a map containing new objects found through the
* registerObjectForMergeCloneIntoWorkingCopy method.
* @return Map
*/
public IdentityHashMap getMergedNewObjects(){
return mergedNewObjects;
}
/**
* INTERNAL:
* Records that this merge manager has transitioned to use deferred locks during the merge.
*/
public void transitionToDeferredLocks(){
this.isTransitionedToDeferredLocks = true;
}
/**
* INTERNAL:
* Update CacheKey properties with new information. This method is called if this code
* actually merges
*/
protected void updateCacheKeyProperties(UnitOfWorkImpl unitOfWork, CacheKey cacheKey, Object original, Object clone, ObjectChangeSet objectChangeSet, ClassDescriptor descriptor){
if (!unitOfWork.isNestedUnitOfWork()) {
boolean locked = false;
// The cache key should never be null for the new commit, but may be for old commit, or depending on the cache isolation level may not be locked,
// so needs to be re-acquired.
if (cacheKey == null || !cacheKey.isAcquired()) {
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(original, unitOfWork);
cacheKey = unitOfWork.getParent().getIdentityMapAccessorInstance().acquireLockNoWait(primaryKey, original.getClass(), false, descriptor);
locked = cacheKey != null;
}
if (cacheKey != null){ // only work if we are locked.
try {
if (descriptor.usesOptimisticLocking() && descriptor.getOptimisticLockingPolicy().isStoredInCache()) {
cacheKey.setWriteLockValue(unitOfWork.getIdentityMapAccessor().getWriteLockValue(clone));
}
cacheKey.setObject(original);
if (descriptor.getCacheInvalidationPolicy().shouldUpdateReadTimeOnUpdate() || ((objectChangeSet != null) && objectChangeSet.isNew())) {
cacheKey.setReadTime(getSystemTime());
}
cacheKey.updateAccess();
} finally {
if (locked) {
cacheKey.release();
}
}
}
}
}
/**
* INTERNAL:
* @return lockThread
*/
public Thread getLockThread() {
return lockThread;
}
/**
* INTERNAL:
* Save the currentThread for later comparison to the activeThread in case they don't match
*/
public void setLockThread(Thread lockThread) {
this.lockThread = lockThread;
}
}