| /* |
| * 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<Object, ObjectDescriptor> objectDescriptors; |
| |
| /** Used to unravel recursion. */ |
| protected Map<AbstractSession, Map<Object, Object>> objectsAlreadyMerged; |
| |
| /** Used to keep track of merged new objects. */ |
| protected IdentityHashMap<Object, Object> 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<Object, ObjectDescriptor> getObjectDescriptors() { |
| if (this.objectDescriptors == null) { |
| this.objectDescriptors = new IdentityHashMap<>(); |
| } |
| return this.objectDescriptors; |
| } |
| |
| public Map<AbstractSession, Map<Object, Object>> 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<Object, Object> 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<Object, Object> sessionMap = this.objectsAlreadyMerged.get(targetSession); |
| if (sessionMap == null){ |
| return false; |
| } |
| return sessionMap.containsKey(object); |
| } |
| |
| public Object getMergedObject(Object key, AbstractSession targetSession){ |
| Map<Object, Object> 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 = 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 = 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<ObjectChangeSet> objectChangeEnum = uowChangeSet.getAllChangeSets().keySet().iterator(); |
| Set<Class<?>> classesChanged = new HashSet<>(); |
| while (objectChangeEnum.hasNext()) { |
| 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<ObjectChangeSet> deletedObjects = uowChangeSet.getDeletedObjects().values().iterator(); |
| while (deletedObjects.hasNext()) { |
| ObjectChangeSet changeSet = 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<Object, ObjectDescriptor> objectDescriptors) { |
| this.objectDescriptors = objectDescriptors; |
| } |
| |
| protected void setObjectsAlreadyMerged(Map<AbstractSession, Map<Object, Object>> 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<Object, Object> 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; |
| } |
| } |