| /* |
| * 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 |
| // 05/28/2008-1.0M8 Andrei Ilitchev |
| // - 224964: Provide support for Proxy Authentication through JPA. |
| // The class was amended to allow it to instantiate ValueHolders after release method has been called |
| // (internalExecuteQuery method no longer throws exception if the uow is dead). |
| // Note that release method clears change sets but keeps the cache. |
| // 02/11/2009-1.1 Michael O'Brien |
| // - 259993: 1) Defer a full clear(true) call from entityManager.clear() to release() |
| // only if uow lifecycle is 1,2 or 4 (//Pending) and perform a clear of the cache only in this case. |
| // 2) During mergeClonesAfterCompletion() If the the acquire and release threads are different |
| // switch back to the stored acquire thread stored on the mergeManager. |
| // 17/04/2009-1.1 Michael O'Brien |
| // - 272022: For rollback scenarios - If the current thread and the active thread |
| // on the mutex do not match for read locks (not yet transitioned to deferred locks) - switch them |
| // 07/16/2009-2.0 Guy Pelletier |
| // - 277039: JPA 2.0 Cache Usage Settings |
| // 07/15/2011-2.2.1 Guy Pelletier |
| // - 349424: persists during an preCalculateUnitOfWorkChangeSet event are lost |
| // 14/05/2012-2.4 Guy Pelletier |
| // - 376603: Provide for table per tenant support for multitenant applications |
| // 08/11/2012-2.5 Guy Pelletier |
| // - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy. |
| // 09/03/2015 - Will Dazey |
| // - 456067 : Added support for defining query timeout units |
| // 01/29/2019-3.0 Sureshkumar Balakrishnan |
| // - 541873: ENTITYMANAGER.DETACH() TRIGGERS LAZY LOADING INTO THE PERSISTENCE CONTEXT |
| package org.eclipse.persistence.internal.sessions; |
| |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| 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 java.util.Vector; |
| |
| import org.eclipse.persistence.annotations.CacheKeyType; |
| import org.eclipse.persistence.config.ReferenceMode; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.DescriptorEvent; |
| import org.eclipse.persistence.descriptors.DescriptorEventManager; |
| import org.eclipse.persistence.descriptors.changetracking.AttributeChangeTrackingPolicy; |
| import org.eclipse.persistence.descriptors.changetracking.ObjectChangePolicy; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.EclipseLinkException; |
| import org.eclipse.persistence.exceptions.OptimisticLockException; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.indirection.ValueHolderInterface; |
| import org.eclipse.persistence.internal.databaseaccess.Accessor; |
| import org.eclipse.persistence.internal.databaseaccess.DatasourceAccessor; |
| import org.eclipse.persistence.internal.databaseaccess.Platform; |
| import org.eclipse.persistence.internal.descriptors.CascadeLockingPolicy; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator.CascadeCondition; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.descriptors.PersistenceEntity; |
| import org.eclipse.persistence.internal.helper.ConcurrencyManager; |
| import org.eclipse.persistence.internal.helper.ConcurrencyUtil; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.helper.IdentityHashSet; |
| import org.eclipse.persistence.internal.helper.IdentityWeakHashMap; |
| import org.eclipse.persistence.internal.identitymaps.CacheId; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.identitymaps.IdentityMapManager; |
| import org.eclipse.persistence.internal.indirection.DatabaseValueHolder; |
| import org.eclipse.persistence.internal.indirection.UnitOfWorkQueryValueHolder; |
| import org.eclipse.persistence.internal.indirection.UnitOfWorkTransformerValueHolder; |
| import org.eclipse.persistence.internal.localization.ExceptionLocalization; |
| import org.eclipse.persistence.internal.localization.LoggingLocalization; |
| import org.eclipse.persistence.internal.sequencing.Sequencing; |
| import org.eclipse.persistence.logging.AbstractSessionLog; |
| import org.eclipse.persistence.logging.SessionLog; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping; |
| import org.eclipse.persistence.platform.server.ServerPlatform; |
| import org.eclipse.persistence.queries.Call; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.DeleteObjectQuery; |
| import org.eclipse.persistence.queries.DoesExistQuery; |
| import org.eclipse.persistence.queries.InMemoryQueryIndirectionPolicy; |
| import org.eclipse.persistence.queries.ModifyAllQuery; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadObjectQuery; |
| import org.eclipse.persistence.queries.ReadQuery; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.sessions.Session; |
| import org.eclipse.persistence.sessions.SessionProfiler; |
| import org.eclipse.persistence.sessions.coordination.MergeChangeSetCommand; |
| |
| /** |
| * Implementation of org.eclipse.persistence.sessions.UnitOfWork |
| * The public interface should be used. |
| * @see org.eclipse.persistence.sessions.UnitOfWork |
| * <p> |
| * <b>Purpose</b>: To allow object level transactions. |
| * <p> |
| * <b>Description</b>: The unit of work is a session that implements all of the normal |
| * protocol of an EclipseLink session. It can be spawned from any other session including another unit of work. |
| * Objects can be brought into the unit of work through reading them or through registering them. |
| * The unit of work will operate on its own object space, that is the objects within the unit of work |
| * will be clones of the original objects. When the unit of work is committed, all changes to any objects |
| * registered within the unit of work will be committed to the database. A minimal commit/update will |
| * be performed and any foreign keys/circular reference/referential integrity will be resolved. |
| * If the commit to the database is successful the changed objects will be merged back into the unit of work |
| * parent session. |
| * <p> |
| * <b>Responsibilities</b>: |
| * <ul> |
| * <li> Allow parallel transactions against a session's objects. |
| * <li> Allow nested transactions. |
| * <li> Not require the application to write objects that is changes, automatically determine what has changed. |
| * <li> Perform a minimal commit/update of all changes that occurred. |
| * <li> Resolve foreign keys for newly created objects and maintain referential integrity. |
| * <li> Allow for the object transaction to use its own object space. |
| * </ul> |
| * |
| */ |
| public class UnitOfWorkImpl extends AbstractSession implements org.eclipse.persistence.sessions.UnitOfWork { |
| |
| //These constants and variables are used in extended thread logging to compare UnitOfWork creation thread and thread which registering object in UnitOfWork |
| public final long CREATION_THREAD_ID = Thread.currentThread().getId(); |
| public final String CREATION_THREAD_NAME = String.copyValueOf(Thread.currentThread().getName().toCharArray()); |
| public final long CREATION_THREAD_HASHCODE = Thread.currentThread().hashCode(); |
| private String creationThreadStackTrace; |
| |
| /** Fix made for weak caches to avoid garbage collection of the originals. **/ |
| /** As well as used as lookup in merge algorithm for aggregates and others **/ |
| protected transient Map<Object, Object> cloneToOriginals; |
| protected transient AbstractSession parent; |
| |
| /** Map of all the clones. The key contains the clone of the object. */ |
| protected Map<Object, Object> cloneMapping; |
| protected Map<Object, Object> newObjectsCloneToOriginal; |
| protected Map<Object, Object> newObjectsOriginalToClone; |
| /** |
| * Stores a map from the clone to the original merged object, as a different instance is used as the original for merges. |
| */ |
| protected Map<Object, Object> newObjectsCloneToMergeOriginal; |
| protected Map<Object, Object> deletedObjects; |
| |
| /** This member variable contains a copy of all of the clones for this particular UOW */ |
| protected Map<Object, Object> allClones; |
| protected Map<Object, Object> objectsDeletedDuringCommit; |
| protected Map<Object, Object> removedObjects; |
| protected Map<Object, Object> unregisteredNewObjects; |
| protected Map<Object, Object> unregisteredNewObjectsInParent; |
| protected Map<Object, Object> unregisteredExistingObjects; |
| |
| // bug # 3228185 |
| // this collection is used to store the new objects from the parent. |
| // They will not be treated as new in the nested unit of work, so we must |
| //store them somewhere specifically to lookup later. |
| protected Map<Object, Object> newObjectsInParentOriginalToClone; |
| |
| /** Cache references of private owned objects for the removal of private owned orphans */ |
| protected Map<DatabaseMapping, Set> privateOwnedObjects; |
| |
| /** used to store a list of the new objects in the parent */ |
| |
| //cr 2783 |
| protected Map<Object, Object> newObjectsInParent; |
| protected Map<Object, Object> newAggregates; |
| |
| /** This method is used to store the current changeSet for this UnitOfWork. */ |
| protected UnitOfWorkChangeSet unitOfWorkChangeSet; |
| |
| /** This is only used for EJB entity beans to manage beans accessed in a transaction context. */ |
| protected UnitOfWorkImpl containerUnitOfWork; |
| protected Map<Object, Object> containerBeans; |
| |
| /** use to track pessimistic locked objects */ |
| protected Map<Object, Object> pessimisticLockedObjects; |
| |
| /** Used to store the list of locks that this UnitOfWork has acquired for this merge */ |
| protected transient MergeManager lastUsedMergeManager; |
| |
| /** |
| * When in transaction batch read objects must use query local |
| * to the unit of work. |
| */ |
| protected Map<ReadQuery, ReadQuery> batchQueries; |
| |
| /** Read-only class can be used for reference data to avoid cloning when not required. */ |
| protected Set<Class> readOnlyClasses; |
| |
| /** Flag indicating that the transaction for this UOW was already begun. */ |
| protected boolean wasTransactionBegunPrematurely; |
| |
| /** Allow for double merges of new objects by putting them into the cache. */ |
| protected boolean shouldNewObjectsBeCached; |
| |
| /** Flag indicating that deletes should be performed before other updates. */ |
| protected boolean shouldPerformDeletesFirst; |
| |
| /** Flag indicating how to deal with exceptions on conforming queries. **/ |
| protected int shouldThrowConformExceptions; |
| |
| /** The amount of validation can be configured. */ |
| protected int validationLevel; |
| static public final int None = 0; |
| static public final int Partial = 1; |
| static public final int Full = 2; |
| |
| /** |
| * With the new synchronized unit of work, need a lifecycle state variable to |
| * track birth, committed, pending_merge and death. |
| */ |
| protected int lifecycle; |
| public static final int Birth = 0; |
| public static final int CommitPending = 1; |
| |
| // After a call to writeChanges() but before commit. |
| public static final int CommitTransactionPending = 2; |
| |
| // After an unsuccessful call to writeChanges(). No recovery at all. |
| public static final int WriteChangesFailed = 3; |
| public static final int MergePending = 4; |
| public static final int Death = 5; |
| public static final int AfterExternalTransactionRolledBack = 6; |
| |
| /** Used for Conforming Queries */ |
| public static final int DO_NOT_THROW_CONFORM_EXCEPTIONS = 0; |
| public static final int THROW_ALL_CONFORM_EXCEPTIONS = 1; |
| |
| //CR 3677 removed option to only throw valueHolderExceptions as this governed by |
| //the InMemoryQueryIndirectionPolicy |
| public static final String LOCK_QUERIES_PROPERTY = "LockQueriesProperties"; |
| |
| /** Used for merging dependent values without use of WL SessionAccessor */ |
| protected static boolean SmartMerge = false; |
| |
| /** Kept reference of read lock objects*/ |
| protected Map<Object, Object> optimisticReadLockObjects; |
| |
| /** Used for read lock to determine update the version field with the same value or increment value */ |
| public static final String ReadLockOnly = "no update"; |
| public static final String ReadLockUpdateVersion = "update version"; |
| |
| /** lazy initialization done in storeModifyAllQuery. For UpdateAllQuery, only clones of all UpdateAllQuery's (deferred and non-deferred) are stored here for validation only.*/ |
| protected List<ModifyAllQuery> modifyAllQueries; |
| |
| /** |
| * Contains deferred ModifyAllQuery's that have translation row for execution only. |
| * At commit their clones will be added to modifyAllQueries for validation afterwards. |
| * Array of the query (ModifyAllQuery) and translationRow (AbstractRecord). |
| */ |
| //Bug4607551 |
| protected List<Object[]> deferredModifyAllQueries; |
| |
| /** |
| * Used during the cloning process to track the recursive depth in. This will |
| * be used to determine at which point the process can begin to wait on locks |
| * without being concerned about creating deadlock situations. |
| */ |
| protected int cloneDepth; |
| |
| /** |
| * PERF: Stores the JTA transaction to optimize activeUnitOfWork lookup. |
| */ |
| protected Object transaction; |
| |
| /** |
| * True if UnitOfWork should be resumed on completion of transaction. |
| * Used when UnitOfWork is Synchronized with external transaction control |
| */ |
| protected boolean resumeOnTransactionCompletion; |
| |
| /** |
| * PERF: Allows discover new objects to be skipped if app always calls persist. |
| */ |
| protected boolean shouldDiscoverNewObjects; |
| |
| /** |
| * True if either DataModifyQuery or ModifyAllQuery was executed. |
| * Gets reset on commit, effects DoesExistQuery behavior and reading. |
| */ |
| protected boolean wasNonObjectLevelModifyQueryExecuted; |
| |
| /** |
| * True if the value holder for the joined attribute should be triggered. |
| * Required by ejb30 fetch join. |
| */ |
| protected boolean shouldCascadeCloneToJoinedRelationship; |
| |
| /** PERF: Cache isNestedUnitOfWork check. */ |
| protected boolean isNestedUnitOfWork; |
| |
| /** Determine if does-exist should be performed on persist. */ |
| protected boolean shouldValidateExistence; |
| |
| /** Allow updates and deletes to be ordered by id or changes to avoid possible deadlocks. */ |
| protected CommitOrderType commitOrder; |
| |
| /** This stored the reference mode for this UOW. If the reference mode is |
| * weak then this unit of work will retain only weak references to non new, |
| * non-deleted objects allowing for garbage collection. If ObjectChangeTracking |
| * is used then any objects with changes will not be garbage collected. |
| */ |
| protected ReferenceMode referenceMode; |
| |
| // This is list is used during change tracking to keep hard references |
| // to changed objects that may otherwise have been garbage collected. |
| protected Set<Object> changeTrackedHardList; |
| |
| /** Used to store objects already deleted from the db and unregistered */ |
| protected Map<Object, Object> unregisteredDeletedObjectsCloneToBackupAndOriginal; |
| |
| /** This attribute records when the preDelete stage of Commit has completed */ |
| protected boolean preDeleteComplete; |
| |
| /** Stores all of the private owned objects that have been removed and may need to cascade deletion */ |
| protected Map<DatabaseMapping, List<Object>> deletedPrivateOwnedObjects; |
| |
| /** temporarily holds a reference to a merge manager that is calling this UnitOfWork during merge **/ |
| protected transient MergeManager mergeManagerForActiveMerge; |
| |
| /** Set of objects that were deleted by database cascade delete constraints. */ |
| protected Set<Object> cascadeDeleteObjects; |
| |
| /** |
| * Used to store deleted objects that have reference to other deleted objects. |
| * This is need to delete cycles of objects in the correct order. |
| */ |
| protected Map<Object, Set<Object>> deletionDependencies; |
| |
| /** |
| * INTERNAL: |
| */ |
| public UnitOfWorkImpl() { |
| } |
| |
| /** |
| * INTERNAL: |
| * Create and return a new unit of work with the session as its parent. |
| */ |
| public UnitOfWorkImpl(AbstractSession parent, ReferenceMode referenceMode) { |
| super(); |
| this.isLoggingOff = parent.isLoggingOff; |
| this.referenceMode = referenceMode; |
| this.shouldDiscoverNewObjects = true; |
| this.name = parent.name; |
| this.parent = parent; |
| this.project = parent.project; |
| this.profiler = parent.profiler; |
| this.isInProfile = parent.isInProfile; |
| this.sessionLog = parent.sessionLog; |
| if (parent.hasEventManager()) { |
| this.eventManager = parent.getEventManager().clone(this); |
| } |
| this.exceptionHandler = parent.exceptionHandler; |
| this.pessimisticLockTimeoutDefault = parent.pessimisticLockTimeoutDefault; |
| this.pessimisticLockTimeoutUnitDefault = parent.pessimisticLockTimeoutUnitDefault; |
| this.queryTimeoutDefault = parent.queryTimeoutDefault; |
| this.queryTimeoutUnitDefault = parent.queryTimeoutUnitDefault; |
| this.shouldOptimizeResultSetAccess = parent.shouldOptimizeResultSetAccess; |
| this.serializer = parent.serializer; |
| this.isConcurrent = parent.isConcurrent; |
| // Initialize the readOnlyClasses variable. |
| this.setReadOnlyClasses(parent.copyReadOnlyClasses()); |
| this.validationLevel = Partial; |
| |
| // for 3.0.x this conforming queries will not throw exceptions unless explicitly asked to |
| this.shouldThrowConformExceptions = DO_NOT_THROW_CONFORM_EXCEPTIONS; |
| |
| // initialize lifecycle state variable |
| this.lifecycle = Birth; |
| // PERF: Cache the write-lock check to avoid cost of checking in every register/clone. |
| this.isNestedUnitOfWork = parent.isUnitOfWork(); |
| |
| if (this.eventManager != null) { |
| this.eventManager.postAcquireUnitOfWork(); |
| } |
| this.descriptors = parent.getDescriptors(); |
| incrementProfile(SessionProfiler.UowCreated); |
| // PERF: Cache the write-lock check to avoid cost of checking in every register/clone. |
| this.shouldCheckWriteLock = parent.getDatasourceLogin().shouldSynchronizedReadOnWrite() || parent.getDatasourceLogin().shouldSynchronizeWrites(); |
| |
| // Order updates by id |
| this.commitOrder = CommitOrderType.ID; |
| |
| // Copy down the table per tenant information. |
| this.tablePerTenantDescriptors = parent.tablePerTenantDescriptors; |
| this.tablePerTenantQueries = parent.tablePerTenantQueries; |
| |
| // Init only if thread extended logging + thread dump is enabled |
| creationThreadStackTrace = project.allowExtendedThreadLoggingThreadDump() ? ConcurrencyUtil.SINGLETON.enrichGenerateThreadDumpForCurrentThread() : null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Acquires a special historical session for reading objects as of a past time. |
| */ |
| @Override |
| public org.eclipse.persistence.sessions.Session acquireHistoricalSession(org.eclipse.persistence.history.AsOfClause clause) throws ValidationException { |
| throw ValidationException.cannotAcquireHistoricalSession(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a nested unit of work for this unit of work. |
| * A nested unit of work can be used to isolate a subset of work on the unit of work, |
| * such as a dialog being open from an editor. The nested unit of work will only |
| * commit change to its objects to its parent unit of work, not the database. |
| * Only the parent unit of work will commit to the database. |
| * |
| * @see UnitOfWorkImpl |
| */ |
| @Override |
| public UnitOfWorkImpl acquireUnitOfWork() { |
| UnitOfWorkImpl uow = super.acquireUnitOfWork(); |
| uow.discoverAllUnregisteredNewObjectsInParent(); |
| |
| return uow; |
| } |
| |
| /** |
| * INTERNAL: |
| * Records a private owned object that has been de-referenced and will need to processed |
| * for related private owned objects. |
| */ |
| public void addDeletedPrivateOwnedObjects(DatabaseMapping mapping, Object object) |
| { |
| if(deletedPrivateOwnedObjects == null){ |
| deletedPrivateOwnedObjects = new IdentityHashMap(); |
| } |
| List<Object> list = deletedPrivateOwnedObjects.get(mapping); |
| if(list == null){ |
| list = new ArrayList<>(); |
| deletedPrivateOwnedObjects.put(mapping, list); |
| } |
| list.add(object); |
| } |
| |
| /** |
| * INTERNAL: |
| * Register a new aggregate object with the unit of work. |
| */ |
| public void addNewAggregate(Object originalObject) { |
| getNewAggregates().put(originalObject, originalObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add object deleted during root commit of unit of work. |
| */ |
| public void addObjectDeletedDuringCommit(Object object, ClassDescriptor descriptor) { |
| // The object's key is keyed on the object, this avoids having to compute the key later on. |
| getObjectsDeletedDuringCommit().put(object, keyFromObject(object, descriptor)); |
| //bug 4730595: changed to add deleted objects to the changesets. |
| ((UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).addDeletedObject(object, this); |
| } |
| |
| /** |
| * PUBLIC: |
| * Adds the given Java class to the receiver's set of read-only classes. |
| * Cannot be called after objects have been registered in the unit of work. |
| */ |
| @Override |
| public void addReadOnlyClass(Class theClass) throws ValidationException { |
| if (!canChangeReadOnlySet()) { |
| throw ValidationException.cannotModifyReadOnlyClassesSetAfterUsingUnitOfWork(); |
| } |
| |
| getReadOnlyClasses().add(theClass); |
| |
| ClassDescriptor descriptor = getDescriptor(theClass); |
| |
| // Also mark all subclasses as read-only. |
| if (descriptor.hasInheritance()) { |
| for (ClassDescriptor childDescriptor : descriptor.getInheritancePolicy().getChildDescriptors()) { |
| addReadOnlyClass(childDescriptor.getJavaClass()); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Adds the classes in the given Vector to the existing set of read-only classes. |
| * Cannot be called after objects have been registered in the unit of work. |
| */ |
| @Override |
| public void addReadOnlyClasses(Collection classes) { |
| for (Iterator iterator = classes.iterator(); iterator.hasNext();) { |
| Class theClass = (Class)iterator.next(); |
| addReadOnlyClass(theClass); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Register that an object was removed in a nested unit of work. |
| */ |
| public void addRemovedObject(Object orignal) { |
| getRemovedObjects().put(orignal, orignal);// Use as set. |
| } |
| |
| /** |
| * ADVANCED: |
| * Assign sequence number to the object. |
| * This allows for an object's id to be assigned before commit. |
| * It can be used if the application requires to use the object id before the object exists on the database. |
| * Normally all ids are assigned during the commit automatically. |
| */ |
| @Override |
| public void assignSequenceNumber(Object object) throws DatabaseException { |
| ClassDescriptor descriptor = getDescriptor(object); |
| Object implementation = descriptor.getObjectBuilder().unwrapObject(object, this); |
| assignSequenceNumber(implementation, descriptor); |
| } |
| |
| /** |
| * INTERNAL: |
| * Assign sequence number to the object. |
| */ |
| public Object assignSequenceNumber(Object object, ClassDescriptor descriptor) throws DatabaseException { |
| Object value = null; |
| |
| // This is done outside of a transaction to ensure optimal concurrency and deadlock avoidance in the sequence table. |
| if (descriptor.usesSequenceNumbers() && !descriptor.getSequence().shouldAcquireValueAfterInsert()) { |
| startOperationProfile(SessionProfiler.AssignSequence); |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| try { |
| value = builder.assignSequenceNumber(object, this); |
| } catch (RuntimeException exception) { |
| handleException(exception); |
| } finally { |
| endOperationProfile(SessionProfiler.AssignSequence); |
| } |
| } |
| |
| return value; |
| } |
| |
| /** |
| * ADVANCED: |
| * Assign sequence numbers to all new objects registered in this unit of work, |
| * or any new objects reference by any objects registered. |
| * This allows for an object's id to be assigned before commit. |
| * It can be used if the application requires to use the object id before the object exists on the database. |
| * Normally all ids are assigned during the commit automatically. |
| */ |
| @Override |
| public void assignSequenceNumbers() throws DatabaseException { |
| // This should be done outside of a transaction to ensure optimal concurrency and deadlock avoidance in the sequence table. |
| // discoverAllUnregisteredNewObjects() should be called no matter whether sequencing used |
| // or not, because collectAndPrepareObjectsForCommit() method (which calls assignSequenceNumbers()) |
| // needs it. |
| // It would be logical to remove discoverAllUnregisteredNewObjects() from assignSequenceNumbers() |
| // and make collectAndPrepareObjectsForCommit() to call discoverAllUnregisteredNewObjects() |
| // first and assignSequenceNumbers() next, |
| // but assignSequenceNumbers() is a public method which could be called by user - and |
| // in this case discoverAllUnregisteredNewObjects() is needed again (though |
| // if sequencing is not used the call will make no sense - but no harm, too). |
| discoverAllUnregisteredNewObjects(); |
| if (hasUnregisteredNewObjects()) { |
| assignSequenceNumbers(getUnregisteredNewObjects()); |
| } |
| if (hasNewObjects()) { |
| assignSequenceNumbers(getNewObjectsCloneToOriginal()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Assign sequence numbers to all of the objects. |
| * This allows for an object's id to be assigned before commit. |
| * It can be used if the application requires to use the object id before the object exists on the database. |
| * Normally all ids are assigned during the commit automatically. |
| */ |
| protected void assignSequenceNumbers(Map objects) throws DatabaseException { |
| if (objects.isEmpty()) { |
| return; |
| } |
| |
| Sequencing sequencing = getSequencing(); |
| if (sequencing == null) { |
| return; |
| } |
| int whenShouldAcquireValueForAll = sequencing.whenShouldAcquireValueForAll(); |
| if (whenShouldAcquireValueForAll == Sequencing.AFTER_INSERT) { |
| return; |
| } |
| boolean shouldAcquireValueBeforeInsertForAll = whenShouldAcquireValueForAll == Sequencing.BEFORE_INSERT; |
| startOperationProfile(SessionProfiler.AssignSequence); |
| Iterator newObjects = objects.keySet().iterator(); |
| while (newObjects.hasNext()) { |
| Object object = newObjects.next(); |
| ClassDescriptor descriptor = getDescriptor(object); |
| if (descriptor.usesSequenceNumbers() |
| && (shouldAcquireValueBeforeInsertForAll || !descriptor.getSequence().shouldAcquireValueAfterInsert())) { |
| descriptor.getObjectBuilder().assignSequenceNumber(object, this); |
| } |
| } |
| endOperationProfile(SessionProfiler.AssignSequence); |
| } |
| |
| /** |
| * PUBLIC: |
| * Tell the unit of work to begin a transaction now. |
| * By default the unit of work will begin a transaction at commit time. |
| * The default is the recommended approach, however sometimes it is |
| * necessary to start the transaction before commit time. When the |
| * unit of work commits, this transaction will be committed. |
| * |
| * @see #commit() |
| * @see #release() |
| */ |
| @Override |
| public void beginEarlyTransaction() throws DatabaseException { |
| beginTransaction(); |
| setWasTransactionBegunPrematurely(true); |
| } |
| |
| /** |
| * INTERNAL: |
| * This is internal to the uow, transactions should not be used explicitly in a uow. |
| * The uow shares its parents transactions. |
| */ |
| @Override |
| public void beginTransaction() throws DatabaseException { |
| this.parent.beginTransaction(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Unregistered new objects have no original so we must create one for commit and resume and |
| * to put into the parent. We can NEVER let the same copy of an object exist in multiple units of work. |
| */ |
| public Object buildOriginal(Object workingClone) { |
| ClassDescriptor descriptor = getDescriptor(workingClone); |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object original = builder.instantiateClone(workingClone, this); |
| |
| // If no original exists can mean any of the following: |
| // -A RemoteUnitOfWork and cloneToOriginals is transient. |
| // -A clone read while in transaction, and built directly from |
| // the database row with no intermediary original. |
| // -An unregistered new object |
| if (checkIfAlreadyRegistered(workingClone, descriptor) != null) { |
| getCloneToOriginals().put(workingClone, original); |
| return original; |
| } else { |
| // Assume it is an unregisteredNewObject, but this is worrisome, as |
| // it may be an unregistered existing object, not in the parent cache? |
| Object backup = builder.instantiateClone(workingClone, this); |
| |
| // Original is fine for backup as state is the same. |
| getCloneMapping().put(workingClone, backup); |
| |
| // Must register new instance / clone as the original. |
| getNewObjectsCloneToOriginal().put(workingClone, original); |
| getNewObjectsOriginalToClone().put(original, workingClone); |
| |
| // no need to register in identity map as the DatabaseQueryMechanism will have |
| //placed the object in the identity map on insert. bug 3431586 |
| } |
| return original; |
| } |
| |
| /** |
| * INTERNAL: |
| * <p> This calculates changes in two passes, first on registered objects, |
| * second it discovers unregistered new objects on only those objects that changed, and calculates their changes. |
| * This also assigns sequence numbers to new objects. |
| */ |
| public UnitOfWorkChangeSet calculateChanges(Map registeredObjects, UnitOfWorkChangeSet changeSet, boolean assignSequences, boolean shouldCloneMap) { |
| // Fire the event first which may add to the registered objects. If we |
| // need to clone the registered objects, it should be done after this |
| // call. |
| if (this.eventManager != null) { |
| this.eventManager.preCalculateUnitOfWorkChangeSet(); |
| } |
| |
| Map allObjects = (shouldCloneMap) ? cloneMap(registeredObjects) : registeredObjects; |
| |
| if (assignSequences && hasNewObjects()) { |
| // First assign sequence numbers to new objects. |
| assignSequenceNumbers(this.newObjectsCloneToOriginal); |
| } |
| |
| // Second calculate changes for all registered objects. |
| Iterator objects = allObjects.keySet().iterator(); |
| Map changedObjects = new IdentityHashMap(); |
| Map visitedNodes = new IdentityHashMap(); |
| while (objects.hasNext()) { |
| Object object = objects.next(); |
| |
| // Block of code removed because it will never be touched see bug # 2903565 |
| |
| ClassDescriptor descriptor = getDescriptor(object); |
| |
| // Update any derived id's. |
| updateDerivedIds(object, descriptor); |
| |
| // Block of code removed for code coverage, as it would never have been touched. bug # 2903600 |
| |
| boolean isNew = isCloneNewObject(object); |
| // Use the object change policy to determine if we should run a comparison for this object - TGW. |
| if (isNew || descriptor.getObjectChangePolicy().shouldCompareExistingObjectForChange(object, this, descriptor)) { |
| ObjectChangeSet changes = null; |
| if (isNew) { |
| changes = descriptor.getObjectChangePolicy().calculateChangesForNewObject(object, changeSet, this, descriptor, true); |
| } else { |
| changes = descriptor.getObjectChangePolicy().calculateChangesForExistingObject(object, changeSet, this, descriptor, true); |
| } |
| if (changes != null) { |
| changeSet.addObjectChangeSet(changes, this, true); |
| changedObjects.put(object, object); |
| if (changes.hasChanges() && !changes.hasForcedChangesFromCascadeLocking()) { |
| if (descriptor.hasCascadeLockingPolicies()) { |
| for (CascadeLockingPolicy policy : descriptor.getCascadeLockingPolicies()) { |
| policy.lockNotifyParent(object, changeSet, this); |
| } |
| } else if (descriptor.usesOptimisticLocking() && descriptor.getOptimisticLockingPolicy().isCascaded()) { |
| changes.setHasForcedChangesFromCascadeLocking(true); |
| } |
| } |
| } else { |
| // Mark as visited so do not need to traverse. |
| visitedNodes.put(object, object); |
| } |
| } else { |
| // Mark as visited so do not need to traverse. |
| visitedNodes.put(object, object); |
| } |
| } |
| if (hasDeletedObjects() && !isNestedUnitOfWork()) { |
| for (Object deletedObject : ((IdentityHashMap)((IdentityHashMap)this.deletedObjects).clone()).keySet()) { |
| getDescriptor(deletedObject).getObjectBuilder().recordPrivateOwnedRemovals(deletedObject, this, true); |
| } |
| } |
| if ((this.deletedPrivateOwnedObjects != null) && !this.isNestedUnitOfWork) { |
| for (Map.Entry<DatabaseMapping, List<Object>> entry : this.deletedPrivateOwnedObjects.entrySet()) { |
| DatabaseMapping databasemapping = entry.getKey(); |
| for (Object deletedObject : entry.getValue()) { |
| databasemapping.getReferenceDescriptor().getObjectBuilder().recordPrivateOwnedRemovals(deletedObject, this, false); |
| } |
| } |
| this.deletedPrivateOwnedObjects.clear(); |
| } |
| |
| if (this.project.hasMappingsPostCalculateChangesOnDeleted()) { |
| if (hasDeletedObjects()) { |
| for (Iterator deletedObjects = getDeletedObjects().keySet().iterator(); deletedObjects.hasNext();) { |
| Object deletedObject = deletedObjects.next(); |
| ClassDescriptor descriptor = getDescriptor(deletedObject); |
| if(descriptor.hasMappingsPostCalculateChangesOnDeleted()) { |
| int size = descriptor.getMappingsPostCalculateChangesOnDeleted().size(); |
| for(int i=0; i < size; i++) { |
| DatabaseMapping mapping = descriptor.getMappingsPostCalculateChangesOnDeleted().get(i); |
| mapping.postCalculateChangesOnDeleted(deletedObject, changeSet, this); |
| } |
| } |
| } |
| } |
| } |
| |
| if (this.shouldDiscoverNewObjects && !changedObjects.isEmpty()) { |
| // Third discover any new objects from the new or changed objects. |
| Map newObjects = new IdentityHashMap(); |
| // Bug 294259 - Do not replace the existingObjects list |
| // Iterate over the changed objects only. |
| discoverUnregisteredNewObjects(changedObjects, newObjects, getUnregisteredExistingObjects(), visitedNodes); |
| |
| setUnregisteredNewObjects(newObjects); |
| if (assignSequences) { |
| assignSequenceNumbers(newObjects); |
| } |
| for (Iterator newObjectsEnum = newObjects.values().iterator(); newObjectsEnum.hasNext(); ) { |
| Object object = newObjectsEnum.next(); |
| ClassDescriptor descriptor = getDescriptor(object); |
| ObjectChangeSet changes = descriptor.getObjectChangePolicy().calculateChangesForNewObject(object, changeSet, this, descriptor, true); |
| // Since it is new, it will always have a change set. |
| changeSet.addObjectChangeSet(changes, this, true); |
| } |
| } |
| |
| // Remove any orphaned privately owned objects from the UnitOfWork and ChangeSets, |
| // these are the objects remaining in the UnitOfWork privateOwnedObjects map |
| if (hasPrivateOwnedObjects()) { |
| Map visitedObjects = new IdentityHashMap(); |
| for (Set privateOwnedObjects : getPrivateOwnedObjects().values()) { |
| for (Object objectToRemove : privateOwnedObjects) { |
| performRemovePrivateOwnedObjectFromChangeSet(objectToRemove, visitedObjects); |
| } |
| } |
| this.privateOwnedObjects.clear(); |
| } |
| if (this.eventManager != null) { |
| this.eventManager.postCalculateUnitOfWorkChangeSet(changeSet); |
| } |
| return changeSet; |
| } |
| |
| /** |
| * INTERNAL: |
| * Checks whether the receiver has been used. i.e. objects have been registered. |
| * |
| * @return true or false depending on whether the read-only set can be changed or not. |
| */ |
| protected boolean canChangeReadOnlySet() { |
| return !hasCloneMapping() && !hasDeletedObjects(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the object is an existing object (but has not been registered), |
| * or a new object (that has not be persisted). |
| */ |
| public boolean checkForUnregisteredExistingObject(Object object) { |
| |
| ClassDescriptor descriptor = getDescriptor(object.getClass()); |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, this, true); |
| if (primaryKey == null) { |
| return false; |
| } |
| DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery(); |
| |
| existQuery = (DoesExistQuery) existQuery.clone(); |
| existQuery.setObject(object); |
| existQuery.setPrimaryKey(primaryKey); |
| existQuery.setDescriptor(descriptor); |
| existQuery.setIsExecutionClone(true); |
| |
| return (Boolean) executeQuery(existQuery); |
| } |
| |
| /** |
| * INTERNAL: Register the object and return the clone if it is existing |
| * otherwise return null if it is new. The unit of work determines existence |
| * during registration, not during the commit. |
| */ |
| public Object checkExistence(Object object) { |
| ClassDescriptor descriptor = getDescriptor(object.getClass()); |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, this, true); |
| // PERF: null primary key cannot exist. |
| if (primaryKey == null) { |
| return null; |
| } |
| DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery(); |
| |
| // PERF: Avoid cost of query execution as normally can determine from checkEarlyReturn. |
| Boolean exists = (Boolean)existQuery.checkEarlyReturn(object, primaryKey, this, null); |
| if (exists == null) { |
| // Need to execute database query. |
| existQuery = (DoesExistQuery)existQuery.clone(); |
| existQuery.setObject(object); |
| existQuery.setPrimaryKey(primaryKey); |
| existQuery.setDescriptor(descriptor); |
| existQuery.setIsExecutionClone(true); |
| exists = (Boolean)executeQuery(existQuery); |
| } |
| if (exists) { |
| //we know if it exists or not, now find or register it |
| Object objectFromCache = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, object.getClass(), descriptor); |
| |
| if (objectFromCache != null) { |
| // Ensure that the registered object is the one from the parent cache. |
| if (shouldPerformFullValidation()) { |
| if ((objectFromCache != object) && (this.parent.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, object.getClass(), descriptor) != object)) { |
| throw ValidationException.wrongObjectRegistered(object, objectFromCache); |
| } |
| } |
| |
| // Has already been cloned. |
| if (!this.isObjectDeleted(objectFromCache)) |
| return objectFromCache; |
| } |
| // This is a case where the object is not in the session cache, |
| // so a new cache-key is used as there is no original to use for locking. |
| // It read time must be set to avoid it being invalidated. |
| CacheKey cacheKey = new CacheKey(primaryKey); |
| cacheKey.setReadTime(System.currentTimeMillis()); |
| cacheKey.setIsolated(true); // if the cache does not have a version then this must be built from the supplied version |
| return cloneAndRegisterObject(object, cacheKey, descriptor); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value of the object if it already is registered, otherwise null. |
| */ |
| public Object checkIfAlreadyRegistered(Object object, ClassDescriptor descriptor) { |
| // Don't register read-only classes |
| if (isClassReadOnly(object.getClass(), descriptor)) { |
| return null; |
| } |
| |
| // Check if the working copy is again being registered in which case we return the same working copy |
| Object registeredObject = getCloneMapping().get(object); |
| if (registeredObject != null) { |
| return object; |
| } |
| |
| // Check if object exists in my new objects if it is in the new objects cache then it means domain object is being |
| // re-registered and we should return the same working clone. This check holds only for the new registered objects |
| // PERF: Avoid initialization of new objects if none. |
| if (hasNewObjects()) { |
| registeredObject = getNewObjectsOriginalToClone().get(object); |
| if (registeredObject != null) { |
| return registeredObject; |
| } |
| } |
| if (this.isNestedUnitOfWork) { |
| // bug # 3228185 |
| //may be a new object from a parent Unit Of Work, let's check our new object in parent list to see |
| //if it has already been registered locally |
| if (hasNewObjectsInParentOriginalToClone()) { |
| registeredObject = getNewObjectsInParentOriginalToClone().get(object); |
| } |
| if (registeredObject != null) { |
| return registeredObject; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Check if the object is invalid and *should* be refreshed. |
| * This is used to ensure that no invalid objects are cloned. |
| */ |
| @Override |
| public boolean isConsideredInvalid(Object object, CacheKey cacheKey, ClassDescriptor descriptor) { |
| if (! isNestedUnitOfWork){ |
| return getParent().isConsideredInvalid(object, cacheKey, descriptor); |
| } |
| return false; |
| } |
| |
| /** |
| * ADVANCED: |
| * Register the new object with the unit of work. |
| * This will register the new object with cloning. |
| * Normally the registerObject method should be used for all registration of new and existing objects. |
| * This version of the register method can only be used for new objects. |
| * This method should only be used if a new object is desired to be registered without an existence Check. |
| * |
| * @see #registerObject(Object) |
| */ |
| protected Object cloneAndRegisterNewObject(Object original, boolean isShallowClone) { |
| ClassDescriptor descriptor = getDescriptor(original); |
| //Nested unit of work is not supported for attribute change tracking |
| if (this.isNestedUnitOfWork && (descriptor.getObjectChangePolicy() instanceof AttributeChangeTrackingPolicy)) { |
| throw ValidationException.nestedUOWNotSupportedForAttributeTracking(); |
| } |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| |
| // bug 2612602 create the working copy object. |
| Object clone = builder.instantiateWorkingCopyClone(original, this); |
| |
| // Must put in the original to clone to resolve circular refs. |
| getNewObjectsOriginalToClone().put(original, clone); |
| getNewObjectsCloneToOriginal().put(clone, original); |
| // Must put in clone mapping. |
| getCloneMapping().put(clone, clone); |
| |
| if (isShallowClone) { |
| builder.copyInto(original, clone, true); |
| } else { |
| builder.populateAttributesForClone(original, null, clone, null, this); |
| } |
| // Must reregister in both new objects. |
| registerNewObjectClone(clone, original, descriptor); |
| |
| //Build backup clone for DeferredChangeDetectionPolicy or ObjectChangeTrackingPolicy, |
| //but not for AttributeChangeTrackingPolicy |
| Object backupClone = descriptor.getObjectChangePolicy().buildBackupClone(clone, builder, this); |
| getCloneMapping().put(clone, backupClone);// The backup clone must be updated. |
| executeDeferredEvents(); |
| |
| return clone; |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone and register the object. |
| * The cache key must the cache key from the session cache, as it will be used for locking. |
| * The unit of work cache key is passed to the normal cloneAndRegisterObject method. |
| */ |
| public Object cloneAndRegisterObject(Object original, CacheKey parentCacheKey, ClassDescriptor descriptor) { |
| CacheKey unitOfWorkCacheKey = null; |
| if (parentCacheKey.getKey() == null) { |
| // The primary key may be null for nested units of work with new parent objects. |
| unitOfWorkCacheKey = new CacheKey(null); |
| unitOfWorkCacheKey.setIsolated(true); |
| unitOfWorkCacheKey.acquire(); |
| } else { |
| unitOfWorkCacheKey = getIdentityMapAccessorInstance().acquireLock(parentCacheKey.getKey(), original.getClass(), descriptor, false); |
| } |
| try { |
| return cloneAndRegisterObject(original, parentCacheKey, unitOfWorkCacheKey, descriptor); |
| } finally { |
| unitOfWorkCacheKey.release(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone and register the object. |
| * The cache key must the cache key from the session cache, |
| * as it will be used for locking. |
| */ |
| public Object cloneAndRegisterObject(Object original, CacheKey parentCacheKey, CacheKey unitOfWorkCacheKey, ClassDescriptor descriptor) { |
| ClassDescriptor concreteDescriptor = descriptor; |
| // Ensure correct subclass descriptor. |
| if (original.getClass() != descriptor.getJavaClass()) { |
| concreteDescriptor = getDescriptor(original); |
| } |
| // Nested unit of work is not supported for attribute change tracking. |
| if (this.isNestedUnitOfWork && (concreteDescriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy())) { |
| throw ValidationException.nestedUOWNotSupportedForAttributeTracking(); |
| } |
| |
| ObjectBuilder builder = concreteDescriptor.getObjectBuilder(); |
| Object workingClone = null; |
| |
| // The cache/objects being registered must first be locked to ensure |
| // that a merge or refresh does not occur on the object while being cloned to |
| // avoid cloning a partially merged/refreshed object. |
| // If a cache isolation level is used, then lock the entire cache. |
| // otherwise lock the object and it related objects (not using indirection) as a unit. |
| // If just a simple object (all indirection) a simple read-lock can be used. |
| // PERF: Cache if check to write is required. |
| boolean identityMapLocked = this.parent.shouldCheckWriteLock && this.parent.getIdentityMapAccessorInstance().acquireWriteLock(); |
| boolean rootOfCloneRecursion = false; |
| if (identityMapLocked) { |
| checkAndRefreshInvalidObject(original, parentCacheKey, descriptor); |
| } else { |
| // Check if we have locked all required objects already. |
| if (this.objectsLockedForClone == null) { |
| // PERF: If a simple object just acquire a simple read-lock. |
| if (concreteDescriptor.shouldAcquireCascadedLocks()) { |
| this.objectsLockedForClone = this.parent.getIdentityMapAccessorInstance().getWriteLockManager().acquireLocksForClone(original, concreteDescriptor, parentCacheKey, this.parent); |
| } else { |
| checkAndRefreshInvalidObject(original, parentCacheKey, descriptor); |
| parentCacheKey.acquireReadLock(); |
| } |
| rootOfCloneRecursion = true; |
| } |
| } |
| try { |
| // bug:6167576 Must acquire the lock before cloning. |
| workingClone = builder.instantiateWorkingCopyClone(original, this); |
| // PERF: Cache the primary key if implements PersistenceEntity. |
| if (workingClone instanceof PersistenceEntity) { |
| ((PersistenceEntity)workingClone)._persistence_setId(parentCacheKey.getKey()); |
| } |
| |
| // This must be registered before it is built to avoid really obscure cycles. |
| getCloneMapping().put(workingClone, workingClone); |
| |
| // bug # 3228185 & Bug4736360 |
| // if this is a nested unit of work and the object is new in the parent |
| // and we must store it in the newobject list for lookup later |
| if (this.isNestedUnitOfWork && isCloneNewObjectFromParent(original)) { |
| getNewObjectsInParentOriginalToClone().put(original, workingClone); |
| } |
| |
| //store this for look up later |
| getCloneToOriginals().put(workingClone, original); |
| // just clone it. |
| populateAndRegisterObject(original, workingClone, unitOfWorkCacheKey, parentCacheKey, concreteDescriptor); |
| |
| //also clone the fetch group reference if applied |
| if (concreteDescriptor.hasFetchGroupManager()) { |
| concreteDescriptor.getFetchGroupManager().copyFetchGroupInto(original, workingClone, this); |
| } |
| } finally { |
| // If the entire cache was locked, release the cache lock, |
| // otherwise either release the cache-key for a simple lock, |
| // otherwise release the entire set of locks for related objects if this was the root. |
| if (identityMapLocked) { |
| this.parent.getIdentityMapAccessorInstance().releaseWriteLock(); |
| } else { |
| if (rootOfCloneRecursion) { |
| if (this.objectsLockedForClone == null) { |
| parentCacheKey.releaseReadLock(); |
| } else { |
| for (Iterator iterator = this.objectsLockedForClone.values().iterator(); iterator.hasNext();) { |
| ((CacheKey)iterator.next()).releaseReadLock(); |
| } |
| this.objectsLockedForClone = null; |
| } |
| executeDeferredEvents(); |
| } |
| } |
| } |
| concreteDescriptor.getObjectBuilder().instantiateEagerMappings(workingClone, this); |
| return workingClone; |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare for merge in nested uow. |
| */ |
| public Map collectAndPrepareObjectsForNestedMerge() { |
| discoverAllUnregisteredNewObjectsInParent(); |
| return new IdentityHashMap(this.getCloneMapping()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Commit the unit of work to its parent. |
| * For a nested unit of work this will merge any changes to its objects |
| * with its parents. |
| * For a first level unit of work it will commit all changes to its objects |
| * to the database as a single transaction. If successful the changes to its |
| * objects will be merged to its parent's objects. If the commit fails the database |
| * transaction will be rolledback, and the unit of work will be released. |
| * If the commit is successful the unit of work is released, and a new unit of work |
| * must be acquired if further changes are desired. |
| * |
| * @see #commitAndResumeOnFailure() |
| * @see #commitAndResume() |
| * @see #release() |
| */ |
| @Override |
| public void commit() throws DatabaseException, OptimisticLockException { |
| //CR#2189 throwing exception if UOW try to commit again(XC) |
| if (!isActive()) { |
| throw ValidationException.cannotCommitUOWAgain(); |
| } |
| if (isAfterWriteChangesFailed()) { |
| throw ValidationException.unitOfWorkAfterWriteChangesFailed("commit"); |
| } |
| |
| if (!this.isNestedUnitOfWork) { |
| if (isSynchronized()) { |
| // If we started the JTS transaction then we have to commit it as well. |
| if (this.parent.wasJTSTransactionInternallyStarted()) { |
| commitInternallyStartedExternalTransaction(); |
| } |
| |
| // Do not commit until the JTS wants to. |
| return; |
| } |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit"); |
| if (this.lifecycle == CommitTransactionPending) { |
| commitAfterWriteChanges(); |
| return; |
| } |
| if (this.eventManager != null) { |
| this.eventManager.preCommitUnitOfWork(); |
| } |
| setLifecycle(CommitPending); |
| if (this.isNestedUnitOfWork) { |
| commitNestedUnitOfWork(); |
| } else { |
| commitRootUnitOfWork(); |
| } |
| if (this.eventManager != null) { |
| this.eventManager.postCommitUnitOfWork(); |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit"); |
| release(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Commit the unit of work to its parent. |
| * For a nested unit of work this will merge any changes to its objects |
| * with its parents. |
| * For a first level unit of work it will commit all changes to its objects |
| * to the database as a single transaction. If successful the changes to its |
| * objects will be merged to its parent's objects. If the commit fails the database |
| * transaction will be rolledback, and the unit of work will be released. |
| * The normal commit releases the unit of work, forcing a new one to be acquired if further changes are desired. |
| * The resuming feature allows for the same unit of work (and working copies) to be continued to be used. |
| * |
| * @see #commitAndResumeOnFailure() |
| * @see #commit() |
| * @see #release() |
| */ |
| @Override |
| public void commitAndResume() throws DatabaseException, OptimisticLockException { |
| //CR#2189 throwing exception if UOW try to commit again(XC) |
| if (!isActive()) { |
| throw ValidationException.cannotCommitUOWAgain(); |
| } |
| |
| if (isAfterWriteChangesFailed()) { |
| throw ValidationException.unitOfWorkAfterWriteChangesFailed("commit"); |
| } |
| |
| if (!this.isNestedUnitOfWork) { |
| if (isSynchronized()) { |
| // JTA synchronized units of work, cannot be resumed as there is no |
| // JTA transaction to register with after the commit, |
| // technically this could be supported if the uow started the transaction, |
| // but currently the after completion releases the uow and client session so not really possible. |
| throw ValidationException.cannotCommitAndResumeSynchronizedUOW(this); |
| } |
| } |
| if (this.lifecycle == CommitTransactionPending) { |
| commitAndResumeAfterWriteChanges(); |
| return; |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit");// bjv - correct spelling |
| if (this.eventManager != null) { |
| this.eventManager.preCommitUnitOfWork(); |
| } |
| setLifecycle(CommitPending); |
| if (this.parent.isUnitOfWork()) { |
| commitNestedUnitOfWork(); |
| } else { |
| commitRootUnitOfWork(); |
| } |
| if (this.eventManager != null) { |
| this.eventManager.postCommitUnitOfWork(); |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit"); |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work"); |
| synchronizeAndResume(); |
| if (this.eventManager != null) { |
| this.eventManager.postResumeUnitOfWork(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used by the MappingWorkbench for their read-only file feature |
| * this method must not be exposed to or used by customers until it has been revised |
| * and the feature revisited to support OptimisticLocking and Serialization |
| */ |
| public void commitAndResumeWithPreBuiltChangeSet(UnitOfWorkChangeSet uowChangeSet) throws DatabaseException, OptimisticLockException { |
| if (!this.isNestedUnitOfWork) { |
| if (isSynchronized()) { |
| // If we started the JTS transaction then we have to commit it as well. |
| if (this.parent.wasJTSTransactionInternallyStarted()) { |
| commitInternallyStartedExternalTransaction(); |
| } |
| |
| // Do not commit until the JTS wants to. |
| return; |
| } |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit");// bjv - correct spelling |
| if (this.eventManager != null) { |
| this.eventManager.preCommitUnitOfWork(); |
| } |
| setLifecycle(CommitPending); |
| if (this.parent.isUnitOfWork()) { |
| commitNestedUnitOfWork(); |
| } else { |
| commitRootUnitOfWorkWithPreBuiltChangeSet(uowChangeSet); |
| } |
| if (this.eventManager != null) { |
| this.eventManager.postCommitUnitOfWork(); |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit"); |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work"); |
| |
| synchronizeAndResume(); |
| if (this.eventManager != null) { |
| this.eventManager.postResumeUnitOfWork(); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Commit the unit of work to its parent. |
| * For a nested unit of work this will merge any changes to its objects |
| * with its parents. |
| * For a first level unit of work it will commit all changes to its objects |
| * to the database as a single transaction. If successful the changes to its |
| * objects will be merged to its parent's objects. If the commit fails the database |
| * transaction will be rolledback, but the unit of work will remain active. |
| * It can then be retried or released. |
| * The normal commit failure releases the unit of work, forcing a new one to be acquired if further changes are desired. |
| * The resuming feature allows for the same unit of work (and working copies) to be continued to be used if an error occurs. |
| * The UnitOfWork will also remain active if the commit is successful. |
| * |
| * @see #commit() |
| * @see #release() |
| */ |
| @Override |
| public void commitAndResumeOnFailure() throws DatabaseException, OptimisticLockException { |
| // First clone the identity map, on failure replace the clone back as the cache. |
| IdentityMapManager failureManager = (IdentityMapManager)getIdentityMapAccessorInstance().getIdentityMapManager().clone(); |
| try { |
| // Call commitAndResume. |
| // Oct 13, 2000 - JED PRS #13551 |
| // This method will always resume now. Calling commitAndResume will sync the cache |
| // if successful. This method will take care of resuming if a failure occurs |
| commitAndResume(); |
| } catch (RuntimeException exception) { |
| //reset unitOfWorkChangeSet. Needed for ObjectChangeTrackingPolicy and DeferredChangeDetectionPolicy |
| setUnitOfWorkChangeSet(null); |
| getIdentityMapAccessorInstance().setIdentityMapManager(failureManager); |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "resuming_unit_of_work_from_failure"); |
| throw exception; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Commits a UnitOfWork where the commit process has already been |
| * initiated by all call to writeChanges(). |
| * <p> |
| * a.k.a finalizeCommit() |
| */ |
| protected void commitAfterWriteChanges() { |
| commitTransactionAfterWriteChanges(); |
| mergeClonesAfterCompletion(); |
| setDead(); |
| release(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Commits and resumes a UnitOfWork where the commit process has already been |
| * initiated by all call to writeChanges(). |
| * <p> |
| * a.k.a finalizeCommit() |
| */ |
| protected void commitAndResumeAfterWriteChanges() { |
| commitTransactionAfterWriteChanges(); |
| mergeClonesAfterCompletion(); |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work"); |
| synchronizeAndResume(); |
| if (this.eventManager != null) { |
| this.eventManager.postResumeUnitOfWork(); |
| } |
| } |
| |
| /** |
| * PROTECTED: |
| * Used in commit and commit-like methods to commit |
| * internally started external transaction |
| */ |
| protected boolean commitInternallyStartedExternalTransaction() { |
| boolean committed = false; |
| if (!this.parent.isInTransaction() || (wasTransactionBegunPrematurely() && (this.parent.getTransactionMutex().getDepth() == 1))) { |
| committed = this.parent.commitExternalTransaction(); |
| } |
| return committed; |
| } |
| |
| /** |
| * INTERNAL: |
| * Commit the changes to any objects to the parent. |
| */ |
| protected void commitNestedUnitOfWork() { |
| this.parent.getIdentityMapAccessorInstance().acquireWriteLock();//Ensure concurrency |
| |
| try { |
| // Iterate over each clone and let the object build merge to clones into the originals. |
| // The change set may already exist if using change tracking. |
| if (getUnitOfWorkChangeSet() == null) { |
| setUnitOfWorkChangeSet(new UnitOfWorkChangeSet(this)); |
| } |
| unitOfWorkChangeSet = calculateChanges(collectAndPrepareObjectsForNestedMerge(), (UnitOfWorkChangeSet)getUnitOfWorkChangeSet(), false, false); |
| this.allClones = null; |
| mergeChangesIntoParent(); |
| |
| if (hasDeletedObjects()) { |
| for (Iterator deletedObjects = getDeletedObjects().keySet().iterator(); |
| deletedObjects.hasNext();) { |
| Object deletedObject = deletedObjects.next(); |
| Object originalObject = getOriginalVersionOfObject(deletedObject); |
| |
| // bug # 3132979 if the deleted object is new in the parent |
| //then unregister in the parent. |
| //else add it to the deleted object list to be removed from the parent's parent |
| //this prevents erroneous insert and delete sql |
| if ((originalObject != null) && ((UnitOfWorkImpl)this.parent).getNewObjectsCloneToOriginal().containsKey(originalObject)) { |
| ((UnitOfWorkImpl)this.parent).unregisterObject(originalObject); |
| } else { |
| ((UnitOfWorkImpl)this.parent).getDeletedObjects().put(originalObject, getId(originalObject)); |
| } |
| } |
| } |
| if (hasRemovedObjects()) { |
| for (Iterator removedObjects = getRemovedObjects().values().iterator(); |
| removedObjects.hasNext();) { |
| ((UnitOfWorkImpl)this.parent).getCloneMapping().remove(removedObjects.next()); |
| } |
| } |
| } finally { |
| this.parent.getIdentityMapAccessorInstance().releaseWriteLock(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Commit the changes to any objects to the parent. |
| */ |
| public void commitRootUnitOfWork() throws DatabaseException, OptimisticLockException { |
| commitToDatabaseWithChangeSet(true); |
| // Merge after commit |
| mergeChangesIntoParent(); |
| this.changeTrackedHardList = null; |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used by the MappingWorkbench read-only files feature |
| * It will commit a pre-built unitofwork change set to the database |
| */ |
| public void commitRootUnitOfWorkWithPreBuiltChangeSet(UnitOfWorkChangeSet uowChangeSet) throws DatabaseException, OptimisticLockException { |
| //new code no need to check old commit |
| commitToDatabaseWithPreBuiltChangeSet(uowChangeSet, true, true); |
| |
| // Merge after commit |
| mergeChangesIntoParent(); |
| } |
| |
| /** |
| * INTERNAL: |
| * CommitChanges To The Database from a calculated changeSet |
| * @param commitTransaction false if called by writeChanges as intent is |
| * not to finalize the transaction. |
| */ |
| protected void commitToDatabase(boolean commitTransaction) { |
| try { |
| //CR4202 - ported from 3.6.4 |
| if (wasTransactionBegunPrematurely()) { |
| // beginTransaction() has been already called |
| setWasTransactionBegunPrematurely(false); |
| } else { |
| beginTransaction(); |
| } |
| |
| if (commitTransaction) { |
| setWasNonObjectLevelModifyQueryExecuted(false); |
| } |
| this.preDeleteComplete = false; |
| |
| List deletedObjects = null;// PERF: Avoid deletion if nothing to delete. |
| if (hasDeletedObjects()) { |
| deletedObjects = new ArrayList(this.deletedObjects.size()); |
| for (Object objectToDelete : this.deletedObjects.keySet()) { |
| ClassDescriptor descriptor = getDescriptor(objectToDelete); |
| if (descriptor.hasPreDeleteMappings()) { |
| for (DatabaseMapping mapping : descriptor.getPreDeleteMappings()) { |
| DeleteObjectQuery deleteQuery = descriptor.getQueryManager().getDeleteQuery(); |
| if (deleteQuery == null) { |
| deleteQuery = new DeleteObjectQuery(); |
| deleteQuery.setDescriptor(descriptor); |
| } else { |
| // Ensure original query has been prepared. |
| deleteQuery.checkPrepare(this, deleteQuery.getTranslationRow()); |
| deleteQuery = (DeleteObjectQuery)deleteQuery.clone(); |
| } |
| deleteQuery.setIsExecutionClone(true); |
| deleteQuery.setTranslationRow(new DatabaseRecord()); |
| deleteQuery.setObject(objectToDelete); |
| deleteQuery.setSession(this); |
| mapping.earlyPreDelete(deleteQuery, objectToDelete); |
| } |
| } |
| deletedObjects.add(objectToDelete); |
| } |
| this.preDeleteComplete = true; |
| } |
| |
| if (this.shouldPerformDeletesFirst) { |
| if (deletedObjects != null) { |
| // This must go to the commit manager because uow overrides to do normal deletion. |
| getCommitManager().deleteAllObjects(deletedObjects); |
| |
| // Clear change sets of the deleted object to avoid redundant updates. |
| for (Iterator objects = getObjectsDeletedDuringCommit().keySet().iterator(); |
| objects.hasNext();) { |
| org.eclipse.persistence.internal.sessions.ObjectChangeSet objectChangeSet = (org.eclipse.persistence.internal.sessions.ObjectChangeSet)this.unitOfWorkChangeSet.getObjectChangeSetForClone(objects.next()); |
| if (objectChangeSet != null) { |
| objectChangeSet.clear(true); |
| } |
| } |
| } |
| |
| // Let the commit manager figure out how to write the objects |
| super.writeAllObjectsWithChangeSet(this.unitOfWorkChangeSet); |
| // Issue all the SQL for the ModifyAllQuery's, don't touch the cache though |
| issueModifyAllQueryList(); |
| } else { |
| // Let the commit manager figure out how to write the objects |
| super.writeAllObjectsWithChangeSet(this.unitOfWorkChangeSet); |
| if (deletedObjects != null) { |
| // This must go to the commit manager because uow overrides to do normal deletion. |
| getCommitManager().deleteAllObjects(deletedObjects); |
| } |
| |
| // Issue all the SQL for the ModifyAllQuery's, don't touch the cache though |
| issueModifyAllQueryList(); |
| } |
| |
| // Issue prepare event. |
| if (this.eventManager != null) { |
| this.eventManager.prepareUnitOfWork(); |
| } |
| |
| // writeChanges() does everything but this step. |
| // do not lock objects unless we are at the commit stage |
| if (commitTransaction) { |
| try { |
| acquireWriteLocks(); |
| commitTransaction(); |
| } catch (RuntimeException throwable) { |
| releaseWriteLocks(); |
| throw throwable; |
| } catch (Error throwable) { |
| releaseWriteLocks(); |
| throw throwable; |
| } |
| } else { |
| setWasTransactionBegunPrematurely(true); |
| |
| //must let the UnitOfWork know that the transaction was begun |
| //before the commit process. |
| } |
| } catch (RuntimeException exception) { |
| // The number of SQL statements been prepared need be stored into UOW |
| // before any exception being thrown. |
| copyStatementsCountIntoProperties(); |
| try { |
| rollbackTransaction(commitTransaction); |
| } catch (RuntimeException ignore) { |
| // Ignore |
| } |
| if (hasExceptionHandler()) { |
| getExceptionHandler().handleException(exception); |
| } else { |
| throw exception; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Commit the changes to any objects to the parent. |
| * @param commitTransaction false if called by writeChanges as intent is |
| * not to finalize the transaction. |
| */ |
| protected void commitToDatabaseWithChangeSet(boolean commitTransaction) throws DatabaseException, OptimisticLockException { |
| |
| try { |
| incrementProfile(SessionProfiler.UowCommits); |
| startOperationProfile(SessionProfiler.UowCommit); |
| // PERF: If this is an empty unit of work, do nothing (but still may need to commit SQL changes). |
| boolean hasChanges = (this.unitOfWorkChangeSet != null) || hasCloneMapping() || hasDeletedObjects() || hasModifyAllQueries() || hasDeferredModifyAllQueries(); |
| if (hasChanges) { |
| try{ |
| // The sequence numbers are assigned outside of the commit transaction. |
| // This improves concurrency, avoids deadlock and in the case of three-tier will |
| // not leave invalid cached sequences on rollback. |
| // Iterate over each clone and let the object build merge to clones into the originals. |
| // The change set may already exist if using change tracking. |
| if (this.unitOfWorkChangeSet == null) { |
| this.unitOfWorkChangeSet = new UnitOfWorkChangeSet(this); |
| } |
| // PERF: clone is faster than new. |
| calculateChanges(getCloneMapping(), this.unitOfWorkChangeSet, true, true); |
| |
| } catch (RuntimeException exception){ |
| // The number of SQL statements been prepared need be stored into UOW |
| // before any exception being thrown. |
| copyStatementsCountIntoProperties(); |
| throw exception; |
| } |
| hasChanges = hasModifications(); |
| } |
| |
| // Bug 2834266 only commit to the database if changes were made, avoid begin/commit of transaction |
| if (hasChanges) { |
| // Also must first set the commit manager active. |
| getCommitManager().setIsActive(true); |
| commitToDatabase(commitTransaction); |
| } else { |
| try { |
| // CR#... need to commit the transaction if begun early. |
| if (wasTransactionBegunPrematurely()) { |
| if (commitTransaction) { |
| // Must be set to false for release to know not to rollback. |
| setWasTransactionBegunPrematurely(false); |
| setWasNonObjectLevelModifyQueryExecuted(false); |
| try { |
| commitTransaction(); |
| } catch (RuntimeException commitFailed) { |
| try { |
| rollbackTransaction(); |
| } catch (RuntimeException ignore) { |
| // Ignore |
| } |
| throw commitFailed; |
| } catch (Error error) { |
| try { |
| rollbackTransaction(); |
| } catch (RuntimeException ignore) { |
| // Ignore |
| } |
| throw error; |
| } |
| } |
| } |
| } catch (RuntimeException exception) { |
| // The number of SQL statements been prepared need be stored into UOW |
| // before any exception being thrown. |
| copyStatementsCountIntoProperties(); |
| throw exception; |
| } |
| } |
| } catch (RuntimeException exception) { |
| handleException(exception); |
| } finally { |
| endOperationProfile(SessionProfiler.UowCommit); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Commit pre-built changeSet to the database changeSet to the database. |
| */ |
| protected void commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkChangeSet uowChangeSet, boolean commitTransaction, boolean isChangeSetFromOutsideUOW) throws DatabaseException, OptimisticLockException { |
| try { |
| uowChangeSet.setIsChangeSetFromOutsideUOW(isChangeSetFromOutsideUOW); |
| // The sequence numbers are assigned outside of the commit transaction. |
| // This improves concurrency, avoids deadlock and in the case of three-tier will |
| // not leave invalid cached sequences on rollback. |
| // Also must first set the commit manager active. |
| getCommitManager().setIsActive(true); |
| // Iterate over each clone and let the object build merge to clones into the originals. |
| setUnitOfWorkChangeSet(uowChangeSet); |
| commitToDatabase(commitTransaction); |
| uowChangeSet.setIsChangeSetFromOutsideUOW(false); |
| } catch (RuntimeException exception) { |
| handleException(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This is internal to the uow, transactions should not be used explicitly in a uow. |
| * The uow shares its parents transactions. |
| */ |
| @Override |
| public void commitTransaction() throws DatabaseException { |
| this.parent.commitTransaction(); |
| } |
| |
| /** |
| * INTERNAL: |
| * After writeChanges() everything has been done except for committing |
| * the transaction. This allows that execution path to 'catch up'. |
| */ |
| public void commitTransactionAfterWriteChanges() { |
| setWasNonObjectLevelModifyQueryExecuted(false); |
| if (hasModifications() || wasTransactionBegunPrematurely()) { |
| try{ |
| //gf934: ensuring release doesn't cause an extra rollback call if acquireRequiredLocks throws an exception |
| setWasTransactionBegunPrematurely(false); |
| acquireWriteLocks(); |
| commitTransaction(); |
| } catch (RuntimeException exception) { |
| releaseWriteLocks(); |
| try { |
| rollbackTransaction(false); |
| } catch (RuntimeException ignore) { |
| // Ignore |
| } |
| setLifecycle(WriteChangesFailed); |
| handleException(exception); |
| } catch (Error throwable) { |
| releaseWriteLocks(); |
| try { |
| rollbackTransaction(); |
| } catch (RuntimeException ignore) { |
| // Ignore |
| } |
| throw throwable; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Acquire the unit of work cache write locks, if required. |
| */ |
| protected void acquireWriteLocks() { |
| // If everything is isolated, can bypass merge entirely. |
| if (this.project.hasNonIsolatedUOWClasses() || (this.modifyAllQueries != null)) { |
| // if we should be acquiring locks before commit let's do that here |
| if (getDatasourceLogin().shouldSynchronizeObjectLevelReadWriteDatabase() && (this.unitOfWorkChangeSet != null)) { |
| writesCompleted();//flush Batch Statements |
| this.lastUsedMergeManager = new MergeManager(this); |
| //If we are merging into the shared cache acquire all required locks before merging. |
| this.parent.getIdentityMapAccessorInstance().getWriteLockManager().acquireRequiredLocks(this.lastUsedMergeManager, this.unitOfWorkChangeSet); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Release the unit of work cache write locks, if acquired. |
| */ |
| public void releaseWriteLocks() { |
| if (this.lastUsedMergeManager != null) { |
| // 272022: If the current thread and the active thread on the mutex do not match - switch them |
| verifyMutexThreadIntegrityBeforeRelease(); |
| // exception occurred during the commit. |
| this.parent.getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(this.lastUsedMergeManager); |
| this.lastUsedMergeManager = null; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Copy the read only classes from the unit of work. |
| */ |
| // Added Nov 8, 2000 JED for Patch 2.5.1.8, Ref: Prs 24502 |
| @Override |
| public Vector copyReadOnlyClasses() { |
| return new Vector(getReadOnlyClasses()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Merge the attributes of the clone into the unit of work copy. |
| * This can be used for objects that are returned from the client through |
| * RMI serialization or other serialization mechanisms, because the RMI object will |
| * be a clone this will merge its attributes correctly to preserve object identity |
| * within the unit of work and record its changes. |
| * Everything connected to this object (i.e. the entire object tree where rmiClone |
| * is the root) is also merged. |
| * |
| * @return the registered version for the clone being merged. |
| * @see #mergeClone(Object) |
| * @see #shallowMergeClone(Object) |
| */ |
| @Override |
| public Object deepMergeClone(Object rmiClone) { |
| return mergeClone(rmiClone, MergeManager.CASCADE_ALL_PARTS, false); |
| } |
| |
| /** |
| * PUBLIC: |
| * Revert the object's attributes from the parent. |
| * This reverts everything the object references. |
| * |
| * @return the object reverted. |
| * @see #revertObject(Object) |
| * @see #shallowRevertObject(Object) |
| */ |
| @Override |
| public Object deepRevertObject(Object clone) { |
| return revertObject(clone, MergeManager.CASCADE_ALL_PARTS); |
| } |
| |
| /** |
| * ADVANCED: |
| * Unregister the object with the unit of work. |
| * This can be used to delete an object that was just created and is not yet persistent. |
| * Delete object can also be used, but will result in inserting the object and then deleting it. |
| * The method should be used carefully because it will delete all the reachable parts. |
| */ |
| @Override |
| public void deepUnregisterObject(Object clone) { |
| unregisterObject(clone, DescriptorIterator.CascadeAllParts); |
| } |
| |
| /** |
| * INTERNAL: |
| * Search for any objects in the parent that have not been registered. |
| * These are required so that the nested unit of work does not add them to the parent |
| * clone mapping on commit, causing possible incorrect insertions if they are dereferenced. |
| */ |
| protected void discoverAllUnregisteredNewObjects() { |
| // 2612538 - the default size of Map (32) is appropriate |
| Map visitedNodes = new IdentityHashMap(); |
| Map newObjects = new IdentityHashMap(); |
| |
| // Bug 294259 - Do not replace the existingObjects list |
| // Iterate over the clones. |
| discoverUnregisteredNewObjects(new IdentityHashMap(getCloneMapping()), newObjects, getUnregisteredExistingObjects(), visitedNodes); |
| setUnregisteredNewObjects(newObjects); |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * Search for any objects in the parent that have not been registered. |
| * These are required so that the nested unit of work does not add them to the parent |
| * clone mapping on commit, causing possible incorrect insertions if they are dereferenced. |
| */ |
| protected void discoverAllUnregisteredNewObjectsInParent() { |
| // Iterate over the clones. |
| if (this.isNestedUnitOfWork) { |
| // 2612538 - the default size of Map (32) is appropriate |
| Map visitedNodes = new IdentityHashMap(); |
| Map newObjects = new IdentityHashMap(); |
| UnitOfWorkImpl parent = (UnitOfWorkImpl)this.parent; |
| parent.discoverUnregisteredNewObjects(((UnitOfWorkImpl)this.parent).getCloneMapping(), newObjects, new IdentityHashMap(), visitedNodes); |
| setUnregisteredNewObjectsInParent(newObjects); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Traverse the object to find references to objects not registered in this unit of work. |
| */ |
| public void discoverUnregisteredNewObjects(Map clones, final Map knownNewObjects, final Map unregisteredExistingObjects, Map visitedObjects) { |
| // This define an inner class for process the iteration operation, don't be scared, its just an inner class. |
| DescriptorIterator iterator = new DescriptorIterator() { |
| @Override |
| public void iterate(Object object) { |
| // If the object is read-only then do not continue the traversal. |
| if (isClassReadOnly(object.getClass(), this.getCurrentDescriptor())) { |
| this.setShouldBreak(true); |
| return; |
| } |
| |
| /* CR3440: Steven Vo |
| * Include the case that object is original then do nothing. |
| */ |
| if (isSmartMerge() && isOriginalNewObject(object)) { |
| return; |
| } else if (!isObjectRegistered(object)) {// Don't need to check for aggregates, as iterator does not iterate on them by default. |
| if (shouldPerformNoValidation()) { |
| if (checkForUnregisteredExistingObject(object)) { |
| // If no validation is performed and the object exists we need |
| // To keep a record of this object to ignore it, also I need to |
| // Stop iterating over it. |
| unregisteredExistingObjects.put(object, object); |
| this.setShouldBreak(true); |
| return; |
| } |
| } else { |
| // This will validate that the object is not from the parent session, moved from calculate to optimize JPA. |
| getBackupClone(object, getCurrentDescriptor()); |
| } |
| // This means it is a unregistered new object |
| knownNewObjects.put(object, object); |
| } |
| } |
| |
| @Override |
| public void iterateReferenceObjectForMapping(Object referenceObject, DatabaseMapping mapping) { |
| super.iterateReferenceObjectForMapping(referenceObject, mapping); |
| if (mapping.isCandidateForPrivateOwnedRemoval()) { |
| removePrivateOwnedObject(mapping, referenceObject); |
| } |
| } |
| }; |
| // Bug 294259 - Do not replace the existingObjects list |
| |
| iterator.setVisitedObjects(visitedObjects); |
| iterator.setResult(knownNewObjects); |
| iterator.setSession(this); |
| // When using wrapper policy in EJB the iteration should stop on beans, |
| // this is because EJB forces beans to be registered anyway and clone identity can be violated |
| // and the violated clones references to session objects should not be traversed. |
| iterator.setShouldIterateOverWrappedObjects(false); |
| |
| for (Iterator clonesEnum = clones.keySet().iterator(); clonesEnum.hasNext(); ) { |
| iterator.startIterationOn(clonesEnum.next()); |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not referred after commit, |
| * ensures that objects from the parent cache are not referred in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violate in the unit of work. |
| */ |
| @Override |
| public void dontPerformValidation() { |
| setValidationLevel(None); |
| } |
| |
| /** |
| * INTERNAL: |
| * Override From session. Get the accessor based on the query, and execute call, |
| * this is here for session broker. |
| */ |
| @Override |
| public Object executeCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException { |
| Collection<Accessor> accessors = query.getSession().getAccessors(call, translationRow, query); |
| query.setAccessors(accessors); |
| try { |
| return basicExecuteCall(call, translationRow, query); |
| } finally { |
| if (call.isFinished()) { |
| query.setAccessor(null); |
| } |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * Set optimistic read lock on the object. This feature is override by normal optimistic lock. |
| * when the object is changed in UnitOfWork. The cloneFromUOW must be the clone of from this |
| * UnitOfWork and it must implements version locking or timestamp locking. |
| * The SQL would look like the followings. |
| * |
| * If shouldModifyVersionField is true, |
| * "UPDATE EMPLOYEE SET VERSION = 2 WHERE EMP_ID = 9 AND VERSION = 1" |
| * |
| * If shouldModifyVersionField is false, |
| * "UPDATE EMPLOYEE SET VERSION = 1 WHERE EMP_ID = 9 AND VERSION = 1" |
| */ |
| @Override |
| public void forceUpdateToVersionField(Object lockObject, boolean shouldModifyVersionField) { |
| ClassDescriptor descriptor = getDescriptor(lockObject); |
| if (descriptor == null) { |
| throw DescriptorException.missingDescriptor(lockObject.getClass().toString()); |
| } |
| getOptimisticReadLockObjects().put(descriptor.getObjectBuilder().unwrapObject(lockObject, this), shouldModifyVersionField); |
| } |
| |
| /** |
| * INTERNAL: |
| * The uow does not store a local accessor but shares its parents. |
| */ |
| @Override |
| public Accessor getAccessor() { |
| return this.parent.getAccessor(); |
| } |
| |
| /** |
| * INTERNAL: |
| * The uow does not store a local accessor but shares its parents. |
| */ |
| @Override |
| public Collection<Accessor> getAccessors() { |
| return this.parent.getAccessors(); |
| } |
| |
| /** |
| * INTERNAL: |
| * The commit manager is used to resolve referential integrity on commits of multiple objects. |
| * The commit manage is lazy init from parent. |
| */ |
| @Override |
| public CommitManager getCommitManager() { |
| // PERF: lazy init, not always required for release/commit with no changes. |
| if (this.commitManager == null) { |
| this.commitManager = new CommitManager(this); |
| // Initialize the commit manager |
| this.commitManager.setCommitOrder(this.parent.getCommitManager().getCommitOrder()); |
| } |
| return this.commitManager; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the connections to use for the query execution. |
| */ |
| @Override |
| public Collection<Accessor> getAccessors(Call call, AbstractRecord translationRow, DatabaseQuery query) { |
| return this.parent.getAccessors(call, translationRow, query); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the active unit of work for the current active external (JTS) transaction. |
| * This should only be used with JTS and will return null if no external transaction exists. |
| */ |
| @Override |
| public org.eclipse.persistence.sessions.UnitOfWork getActiveUnitOfWork() { |
| |
| /* Steven Vo: CR# 2517 |
| This fixed the problem of returning null when this method is called on a UOW. |
| UOW does not copy the parent session's external transaction controller |
| when it is acquired but session does */ |
| return this.parent.getActiveUnitOfWork(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any new objects matching the expression. |
| * Used for in-memory querying. |
| */ |
| public Vector getAllFromNewObjects(Expression selectionCriteria, Class theClass, AbstractRecord translationRow, int valueHolderPolicy) { |
| // PERF: Avoid initialization of new objects if none. |
| if (!hasNewObjects()) { |
| return new Vector(1); |
| } |
| |
| // bug 327900 - If don't read subclasses is set on the descriptor heed it. |
| ClassDescriptor descriptor = getDescriptor(theClass); |
| boolean readSubclassesOrNoInheritance = (!descriptor.hasInheritance() || descriptor.getInheritancePolicy().shouldReadSubclasses()); |
| |
| Vector objects = new Vector(); |
| for (Iterator newObjectsEnum = getNewObjectsCloneToOriginal().keySet().iterator(); |
| newObjectsEnum.hasNext();) { |
| Object object = newObjectsEnum.next(); |
| // bug 327900 |
| if ((object.getClass() == theClass) || (readSubclassesOrNoInheritance && (theClass.isInstance(object)))) { |
| if (selectionCriteria == null) { |
| objects.addElement(object); |
| } else if (selectionCriteria.doesConform(object, this, translationRow, valueHolderPolicy)) { |
| objects.addElement(object); |
| } |
| } |
| } |
| return objects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the backup clone for the working clone. |
| */ |
| public Object getBackupClone(Object clone) throws QueryException { |
| return getBackupClone(clone, null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the backup clone for the working clone. |
| */ |
| public Object getBackupClone(Object clone, ClassDescriptor descriptor) throws QueryException { |
| Object backupClone = getCloneMapping().get(clone); |
| if (backupClone != null) { |
| return backupClone; |
| } |
| |
| /* CR3440: Steven Vo |
| * Smart merge if necessary in isObjectRegistered() |
| */ |
| if (isObjectRegistered(clone)) { |
| return getCloneMapping().get(clone); |
| |
| } else { |
| if(descriptor == null) { |
| descriptor = getDescriptor(clone); |
| } |
| Object primaryKey = keyFromObject(clone, descriptor); |
| |
| // This happens if clone was from the parent identity map. |
| if (this.getParentIdentityMapSession(descriptor, false, true).getIdentityMapAccessorInstance().containsObjectInIdentityMap(primaryKey, clone.getClass(), descriptor)) { |
| //cr 3796 |
| if ((getUnregisteredNewObjects().get(clone) != null) && isMergePending()) { |
| //Another thread has read the new object before it has had a chance to |
| //merge this object. |
| // It also means it is an unregistered new object, so create a new backup clone for it. |
| return descriptor.getObjectBuilder().buildNewInstance(); |
| } |
| if (hasObjectsDeletedDuringCommit() && getObjectsDeletedDuringCommit().containsKey(clone)) { |
| throw QueryException.backupCloneIsDeleted(clone); |
| } |
| throw QueryException.backupCloneIsOriginalFromParent(clone); |
| } |
| // Also check that the object is not the original to a registered new object |
| // (the original should not be referenced if not smart merge, this is an error. |
| else if (hasNewObjects() && getNewObjectsOriginalToClone().containsKey(clone)) { |
| |
| /* CR3440: Steven Vo |
| * Check case that clone is original |
| */ |
| if (isSmartMerge()) { |
| backupClone = getCloneMapping().get(getNewObjectsOriginalToClone().get(clone)); |
| |
| } else { |
| throw QueryException.backupCloneIsOriginalFromSelf(clone); |
| } |
| } else { |
| // This means it is an unregistered new object, so create a new backup clone for it. |
| backupClone = descriptor.getObjectBuilder().buildNewInstance(); |
| } |
| } |
| |
| return backupClone; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the backup clone for the working clone. |
| */ |
| public Object getBackupCloneForCommit(Object clone, ClassDescriptor descriptor) { |
| Object backupClone = getBackupClone(clone, descriptor); |
| |
| /* CR3440: Steven Vo |
| * Build new instance only if it was not handled by getBackupClone() |
| */ |
| if (isCloneNewObject(clone)) { |
| if(descriptor != null) { |
| return descriptor.getObjectBuilder().buildNewInstance(); |
| } else { |
| // Can this ever happen? |
| return getDescriptor(clone).getObjectBuilder().buildNewInstance(); |
| } |
| } |
| |
| return backupClone; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the backup clone for the working clone. |
| */ |
| public Object getBackupCloneForCommit(Object clone) { |
| Object backupClone = getBackupClone(clone); |
| |
| /* CR3440: Steven Vo |
| * Build new instance only if it was not handled by getBackupClone() |
| */ |
| if (isCloneNewObject(clone)) { |
| return getDescriptor(clone).getObjectBuilder().buildNewInstance(); |
| } |
| |
| return backupClone; |
| } |
| |
| |
| /** |
| * ADVANCED: |
| * This method Will Calculate the changes for the UnitOfWork. Without assigning sequence numbers |
| * This is a computationally intensive operation and should be avoided unless necessary. |
| * A valid changeSet, with sequencenumbers can be collected from the UnitOfWork after the commit |
| * is complete by calling unitOfWork.getUnitOfWorkChangeSet() |
| */ |
| @Override |
| public org.eclipse.persistence.sessions.changesets.UnitOfWorkChangeSet getCurrentChanges() { |
| Map allObjects = collectAndPrepareObjectsForNestedMerge(); |
| return calculateChanges(allObjects, new UnitOfWorkChangeSet(this), false, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the appropriate IdentityMap session for this descriptor. Sessions can be |
| * chained and each session can have its own Cache/IdentityMap. Entities can be stored |
| * at different levels based on Cache Isolation. This method will return the correct Session |
| * for a particular Entity class based on the Isolation Level and the attributes provided. |
| * |
| * @param canReturnSelf true when method calls itself. If the path |
| * starting at <code>this</code> is acceptable. Sometimes true if want to |
| * move to the first valid session, i.e. executing on ClientSession when really |
| * should be on ServerSession. |
| * @param terminalOnly return the last session in the chain where the Enitity is stored. |
| * @return Session with the required IdentityMap |
| */ |
| @Override |
| public AbstractSession getParentIdentityMapSession(ClassDescriptor descriptor, boolean canReturnSelf, boolean terminalOnly) { |
| if (canReturnSelf && !terminalOnly) { |
| return this; |
| } else { |
| return this.parent.getParentIdentityMapSession(descriptor, true, terminalOnly); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Gets the session which this query will be executed on. |
| * Generally will be called immediately before the call is translated, |
| * which is immediately before session.executeCall. |
| * <p> |
| * Since the execution session also knows the correct datasource platform |
| * to execute on, it is often used in the mappings where the platform is |
| * needed for type conversion, or where calls are translated. |
| * <p> |
| * Is also the session with the accessor. Will return a ClientSession if |
| * it is in transaction and has a write connection. |
| * @return a session with a live accessor |
| * @param query may store session name or reference class for brokers case |
| */ |
| @Override |
| public AbstractSession getExecutionSession(DatabaseQuery query) { |
| // This optimization is only for when executing with a ClientSession in |
| // transaction. In that case log with the UnitOfWork instead of the |
| // ClientSession. |
| // Note that if actually executing on ServerSession or a registered |
| // session of a broker, must execute on that session directly. |
| |
| //bug 5201121 Always use the parent or execution session from the parent |
| // should never use the unit of work as it does not control the |
| //accessors and with a session broker it will not have the correct |
| //login info |
| return this.parent.getExecutionSession(query); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the clone mapping. |
| * The clone mapping contains clone of all registered objects, |
| * this is required to store the original state of the objects when registered |
| * so that only what is changed will be committed to the database and the parent, |
| * (this is required to support parallel unit of work). |
| */ |
| public Map getCloneMapping() { |
| // PERF: lazy-init (3286089) |
| if (cloneMapping == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| cloneMapping = createMap(); |
| } |
| return cloneMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work has any clones. |
| */ |
| public boolean hasCloneMapping() { |
| return ((cloneMapping != null) && !cloneMapping.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Map used to avoid garbage collection in weak caches. |
| * Also, map used as lookup when originals used for merge when original in |
| * identitymap can not be found. As in a CacheIdentityMap |
| */ |
| public Map getCloneToOriginals() { |
| if (cloneToOriginals == null) {// Must lazy initialize for remote. |
| // 2612538 - the default size of Map (32) is appropriate |
| cloneToOriginals = createMap(); |
| } |
| return cloneToOriginals; |
| } |
| |
| protected boolean hasCloneToOriginals() { |
| return ((cloneToOriginals != null) && !cloneToOriginals.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * This is only used for EJB entity beans to manage beans accessed in a transaction context. |
| */ |
| public Map getContainerBeans() { |
| if (containerBeans == null) { |
| containerBeans = new IdentityHashMap(); |
| } |
| return containerBeans; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if any container beans exist. |
| * PERF: Used to avoid lazy initialization of getContainerBeans(). |
| */ |
| public boolean hasContainerBeans() { |
| return ((containerBeans != null) && !containerBeans.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any objects have been deleted through database cascade delete constraints. |
| */ |
| public Set<Object> getCascadeDeleteObjects() { |
| if (this.cascadeDeleteObjects == null) { |
| this.cascadeDeleteObjects = new IdentityHashSet(); |
| } |
| return this.cascadeDeleteObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set any objects have been deleted through database cascade delete constraints. |
| */ |
| protected void setCascadeDeleteObjects(Set<Object> cascadeDeleteObjects) { |
| this.cascadeDeleteObjects = cascadeDeleteObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if any objects have been deleted through database cascade delete constraints. |
| */ |
| public boolean hasCascadeDeleteObjects() { |
| return ((this.cascadeDeleteObjects != null) && !this.cascadeDeleteObjects.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if there are any unregistered new objects. |
| * PERF: Used to avoid initialization of new objects map unless required. |
| */ |
| public boolean hasUnregisteredNewObjects() { |
| return ((this.unregisteredNewObjects != null) && !this.unregisteredNewObjects.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if there are any registered new objects. |
| * This is used for both newObjectsOriginalToClone and newObjectsCloneToOriginal as they are always in synch. |
| * PERF: Used to avoid initialization of new objects map unless required. |
| */ |
| public boolean hasNewObjects() { |
| return ((newObjectsCloneToOriginal != null) && !newObjectsCloneToOriginal.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * This is only used for EJB entity beans to manage beans accessed in a transaction context. |
| */ |
| public UnitOfWorkImpl getContainerUnitOfWork() { |
| if (containerUnitOfWork == null) { |
| containerUnitOfWork = this.parent.acquireNonSynchronizedUnitOfWork(ReferenceMode.WEAK); |
| } |
| return containerUnitOfWork; |
| } |
| |
| /** |
| * INTERNAL: Returns the set of read-only classes that gets assigned to each newly created UnitOfWork. |
| * |
| * @see org.eclipse.persistence.sessions.Project#setDefaultReadOnlyClasses(Collection) |
| */ |
| @Override |
| public Vector getDefaultReadOnlyClasses() { |
| return this.parent.getDefaultReadOnlyClasses(); |
| } |
| |
| /** |
| * INTERNAL: |
| * The deleted objects stores any objects removed during the unit of work. |
| * On commit they will all be removed from the database. |
| */ |
| public Map getDeletedObjects() { |
| if (deletedObjects == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| deletedObjects = new IdentityHashMap(); |
| } |
| return deletedObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * The deleted objects stores any objects removed during the unit of work. |
| * On commit they will all be removed from the database. |
| */ |
| public boolean hasDeletedObjects() { |
| return ((deletedObjects != null) && !deletedObjects.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * The life cycle tracks if the unit of work is active and is used for JTS. |
| */ |
| public int getLifecycle() { |
| return lifecycle; |
| } |
| |
| /** |
| * A reference to the last used merge manager. This is used to track locked |
| * objects. |
| */ |
| public MergeManager getMergeManager() { |
| return this.lastUsedMergeManager; |
| } |
| |
| /** |
| * INTERNAL: |
| * The map stores any new aggregates that have been cloned. |
| */ |
| public Map getNewAggregates() { |
| if (this.newAggregates == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| this.newAggregates = new IdentityHashMap(); |
| } |
| return newAggregates; |
| } |
| |
| /** |
| * INTERNAL: |
| * The new objects stores any objects newly created during the unit of work. |
| * On commit they will all be inserted into the database. |
| */ |
| public Map getNewObjectsCloneToOriginal() { |
| if (newObjectsCloneToOriginal == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| newObjectsCloneToOriginal = new IdentityHashMap(); |
| } |
| return newObjectsCloneToOriginal; |
| } |
| |
| /** |
| * INTERNAL: |
| * The stores a map from new object clones to the original object used from merge. |
| */ |
| public Map getNewObjectsCloneToMergeOriginal() { |
| if (newObjectsCloneToMergeOriginal == null) { |
| newObjectsCloneToMergeOriginal = new IdentityHashMap(); |
| } |
| return newObjectsCloneToMergeOriginal; |
| } |
| |
| /** |
| * INTERNAL: |
| * The returns the list that will hold the new objects from the Parent UnitOfWork |
| */ |
| public Map getNewObjectsInParentOriginalToClone() { |
| // PERF: lazy-init (3286089) |
| if (newObjectsInParentOriginalToClone == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| newObjectsInParentOriginalToClone = new IdentityHashMap(); |
| } |
| return newObjectsInParentOriginalToClone; |
| } |
| |
| protected boolean hasNewObjectsInParentOriginalToClone() { |
| return ((newObjectsInParentOriginalToClone != null) && !newObjectsInParentOriginalToClone.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the privateOwnedRelationships attribute. |
| */ |
| private Map<DatabaseMapping, Set> getPrivateOwnedObjects() { |
| if (privateOwnedObjects == null) { |
| privateOwnedObjects = new IdentityHashMap<>(); |
| } |
| return privateOwnedObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if privateOwnedObjects is not null and not empty, false otherwise. |
| */ |
| public boolean hasPrivateOwnedObjects() { |
| return privateOwnedObjects != null && !privateOwnedObjects.isEmpty(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if there are any optimistic read locks. |
| */ |
| public boolean hasOptimisticReadLockObjects() { |
| return ((optimisticReadLockObjects != null) && !optimisticReadLockObjects.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * The new objects stores any objects newly created during the unit of work. |
| * On commit they will all be inserted into the database. |
| */ |
| public Map getNewObjectsOriginalToClone() { |
| if (newObjectsOriginalToClone == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| newObjectsOriginalToClone = new IdentityHashMap(); |
| } |
| return newObjectsOriginalToClone; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the Sequencing object used by the session. |
| */ |
| @Override |
| public Sequencing getSequencing() { |
| return this.parent.getSequencing(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Marked internal as this is not customer API but helper methods for |
| * accessing the server platform from within EclipseLink's other sessions types |
| * (i.e. not DatabaseSession) |
| */ |
| @Override |
| public ServerPlatform getServerPlatform() { |
| return this.parent.getServerPlatform(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the type of session, its class. |
| * <p> |
| * Override to hide from the user when they are using an internal subclass |
| * of a known class. |
| * <p> |
| * A user does not need to know that their UnitOfWork is a |
| * non-deferred UnitOfWork, or that their ClientSession is an |
| * IsolatedClientSession. |
| */ |
| @Override |
| public String getSessionTypeString() { |
| return "UnitOfWork"; |
| } |
| |
| /** |
| * INTERNAL: |
| * Called after external transaction rolled back. |
| */ |
| public void afterExternalTransactionRollback() { |
| // In case jts transaction was internally started but rolled back |
| // directly by TransactionManager this flag may still be true during afterCompletion |
| this.parent.setWasJTSTransactionInternallyStarted(false); |
| //bug#4699614 -- added a new life cycle status so we know if the external transaction was rolledback and we don't try to rollback again later |
| setLifecycle(AfterExternalTransactionRolledBack); |
| |
| if ((getMergeManager() != null) && (getMergeManager().getAcquiredLocks() != null) && (!getMergeManager().getAcquiredLocks().isEmpty())) { |
| // 272022: If the current thread and the active thread on the mutex do not match - switch them |
| verifyMutexThreadIntegrityBeforeRelease(); |
| //may have unreleased cache locks because of a rollback... |
| this.parent.getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(getMergeManager()); |
| this.setMergeManager(null); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Called in the end of beforeCompletion of external transaction synchronization listener. |
| * Close the managed sql connection corresponding to the external transaction. |
| */ |
| @Override |
| public void releaseJTSConnection() { |
| this.parent.releaseJTSConnection(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any new object matching the expression. |
| * Used for in-memory querying. |
| */ |
| public Object getObjectFromNewObjects(Class theClass, Object selectionKey) { |
| // PERF: Avoid initialization of new objects if none. |
| if (!hasNewObjects()) { |
| return null; |
| } |
| |
| // bug 327900 - If don't read subclasses is set on the descriptor heed it. |
| ClassDescriptor descriptor = getDescriptor(theClass); |
| boolean readSubclassesOrNoInheritance = (!descriptor.hasInheritance() || descriptor.getInheritancePolicy().shouldReadSubclasses()); |
| |
| ObjectBuilder objectBuilder = descriptor.getObjectBuilder(); |
| for (Iterator newObjectsEnum = getNewObjectsCloneToOriginal().keySet().iterator(); |
| newObjectsEnum.hasNext();) { |
| Object object = newObjectsEnum.next(); |
| // bug 327900 |
| if ((object.getClass() == theClass) || (readSubclassesOrNoInheritance && (theClass.isInstance(object)))) { |
| Object primaryKey = objectBuilder.extractPrimaryKeyFromObject(object, this, true); |
| if ((primaryKey != null) && primaryKey.equals(selectionKey)) { |
| return object; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return any new object matching the expression. |
| * Used for in-memory querying. |
| */ |
| public Object getObjectFromNewObjects(Expression selectionCriteria, Class theClass, AbstractRecord translationRow, int valueHolderPolicy) { |
| // PERF: Avoid initialization of new objects if none. |
| if (!hasNewObjects()) { |
| return null; |
| } |
| |
| // bug 327900 - If don't read subclasses is set on the descriptor heed it. |
| ClassDescriptor descriptor = getDescriptor(theClass); |
| boolean readSubclassesOrNoInheritance = (!descriptor.hasInheritance() || descriptor.getInheritancePolicy().shouldReadSubclasses()); |
| |
| for (Object object : getNewObjectsCloneToOriginal().keySet()) { |
| // bug 327900 |
| if ((object.getClass() == theClass) || (readSubclassesOrNoInheritance && (theClass.isInstance(object)))) { |
| if (selectionCriteria == null) { |
| return object; |
| } |
| if (selectionCriteria.doesConform(object, this, translationRow, valueHolderPolicy)) { |
| return object; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns all the objects which are deleted during root commit of unit of work. |
| */ |
| public Map getObjectsDeletedDuringCommit() { |
| // PERF: lazy-init (3286089) |
| if (objectsDeletedDuringCommit == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| objectsDeletedDuringCommit = new IdentityHashMap(); |
| } |
| return objectsDeletedDuringCommit; |
| } |
| |
| protected boolean hasObjectsDeletedDuringCommit() { |
| return ((objectsDeletedDuringCommit != null) && !objectsDeletedDuringCommit.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return optimistic read lock objects |
| */ |
| public Map getOptimisticReadLockObjects() { |
| if (this.optimisticReadLockObjects == null) { |
| this.optimisticReadLockObjects = new HashMap(2); |
| } |
| return this.optimisticReadLockObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the original version of the new object (working clone). |
| */ |
| public Object getOriginalVersionOfNewObject(Object workingClone) { |
| // PERF: Avoid initialization of new objects if none. |
| if (!hasNewObjects()) { |
| return null; |
| } |
| return getNewObjectsCloneToOriginal().get(workingClone); |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the original version of the object(clone) from the parent's identity map. |
| */ |
| @Override |
| public Object getOriginalVersionOfObject(Object workingClone) { |
| // Can be null when called from the mappings. |
| if (workingClone == null) { |
| return null; |
| } |
| Object original = null; |
| ClassDescriptor descriptor = getDescriptor(workingClone); |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(workingClone, this); |
| CacheKey cacheKey = getParentIdentityMapSession(descriptor, false, false).getCacheKeyFromTargetSessionForMerge(implementation, builder, descriptor, lastUsedMergeManager); |
| if (cacheKey != null){ |
| original = cacheKey.getObject(); |
| } |
| if (original == null) { |
| // Check if it is a registered new object. |
| original = getOriginalVersionOfNewObject(implementation); |
| } |
| |
| if (original == null) { |
| // For bug 3013948 looking in the cloneToOriginals mapping will not help |
| // if the object was never registered. |
| if (isClassReadOnly(implementation.getClass(), descriptor)) { |
| return implementation; |
| } |
| |
| // The object could have been removed from the cache even though it was in the unit of work. |
| // fix for 2.5.1.3 PWK (1360) |
| if (hasCloneToOriginals()) { |
| original = getCloneToOriginals().get(workingClone); |
| } |
| } |
| |
| if (original == null) { |
| // This means that it must be an unregistered new object, so register a new clone as its original. |
| original = buildOriginal(implementation); |
| } |
| |
| return original; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the original version of the object(clone) from the parent's identity map. |
| * PERF: Use the change set to avoid cache lookups. |
| */ |
| public Object getOriginalVersionOfObjectOrNull(Object workingClone, ObjectChangeSet changeSet, ClassDescriptor descriptor, AbstractSession targetSession) { |
| // Can be null when called from the mappings. |
| if (workingClone == null) { |
| return null; |
| } |
| |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(workingClone, this); |
| Object original = getOriginalVersionOfNewObject(implementation); |
| |
| if (original == null) { |
| // For bug 3013948 looking in the cloneToOriginals mapping will not help |
| // if the object was never registered. |
| if (isClassReadOnly(implementation.getClass(), descriptor)) { |
| return implementation; |
| } |
| |
| // The object could have been removed from the cache even though it was in the unit of work. |
| // fix for 2.5.1.3 PWK (1360) |
| if (hasCloneToOriginals()) { |
| original = getCloneToOriginals().get(workingClone); |
| } |
| } |
| return original; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the original version of the object(clone) from the parent's identity map. |
| */ |
| public Object getOriginalVersionOfObjectOrNull(Object workingClone, ClassDescriptor descriptor) { |
| // Can be null when called from the mappings. |
| if (workingClone == null) { |
| return null; |
| } |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(workingClone, this); |
| |
| Object primaryKey = builder.extractPrimaryKeyFromObject(implementation, this); |
| // there's no need to elaborately avoid the readlock like the other getOriginalVersionOfObjectOrNull |
| // method as this one is not used during the commit cycle |
| Object original = this.parent.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, implementation.getClass(), descriptor); |
| |
| if (original == null) { |
| // Check if it is a registered new object. |
| original = getOriginalVersionOfNewObject(implementation); |
| } |
| |
| if (original == null) { |
| // For bug 3013948 looking in the cloneToOriginals mapping will not help |
| // if the object was never registered. |
| if (isClassReadOnly(implementation.getClass(), descriptor)) { |
| return implementation; |
| } |
| |
| // The object could have been removed from the cache even though it was in the unit of work. |
| // fix for 2.5.1.3 PWK (1360) |
| if (hasCloneToOriginals()) { |
| original = getCloneToOriginals().get(workingClone); |
| } |
| } |
| return original; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the parent. |
| * This is a unit of work if nested, otherwise a database session or client session. |
| */ |
| @Override |
| public AbstractSession getParent() { |
| return parent; |
| } |
| |
| /** |
| * INTERNAL: |
| * Search for and return the user defined property from this UOW, if it not found then search for the property |
| * from parent. |
| */ |
| @Override |
| public Object getProperty(String name){ |
| Object propertyValue = super.getProperties().get(name); |
| if (propertyValue == null) { |
| propertyValue = this.parent.getProperty(name); |
| } |
| return propertyValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the platform for a particular class. |
| */ |
| @Override |
| public Platform getPlatform(Class domainClass) { |
| return this.parent.getPlatform(domainClass); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether to throw exceptions on conforming queries |
| */ |
| public int getShouldThrowConformExceptions() { |
| return shouldThrowConformExceptions; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the query from the session pre-defined queries with the given name. |
| * This allows for common queries to be pre-defined, reused and executed by name. |
| */ |
| @Override |
| public DatabaseQuery getQuery(String name, Vector arguments) { |
| DatabaseQuery query = super.getQuery(name, arguments); |
| if (query == null) { |
| query = this.parent.getQuery(name, arguments); |
| } |
| |
| return query; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the query from the session pre-defined queries with the given name. |
| * This allows for common queries to be pre-defined, reused and executed by name. |
| */ |
| @Override |
| public DatabaseQuery getQuery(String name) { |
| DatabaseQuery query = super.getQuery(name); |
| if (query == null) { |
| query = this.parent.getQuery(name); |
| } |
| |
| return query; |
| } |
| |
| /** |
| * ADVANCED: |
| * Returns the set of read-only classes in this UnitOfWork. |
| */ |
| @Override |
| public Set getReadOnlyClasses() { |
| if (this.readOnlyClasses == null) { |
| this.readOnlyClasses = new HashSet(); |
| } |
| return this.readOnlyClasses; |
| } |
| |
| /** |
| * INTERNAL: |
| * The removed objects stores any newly registered objects removed during the nested unit of work. |
| * On commit they will all be removed from the parent unit of work. |
| */ |
| protected Map getRemovedObjects() { |
| // PERF: lazy-init (3286089) |
| if (removedObjects == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| removedObjects = new IdentityHashMap(); |
| } |
| return removedObjects; |
| } |
| |
| protected boolean hasRemovedObjects() { |
| return ((removedObjects != null) && !removedObjects.isEmpty()); |
| } |
| |
| protected boolean hasModifyAllQueries() { |
| return ((modifyAllQueries != null) && !modifyAllQueries.isEmpty()); |
| } |
| |
| protected boolean hasDeferredModifyAllQueries() { |
| return ((deferredModifyAllQueries != null) && !deferredModifyAllQueries.isEmpty()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Find out what the lifecycle state of this UoW is in. |
| */ |
| public int getState() { |
| return lifecycle; |
| } |
| |
| /** |
| * INTERNAL: |
| * PERF: Return the associated external transaction. |
| * Used to optimize activeUnitOfWork lookup. |
| */ |
| public Object getTransaction() { |
| return transaction; |
| } |
| |
| /** |
| * INTERNAL: |
| * PERF: Set the associated external transaction. |
| * Used to optimize activeUnitOfWork lookup. |
| */ |
| public void setTransaction(Object transaction) { |
| this.transaction = transaction; |
| } |
| |
| /** |
| * ADVANCED: |
| * Returns the currentChangeSet from the UnitOfWork. |
| * This is only valid after the UnitOfWOrk has committed successfully |
| */ |
| @Override |
| public org.eclipse.persistence.sessions.changesets.UnitOfWorkChangeSet getUnitOfWorkChangeSet() { |
| return unitOfWorkChangeSet; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to lazy Initialize the unregistered existing Objects collection. |
| * @return Map |
| */ |
| public Map getUnregisteredExistingObjects() { |
| if (this.unregisteredExistingObjects == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| this.unregisteredExistingObjects = new IdentityHashMap(); |
| } |
| return unregisteredExistingObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * This is used to store unregistered objects discovered in the parent so that the child |
| * unit of work knows not to register them on commit. |
| */ |
| protected Map getUnregisteredNewObjects() { |
| if (unregisteredNewObjects == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| unregisteredNewObjects = new IdentityHashMap(); |
| } |
| return unregisteredNewObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * This is used to store unregistered objects discovered in the parent so that the child |
| * unit of work knows not to register them on commit. |
| */ |
| protected Map getUnregisteredNewObjectsInParent() { |
| if (unregisteredNewObjectsInParent == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| unregisteredNewObjectsInParent = new IdentityHashMap(); |
| } |
| return unregisteredNewObjectsInParent; |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not referred after commit, |
| * ensures that objects from the parent cache are not referred in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violate in the unit of work. |
| */ |
| @Override |
| public int getValidationLevel() { |
| return validationLevel; |
| } |
| |
| /** |
| * ADVANCED: |
| * The Unit of work is capable of preprocessing to determine if any on the clone have been changed. |
| * This is computationally expensive and should be avoided on large object graphs. |
| */ |
| @Override |
| public boolean hasChanges() { |
| if (hasNewObjects()) { |
| return true; |
| } |
| if (hasDeletedObjects()) { |
| return true; |
| } |
| Map allObjects = new IdentityHashMap(getCloneMapping()); |
| UnitOfWorkChangeSet changeSet = calculateChanges(allObjects, new UnitOfWorkChangeSet(this), false, false); |
| return changeSet.hasChanges(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Does this unit of work have any changes or anything that requires a write |
| * to the database and a transaction to be started. |
| * Should be called after changes are calculated internally by commit. |
| * <p> |
| * Note if a transaction was begun prematurely it still needs to be committed. |
| */ |
| protected boolean hasModifications() { |
| if (((this.unitOfWorkChangeSet != null) && (this.unitOfWorkChangeSet.hasChanges() || ((org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).hasForcedChanges())) |
| || hasDeletedObjects() || hasModifyAllQueries() || hasDeferredModifyAllQueries()) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set up the IdentityMapManager. This method allows subclasses of Session to override |
| * the default IdentityMapManager functionality. |
| */ |
| @Override |
| public void initializeIdentityMapAccessor() { |
| this.identityMapAccessor = new UnitOfWorkIdentityMapAccessor(this, new IdentityMapManager(this)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the results from executing the database query. |
| * the arguments should be a database row with raw data values. |
| */ |
| @Override |
| public Object internalExecuteQuery(DatabaseQuery query, AbstractRecord databaseRow) throws DatabaseException, QueryException { |
| if (project.allowExtendedThreadLogging()) { |
| Thread currentThread = Thread.currentThread(); |
| if (this.CREATION_THREAD_HASHCODE != currentThread.hashCode()) { |
| log(SessionLog.SEVERE, SessionLog.THREAD, "unit_of_work_thread_info", new Object[]{this.getName(), |
| this.CREATION_THREAD_ID, this.CREATION_THREAD_NAME, |
| currentThread.getId(), currentThread.getName()}); |
| if (project.allowExtendedThreadLoggingThreadDump()) { |
| log(SessionLog.SEVERE, SessionLog.THREAD, "unit_of_work_thread_info_thread_dump", new Object[]{ |
| this.CREATION_THREAD_ID, this.CREATION_THREAD_NAME, this.creationThreadStackTrace, |
| currentThread.getId(), currentThread.getName(), ConcurrencyUtil.SINGLETON.enrichGenerateThreadDumpForCurrentThread()}); |
| } |
| } |
| } |
| Object result = query.executeInUnitOfWork(this, databaseRow); |
| executeDeferredEvents(); |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * Register the object with the unit of work. |
| * This does not perform wrapping or unwrapping. |
| * This is used for internal registration in the merge manager. |
| */ |
| public Object internalRegisterObject(Object object, ClassDescriptor descriptor, boolean isShallowClone) { |
| if (object == null) { |
| return null; |
| } |
| if (descriptor.isDescriptorTypeAggregate()) { |
| throw ValidationException.cannotRegisterAggregateObjectInUnitOfWork(object.getClass()); |
| } |
| Object registeredObject = checkIfAlreadyRegistered(object, descriptor); |
| if (registeredObject == null) { |
| // Nested units of work are special because the parent can be used to determine if the object exists |
| // in most case and new object may be in the cache in the parent. |
| if (this.isNestedUnitOfWork) { |
| UnitOfWorkImpl parentUnitOfWork = (UnitOfWorkImpl)this.parent; |
| |
| // If it is not registered in the parent we must go through the existence check. |
| if (parentUnitOfWork.isObjectRegistered(object) || isUnregisteredNewObjectInParent(object)) { |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, this); |
| if (this.isCloneNewObjectFromParent(object) || isUnregisteredNewObjectInParent(object)) { |
| // Since it is a new object a new cache-key can be used for both parent and child as not put into the cache. |
| registeredObject = cloneAndRegisterObject(object, new CacheKey(primaryKey), new CacheKey(primaryKey), descriptor); |
| } else { |
| registeredObject = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, descriptor.getJavaClass(), descriptor); |
| } |
| return registeredObject; |
| } |
| } |
| registeredObject = checkExistence(object); |
| if (registeredObject == null) { |
| // This means that the object is not in the parent im, so was created under this unit of work. |
| // This means that it must be new. |
| registeredObject = cloneAndRegisterNewObject(object, isShallowClone); |
| if (mergeManagerForActiveMerge != null) { |
| mergeManagerForActiveMerge.getMergedNewObjects().put(registeredObject, registeredObject); |
| } |
| } |
| } |
| |
| return registeredObject; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if the unit of work is active. (i.e. has not been released). |
| */ |
| @Override |
| public boolean isActive() { |
| return this.lifecycle != Death; |
| } |
| |
| /** |
| * INTERNAL: |
| * Checks to see if the specified class or descriptor is read-only or not in this UnitOfWork. |
| * @return boolean, true if the class is read-only, false otherwise. |
| */ |
| @Override |
| public boolean isClassReadOnly(Class theClass, ClassDescriptor descriptor) { |
| if ((descriptor != null) && (descriptor.shouldBeReadOnly())) { |
| return true; |
| } |
| if ((theClass != null) && (this.readOnlyClasses != null) && this.readOnlyClasses.contains(theClass)) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Check if the object is already registered in a parent Unit Of Work |
| */ |
| public boolean isCloneNewObjectFromParent(Object clone) { |
| if (this.parent.isUnitOfWork()) { |
| if (((UnitOfWorkImpl)this.parent).isCloneNewObject(clone)) { |
| return true; |
| } else { |
| if (((UnitOfWorkImpl)this.parent).isObjectRegistered(clone)) { |
| clone = ((UnitOfWorkImpl)this.parent).getCloneToOriginals().get(clone); |
| } |
| return ((UnitOfWorkImpl)this.parent).isCloneNewObjectFromParent(clone); |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Check if the object is already registered. |
| */ |
| public boolean isCloneNewObject(Object clone) { |
| return (this.newObjectsCloneToOriginal != null) && this.newObjectsCloneToOriginal.containsKey(clone); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work is waiting to be committed or in the process of being committed. |
| */ |
| public boolean isCommitPending() { |
| return this.lifecycle == CommitPending; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work is dead. |
| */ |
| public boolean isDead() { |
| return this.lifecycle == Death; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return whether the session currently has a database transaction in progress. |
| */ |
| @Override |
| public boolean isInTransaction() { |
| return this.parent.isInTransaction(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work is waiting to be merged or in the process of being merged. |
| */ |
| public boolean isMergePending() { |
| return this.lifecycle == MergePending; |
| } |
| |
| /** |
| * INTERNAL: |
| * Has writeChanges() been attempted on this UnitOfWork? It may have |
| * either succeeded or failed but either way the UnitOfWork is in a highly |
| * restricted state. |
| */ |
| public boolean isAfterWriteChangesButBeforeCommit() { |
| return ((this.lifecycle == CommitTransactionPending) || (this.lifecycle == WriteChangesFailed)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Once writeChanges has failed all a user can do really is rollback. |
| */ |
| protected boolean isAfterWriteChangesFailed() { |
| return this.lifecycle == WriteChangesFailed; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return whether this session is a nested unit of work or not. |
| */ |
| @Override |
| public boolean isNestedUnitOfWork() { |
| return isNestedUnitOfWork; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method determines if the specified clone is new in the parent UnitOfWork |
| */ |
| public boolean isNewObjectInParent(Object clone) { |
| Object original = null; |
| if (hasCloneToOriginals()) { |
| original = getCloneToOriginals().get(clone); |
| } |
| if (original != null) { |
| //bug 3115160 as a side this method was fixed to perform the correct lookup on the collection |
| return ((UnitOfWorkImpl)this.parent).getNewObjectsCloneToOriginal().containsKey(original); |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the object has been deleted in this unit of work. |
| */ |
| public boolean isObjectDeleted(Object object) { |
| boolean isDeleted = (this.deletedObjects != null) && this.deletedObjects.containsKey(object); |
| |
| if (this.parent.isUnitOfWork()) { |
| return isDeleted || ((UnitOfWorkImpl)this.parent).isObjectDeleted(object); |
| } else { |
| return isDeleted; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to determine if the clone is a new Object in the UnitOfWork |
| */ |
| public boolean isObjectNew(Object clone) { |
| //CR3678 - ported from 4.0 |
| return (isCloneNewObject(clone) || (!isObjectRegistered(clone) && !isClassReadOnly(clone.getClass()) && !isUnregisteredExistingObject(clone))); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the object is a known unregistered existing object. |
| */ |
| public boolean isUnregisteredExistingObject(Object object) { |
| return (this.unregisteredExistingObjects != null) && this.unregisteredExistingObjects.containsKey(object); |
| } |
| |
| /** |
| * ADVANCED: |
| * Return whether the clone object is already registered. |
| */ |
| @Override |
| public boolean isObjectRegistered(Object clone) { |
| if (getCloneMapping().containsKey(clone)) { |
| return true; |
| } |
| |
| // We do smart merge here |
| if (isSmartMerge()){ |
| ClassDescriptor descriptor = getDescriptor(clone); |
| if (this.parent.getIdentityMapAccessorInstance().containsObjectInIdentityMap(keyFromObject(clone, descriptor), clone.getClass(), descriptor) ) { |
| mergeCloneWithReferences(clone); |
| |
| // don't put clone in clone mapping since it would result in duplicate clone |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the original object is new. |
| * It was either registered as new or discovered as a new aggregate |
| * within another new object. |
| */ |
| public boolean isOriginalNewObject(Object original) { |
| return ((this.newObjectsOriginalToClone != null) && this.newObjectsOriginalToClone.containsKey(original)) |
| || ((this.newAggregates != null) && this.newAggregates.containsKey(original)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the status of smart merge |
| */ |
| public static boolean isSmartMerge() { |
| return SmartMerge; |
| } |
| |
| /** |
| * INTERNAL: |
| * For synchronized units of work, dump SQL to database. |
| * For cases where writes occur before the end of the transaction don't commit |
| */ |
| public void issueSQLbeforeCompletion() { |
| issueSQLbeforeCompletion(true); |
| } |
| /** |
| * INTERNAL: |
| * For synchronized units of work, dump SQL to database. |
| * For cases where writes occur before the end of the transaction don't commit |
| */ |
| public void issueSQLbeforeCompletion(boolean commitTransaction) { |
| if (this.lifecycle == CommitTransactionPending) { |
| commitTransactionAfterWriteChanges(); |
| return; |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit"); |
| mergeBmpAndWsEntities(); |
| // CR#... call event and log. |
| if (this.eventManager != null) { |
| this.eventManager.preCommitUnitOfWork(); |
| } |
| this.lifecycle = CommitPending; |
| commitToDatabaseWithChangeSet(commitTransaction); |
| } |
| |
| /** |
| * INTERNAL: |
| * Will notify all the deferred ModifyAllQuery's (excluding UpdateAllQuery's) and deferred UpdateAllQuery's to execute. |
| */ |
| protected void issueModifyAllQueryList() { |
| if (this.deferredModifyAllQueries != null) { |
| int size = this.deferredModifyAllQueries.size(); |
| for (int index = 0; index < size; index++) { |
| Object[] queries = this.deferredModifyAllQueries.get(index); |
| ModifyAllQuery query = (ModifyAllQuery)queries[0]; |
| AbstractRecord translationRow = (AbstractRecord)queries[1]; |
| this.parent.executeQuery(query, translationRow); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if this session is a unit of work. |
| */ |
| @Override |
| public boolean isUnitOfWork() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the object was existing but not registered in the parent of the nested unit of work. |
| */ |
| public boolean isUnregisteredNewObjectInParent(Object originalObject) { |
| return getUnregisteredNewObjectsInParent().containsKey(originalObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * BMP and Websphere CMP entities have to be merged if they are registered in the unit of work. |
| * Check to see if there are any such entities and do the merge if required. |
| */ |
| protected void mergeBmpAndWsEntities() { |
| // Check for container registered beans that need to be merged. |
| // This is required for EJB entity beans. |
| // PERF: First check if there are any. |
| if (hasContainerBeans()) { |
| Iterator containerBeansEnum = getContainerBeans().keySet().iterator(); |
| while (containerBeansEnum.hasNext()) { |
| mergeCloneWithReferences(containerBeansEnum.next()); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: Merge the changes to all objects to the parent. |
| */ |
| protected void mergeChangesIntoParent() { |
| UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)getUnitOfWorkChangeSet(); |
| if (uowChangeSet == null) { |
| // No changes. |
| return; |
| } |
| |
| // 3286123 - if no work to be done, skip this part of uow.commit() |
| if (!hasModifications()) { |
| return; |
| } |
| |
| boolean isNestedUnitOfWork = this.isNestedUnitOfWork; |
| |
| // If everything is isolated, can bypass merge entirely. |
| if (!isNestedUnitOfWork && (!this.project.hasNonIsolatedUOWClasses() && (this.modifyAllQueries == null))) { |
| return; |
| } |
| |
| setPendingMerge(); |
| startOperationProfile(SessionProfiler.Merge); |
| // Ensure concurrency if cache isolation requires. |
| this.parent.getIdentityMapAccessorInstance().acquireWriteLock(); |
| MergeManager manager = getMergeManager(); |
| if (manager == null) { |
| // no MergeManager created for locks during commit |
| manager = new MergeManager(this); |
| } |
| try { |
| if (!isNestedUnitOfWork) { |
| preMergeChanges(); |
| } |
| |
| // Must clone the clone mapping because entries can be added to it during the merging, |
| // and that can lead to concurrency problems.if (this.eventManager != null) { |
| if (this.parent.hasEventManager()) { |
| this.parent.getEventManager().preMergeUnitOfWorkChangeSet(uowChangeSet); |
| } |
| if (!isNestedUnitOfWork && getDatasourceLogin().shouldSynchronizeObjectLevelReadWrite()) { |
| // Note shouldSynchronizeObjectLevelReadWrite is not the default, shouldSynchronizeObjectLevelReadWriteDatabase |
| // is the default, and locks are normally acquire before the commit transaction. |
| setMergeManager(manager); |
| //If we are merging into the shared cache acquire all required locks before merging. |
| this.parent.getIdentityMapAccessorInstance().getWriteLockManager().acquireRequiredLocks(getMergeManager(), (UnitOfWorkChangeSet)getUnitOfWorkChangeSet()); |
| } |
| Set<Class> classesChanged = new HashSet<>(); |
| if (! shouldStoreBypassCache()) { |
| for (Map<ObjectChangeSet, ObjectChangeSet> objectChangesList : ((UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).getObjectChanges().values()) { |
| // May be no changes for that class type. |
| for (ObjectChangeSet changeSetToWrite : objectChangesList.values()) { |
| if (changeSetToWrite.hasChanges()) { |
| Object objectToWrite = changeSetToWrite.getUnitOfWorkClone(); |
| ClassDescriptor descriptor = changeSetToWrite.getDescriptor(); |
| // PERF: Do not merge into the session cache if set to unit of work isolated. |
| if ((!isNestedUnitOfWork) && descriptor.getCachePolicy().shouldIsolateObjectsInUnitOfWork() ) { |
| break; |
| } |
| manager.mergeChanges(objectToWrite, changeSetToWrite, this.getParentIdentityMapSession(descriptor, false, false)); |
| classesChanged.add(objectToWrite.getClass()); |
| } |
| } |
| } |
| } |
| |
| // Notify the queries to merge into the shared cache |
| if (this.modifyAllQueries != null) { |
| int size = this.modifyAllQueries.size(); |
| for (int index = 0; index < size; index++) { |
| ModifyAllQuery query = this.modifyAllQueries.get(index); |
| query.setSession(this.parent);// ensure the query knows which cache to update |
| query.mergeChangesIntoSharedCache(); |
| } |
| } |
| |
| if (isNestedUnitOfWork) { |
| for (Map<ObjectChangeSet, ObjectChangeSet> objectChangesList : ((UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).getNewObjectChangeSets().values()) { |
| for (ObjectChangeSet changeSetToWrite : objectChangesList.values()) { |
| if (changeSetToWrite.hasChanges()) { |
| Object objectToWrite = changeSetToWrite.getUnitOfWorkClone(); |
| manager.mergeChanges(objectToWrite, changeSetToWrite, this.parent); |
| } |
| } |
| } |
| } |
| if (!isNestedUnitOfWork) { |
| //If we are merging into the shared cache release all of the locks that we acquired. |
| // We will not check If the current thread and the active thread on the mutex do not match |
| this.parent.getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(manager); |
| setMergeManager(null); |
| |
| postMergeChanges(classesChanged); |
| |
| for (Class changedClass : classesChanged) { |
| this.parent.getIdentityMapAccessorInstance().invalidateQueryCache(changedClass); |
| } |
| // If change propagation enabled through RemoteCommandManager then go for it |
| if (this.parent.shouldPropagateChanges() && (this.parent.getCommandManager() != null)) { |
| if (hasDeletedObjects()) { |
| uowChangeSet.addDeletedObjects(getDeletedObjects(), this); |
| } |
| if (hasObjectsDeletedDuringCommit()) { |
| uowChangeSet.addDeletedObjects(getObjectsDeletedDuringCommit(), this); |
| } |
| if (uowChangeSet.hasChanges()) { |
| UnitOfWorkChangeSet remoteChangeSet = uowChangeSet.buildCacheCoordinationMergeChangeSet(this); |
| if (remoteChangeSet != null) { |
| MergeChangeSetCommand command = new MergeChangeSetCommand(); |
| command.setChangeSet(remoteChangeSet); |
| this.parent.getCommandManager().propagateCommand(command); |
| } |
| } |
| } |
| } |
| } finally { |
| if (!this.isNestedUnitOfWork && !manager.getAcquiredLocks().isEmpty()) { |
| // if the locks have not already been released (!acquiredLocks.empty) |
| // then there must have been an error, release all of the locks. |
| try{ |
| // 272022: If the current thread and the active thread on the mutex do not match - switch them |
| verifyMutexThreadIntegrityBeforeRelease(); |
| this.parent.getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(manager); |
| }catch(Exception ex){ |
| //something has gone wrong twice so lets make sure the original exception is raised |
| } |
| setMergeManager(null); |
| } |
| this.parent.getIdentityMapAccessorInstance().releaseWriteLock(); |
| this.parent.getEventManager().postMergeUnitOfWorkChangeSet(uowChangeSet); |
| endOperationProfile(SessionProfiler.Merge); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Merge the attributes of the clone into the unit of work copy. |
| * This can be used for objects that are returned from the client through |
| * RMI serialization (or another serialization mechanism), because the RMI object |
| * will be a clone this will merge its attributes correctly to preserve object |
| * identity within the unit of work and record its changes. |
| * |
| * The object and its private owned parts are merged. |
| * |
| * @return the registered version for the clone being merged. |
| * @see #shallowMergeClone(Object) |
| * @see #deepMergeClone(Object) |
| */ |
| @Override |
| public Object mergeClone(Object rmiClone) { |
| return mergeClone(rmiClone, MergeManager.CASCADE_PRIVATE_PARTS, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge the attributes of the clone into the unit of work copy. |
| */ |
| public Object mergeClone(Object rmiClone, int cascadeDepth, boolean forRefresh) { |
| if (rmiClone == null) { |
| return null; |
| } |
| |
| //CR#2272 |
| logDebugMessage(rmiClone, "merge_clone"); |
| |
| startOperationProfile(SessionProfiler.Merge); |
| ObjectBuilder builder = getDescriptor(rmiClone).getObjectBuilder(); |
| Object implementation = builder.unwrapObject(rmiClone, this); |
| |
| MergeManager manager = new MergeManager(this); |
| manager.mergeCloneIntoWorkingCopy(); |
| manager.setCascadePolicy(cascadeDepth); |
| manager.setForRefresh(forRefresh); |
| |
| Object merged = null; |
| try { |
| merged = manager.mergeChanges(implementation, null, this); |
| } catch (RuntimeException exception) { |
| merged = handleException(exception); |
| } |
| endOperationProfile(SessionProfiler.Merge); |
| |
| return merged; |
| } |
| |
| /** |
| * INTERNAL: |
| * for synchronized units of work, merge changes into parent |
| */ |
| public void mergeClonesAfterCompletion() { |
| // 259993: If the current thread and the active thread on the mutex do not match - switch them |
| verifyMutexThreadIntegrityBeforeRelease(); |
| mergeChangesIntoParent(); |
| // CR#... call event and log. |
| if (this.eventManager != null) { |
| this.eventManager.postCommitUnitOfWork(); |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit"); |
| } |
| |
| /** |
| * PUBLIC: |
| * Merge the attributes of the clone into the unit of work copy. |
| * This can be used for objects that are returned from the client through |
| * RMI serialization (or another serialization mechanism), because the RMI object |
| * will be a clone this will merge its attributes correctly to preserve object |
| * identity within the unit of work and record its changes. |
| * |
| * The object and its private owned parts are merged. This will include references from |
| * dependent objects to independent objects. |
| * |
| * @return the registered version for the clone being merged. |
| * @see #shallowMergeClone(Object) |
| * @see #deepMergeClone(Object) |
| */ |
| @Override |
| public Object mergeCloneWithReferences(Object rmiClone) { |
| return this.mergeCloneWithReferences(rmiClone, MergeManager.CASCADE_PRIVATE_PARTS); |
| } |
| |
| /** |
| * PUBLIC: |
| * Merge the attributes of the clone into the unit of work copy. |
| * This can be used for objects that are returned from the client through |
| * RMI serialization (or another serialization mechanism), because the RMI object |
| * will be a clone this will merge its attributes correctly to preserve object |
| * identity within the unit of work and record its changes. |
| * |
| * The object and its private owned parts are merged. This will include references from |
| * dependent objects to independent objects. |
| * |
| * @return the registered version for the clone being merged. |
| * @see #shallowMergeClone(Object) |
| * @see #deepMergeClone(Object) |
| */ |
| public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy) { |
| return mergeCloneWithReferences(rmiClone, cascadePolicy, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge the attributes of the clone into the unit of work copy. |
| * This can be used for objects that are returned from the client through |
| * RMI serialization (or another serialization mechanism), because the RMI object |
| * will be a clone this will merge its attributes correctly to preserve object |
| * identity within the unit of work and record its changes. |
| * |
| * The object and its private owned parts are merged. This will include references from |
| * dependent objects to independent objects. |
| * |
| * @return the registered version for the clone being merged. |
| * @see #shallowMergeClone(Object) |
| * @see #deepMergeClone(Object) |
| */ |
| public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy, boolean forceCascade) { |
| Object returnValue = null; |
| try{ |
| MergeManager manager = new MergeManager(this); |
| manager.mergeCloneWithReferencesIntoWorkingCopy(); |
| manager.setCascadePolicy(cascadePolicy); |
| manager.setForceCascade(forceCascade); |
| mergeManagerForActiveMerge = manager; |
| returnValue= mergeCloneWithReferences(rmiClone, manager); |
| } finally { |
| mergeManagerForActiveMerge = null; |
| } |
| return returnValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge the attributes of the clone into the unit of work copy. |
| * This can be used for objects that are returned from the client through |
| * RMI serialization (or another serialization mechanism), because the RMI object |
| * will be a clone this will merge its attributes correctly to preserve object |
| * identity within the unit of work and record its changes. |
| * |
| * The object and its private owned parts are merged. This will include references from |
| * dependent objects to independent objects. |
| * |
| * @return the registered version for the clone being merged. |
| * @see #shallowMergeClone(Object) |
| * @see #deepMergeClone(Object) |
| */ |
| public Object mergeCloneWithReferences(Object rmiClone, MergeManager manager) { |
| if (rmiClone == null) { |
| return null; |
| } |
| ClassDescriptor descriptor = getDescriptor(rmiClone); |
| if ((descriptor == null) || descriptor.isDescriptorTypeAggregate()) { |
| if (manager.getCascadePolicy() == MergeManager.CASCADE_BY_MAPPING){ |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[]{rmiClone})); |
| } |
| return rmiClone; |
| } |
| |
| //CR#2272 |
| logDebugMessage(rmiClone, "merge_clone_with_references"); |
| |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(rmiClone, this); |
| |
| Object mergedObject = manager.mergeChanges(implementation, null, this); |
| if (isSmartMerge()) { |
| return builder.wrapObject(mergedObject, this); |
| } else { |
| return mergedObject; |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new instance of the class registered in this unit of work. |
| * This can be used to ensure that new objects are registered correctly. |
| */ |
| @Override |
| public Object newInstance(Class theClass) { |
| //CR#2272 |
| logDebugMessage(theClass, "new_instance"); |
| |
| ClassDescriptor descriptor = getDescriptor(theClass); |
| Object newObject = descriptor.getObjectBuilder().buildNewInstance(); |
| return registerObject(newObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method will perform a delete operation on the provided objects pre-determining |
| * the objects that will be deleted by a commit of the UnitOfWork including privately |
| * owned objects. It does not execute a query for the deletion of these objects as the |
| * normal deleteobject operation does. Mainly implemented to provide EJB 3.0 deleteObject |
| * support. |
| */ |
| public void performRemove(Object toBeDeleted, Map visitedObjects) { |
| if (toBeDeleted == null) { |
| return; |
| } |
| ClassDescriptor descriptor = getDescriptor(toBeDeleted); |
| if ((descriptor == null) || descriptor.isDescriptorTypeAggregate()) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { toBeDeleted })); |
| } |
| logDebugMessage(toBeDeleted, "deleting_object"); |
| |
| //bug 4568370+4599010; fix EntityManager.remove() to handle new objects |
| if (getDeletedObjects().containsKey(toBeDeleted)){ |
| return; |
| } |
| visitedObjects.put(toBeDeleted,toBeDeleted); |
| Object registeredObject = checkIfAlreadyRegistered(toBeDeleted, descriptor); |
| if (registeredObject == null) { |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(toBeDeleted, this); |
| DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery(); |
| existQuery = (DoesExistQuery)existQuery.clone(); |
| existQuery.setObject(toBeDeleted); |
| existQuery.setPrimaryKey(primaryKey); |
| existQuery.setDescriptor(descriptor); |
| existQuery.setIsExecutionClone(true); |
| |
| existQuery.setCheckCacheFirst(true); |
| if ((Boolean) executeQuery(existQuery)){ |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_remove_detatched_entity", new Object[]{toBeDeleted})); |
| }//else, it is a new or previously deleted object that should be ignored (and delete should cascade) |
| } else { |
| //fire events only if this is a managed object |
| if (descriptor.getEventManager().hasAnyEventListeners()) { |
| org.eclipse.persistence.descriptors.DescriptorEvent event = new org.eclipse.persistence.descriptors.DescriptorEvent(toBeDeleted); |
| event.setEventCode(DescriptorEventManager.PreRemoveEvent); |
| event.setSession(this); |
| descriptor.getEventManager().executeEvent(event); |
| } |
| if (hasNewObjects() && getNewObjectsCloneToOriginal().containsKey(registeredObject)){ |
| unregisterObject(registeredObject, DescriptorIterator.NoCascading); |
| } else { |
| getDeletedObjects().put(toBeDeleted, toBeDeleted); |
| } |
| } |
| descriptor.getObjectBuilder().cascadePerformRemove(toBeDeleted, this, visitedObjects); |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade remove the private owned object from the owned UnitOfWorkChangeSet |
| */ |
| public void performRemovePrivateOwnedObjectFromChangeSet(Object toBeRemoved, Map visitedObjects) { |
| if (toBeRemoved == null) { |
| return; |
| } |
| visitedObjects.put(toBeRemoved, toBeRemoved); |
| ClassDescriptor descriptor = getDescriptor(toBeRemoved); |
| |
| // remove object from ChangeSet |
| UnitOfWorkChangeSet uowChanges = (UnitOfWorkChangeSet)getUnitOfWorkChangeSet(); |
| |
| if (uowChanges != null) { |
| ObjectChangeSet ocs = (ObjectChangeSet)uowChanges.getObjectChangeSetForClone(toBeRemoved); |
| if (ocs != null) { |
| // remove object change set and object change set from new list |
| uowChanges.removeObjectChangeSet(ocs); |
| uowChanges.removeObjectChangeSetFromNewList(ocs, this); |
| } |
| } |
| |
| // unregister object and cascade the removal |
| unregisterObject(toBeRemoved, DescriptorIterator.NoCascading); |
| |
| descriptor.getObjectBuilder().cascadePerformRemovePrivateOwnedObjectFromChangeSet(toBeRemoved, this, visitedObjects); |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not referred after commit, |
| * ensures that objects from the parent cache are not referred in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violate in the unit of work. |
| */ |
| @Override |
| public void performFullValidation() { |
| setValidationLevel(Full); |
| |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not referred after commit, |
| * ensures that objects from the parent cache are not referred in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violate in the unit of work. |
| */ |
| @Override |
| public void performPartialValidation() { |
| setValidationLevel(Partial); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is called from clone and register. It includes the processing |
| * required to clone an object, including populating attributes, putting in |
| * UOW identitymap and building a backupclone |
| */ |
| protected void populateAndRegisterObject(Object original, Object workingClone, CacheKey unitOfWorkCacheKey, CacheKey parentCacheKey, ClassDescriptor descriptor) { |
| // This must be registered before it is built to avoid cycles. |
| unitOfWorkCacheKey.setObject(workingClone); |
| unitOfWorkCacheKey.setReadTime(parentCacheKey.getReadTime()); |
| unitOfWorkCacheKey.setWriteLockValue(parentCacheKey.getWriteLockValue()); |
| |
| //Set ChangeListener for ObjectChangeTrackingPolicy and AttributeChangeTrackingPolicy, |
| //but not DeferredChangeDetectionPolicy. Build backup clone for DeferredChangeDetectionPolicy |
| //or ObjectChangeTrackingPolicy, but not for AttributeChangeTrackingPolicy. |
| // - Set listener before populating attributes so aggregates can find the parent's listener |
| ObjectChangePolicy changePolicy = descriptor.getObjectChangePolicy(); |
| changePolicy.setChangeListener(workingClone, this, descriptor); |
| changePolicy.dissableEventProcessing(workingClone); |
| |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| builder.populateAttributesForClone(original, parentCacheKey, workingClone, null, this); |
| Object backupClone = changePolicy.buildBackupClone(workingClone, builder, this); |
| // PERF: Avoid put if no backup clone. |
| if (workingClone != backupClone) { |
| getCloneMapping().put(workingClone, backupClone); |
| } |
| changePolicy.enableEventProcessing(workingClone); |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove objects from parent's identity map. |
| */ |
| protected void postMergeChanges(Set classesChanged) { |
| //bug 4730595: objects removed during flush are not removed from the cache during commit |
| if (this.unitOfWorkChangeSet.hasDeletedObjects()) { |
| Map<ObjectChangeSet, ObjectChangeSet> deletedObjects = this.unitOfWorkChangeSet.getDeletedObjects(); |
| for (Iterator<ObjectChangeSet> removedObjects = deletedObjects.keySet().iterator(); removedObjects.hasNext(); ) { |
| ObjectChangeSet removedObjectChangeSet = removedObjects.next(); |
| Object primaryKey = removedObjectChangeSet.getId(); |
| ClassDescriptor descriptor = removedObjectChangeSet.getDescriptor(); |
| // PERF: Do not remove if uow is isolated. |
| if (!descriptor.getCachePolicy().shouldIsolateObjectsInUnitOfWork()) { |
| this.parent.getIdentityMapAccessorInstance().removeFromIdentityMap(primaryKey, descriptor.getJavaClass(), descriptor, removedObjectChangeSet.getUnitOfWorkClone()); |
| classesChanged.add(descriptor.getJavaClass()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove objects deleted during commit from clone and new object cache so that these are not merged |
| */ |
| protected void preMergeChanges() { |
| if (hasObjectsDeletedDuringCommit()) { |
| for (Iterator removedObjects = getObjectsDeletedDuringCommit().keySet().iterator(); |
| removedObjects.hasNext();) { |
| Object removedObject = removedObjects.next(); |
| getCloneMapping().remove(removedObject); |
| // PERF: Avoid initialization of new objects if none. |
| if (hasNewObjects()) { |
| Object referenceObjectToRemove = getNewObjectsCloneToOriginal().get(removedObject); |
| if (referenceObjectToRemove != null) { |
| getNewObjectsCloneToOriginal().remove(removedObject); |
| getNewObjectsOriginalToClone().remove(referenceObjectToRemove); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Print the objects in the unit of work. |
| * The output of this method will be logged to this unit of work's SessionLog at SEVERE level. |
| */ |
| @Override |
| public void printRegisteredObjects() { |
| if (shouldLog(SessionLog.SEVERE, SessionLog.CACHE)) { |
| basicPrintRegisteredObjects(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to process delete queries that pass through the unitOfWork |
| * It is extracted out of the internalExecuteQuery method to reduce duplication |
| */ |
| public Object processDeleteObjectQuery(DeleteObjectQuery deleteQuery) { |
| // We must ensure that we delete the clone not the original, (this can happen in the mappings update) |
| if (deleteQuery.getObject() == null) {// Must validate. |
| throw QueryException.objectToModifyNotSpecified(deleteQuery); |
| } |
| |
| ClassDescriptor descriptor = deleteQuery.getDescriptor(); |
| if(descriptor == null) { |
| descriptor = getDescriptor(deleteQuery.getObject()); |
| } |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(deleteQuery.getObject(), this); |
| |
| if (isClassReadOnly(implementation.getClass(), descriptor)) { |
| throw QueryException.cannotDeleteReadOnlyObject(implementation); |
| } |
| |
| if (isCloneNewObject(implementation)) { |
| unregisterObject(implementation); |
| return implementation; |
| } |
| Object primaryKey = builder.extractPrimaryKeyFromObject(implementation, this); |
| Object clone = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, implementation.getClass(), descriptor); |
| if (clone == null) { |
| clone = implementation; |
| } |
| |
| // Register will wrap so must unwrap again. |
| clone = builder.unwrapObject(clone, this); |
| |
| deleteQuery.setObject(clone); |
| if (!getCommitManager().isActive()) { |
| getDeletedObjects().put(clone, primaryKey); |
| return clone; |
| } else { |
| // If the object has already been deleted i.e. private-owned + deleted then don't do it twice. |
| if (hasObjectsDeletedDuringCommit()) { |
| if (getObjectsDeletedDuringCommit().containsKey(clone)) { |
| return clone; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Print the objects in the unit of work. |
| */ |
| protected void basicPrintRegisteredObjects() { |
| String cr = Helper.cr(); |
| StringWriter writer = new StringWriter(); |
| writer.write(LoggingLocalization.buildMessage("unitofwork_identity_hashcode", new Object[] { cr, String.valueOf(System.identityHashCode(this)) })); |
| if (hasDeletedObjects()) { |
| writer.write(cr + LoggingLocalization.buildMessage("deleted_objects")); |
| for (Iterator enumtr = getDeletedObjects().keySet().iterator(); enumtr.hasNext();) { |
| Object object = enumtr.next(); |
| writer.write(LoggingLocalization.buildMessage("key_identity_hash_code_object", new Object[] { cr, getDescriptor(object).getObjectBuilder().extractPrimaryKeyFromObject(object, this), "\t", String.valueOf(System.identityHashCode(object)), object })); |
| } |
| } |
| writer.write(cr + LoggingLocalization.buildMessage("all_registered_clones")); |
| for (Iterator enumtr = getCloneMapping().keySet().iterator(); enumtr.hasNext();) { |
| Object object = enumtr.next(); |
| writer.write(LoggingLocalization.buildMessage("key_identity_hash_code_object", new Object[] { cr, getDescriptor(object).getObjectBuilder().extractPrimaryKeyFromObject(object, this), "\t", String.valueOf(System.identityHashCode(object)), object })); |
| } |
| if (hasNewObjectsInParentOriginalToClone()) { |
| writer.write(cr + LoggingLocalization.buildMessage("new_objects")); |
| for (Iterator enumtr = getNewObjectsCloneToOriginal().keySet().iterator(); |
| enumtr.hasNext();) { |
| Object object = enumtr.next(); |
| writer.write(LoggingLocalization.buildMessage("key_identity_hash_code_object", new Object[] { cr, getDescriptor(object).getObjectBuilder().extractPrimaryKeyFromObject(object, this), "\t", String.valueOf(System.identityHashCode(object)), object })); |
| } |
| } |
| log(SessionLog.SEVERE, SessionLog.TRANSACTION, writer.toString(), null, null, false); |
| } |
| |
| /** |
| * PUBLIC: |
| * Register the objects with the unit of work. |
| * All newly created root domain objects must be registered to be inserted on commit. |
| * Also any existing objects that will be edited and were not read from this unit of work |
| * must also be registered. |
| * Once registered any changes to the objects will be committed to the database on commit. |
| * |
| * @return is the clones of the original objects, the return value must be used for editing. |
| * Editing the original is not allowed in the unit of work. |
| */ |
| @Override |
| public Vector registerAllObjects(Collection domainObjects) { |
| Vector clones = new Vector(domainObjects.size()); |
| for (Iterator objectsEnum = domainObjects.iterator(); objectsEnum.hasNext();) { |
| clones.addElement(registerObject(objectsEnum.next())); |
| } |
| return clones; |
| } |
| |
| /** |
| * PUBLIC: |
| * Register the objects with the unit of work. |
| * All newly created root domain objects must be registered to be inserted on commit. |
| * Also any existing objects that will be edited and were not read from this unit of work |
| * must also be registered. |
| * Once registered any changes to the objects will be committed to the database on commit. |
| * |
| * @return is the clones of the original objects, the return value must be used for editing. |
| * Editing the original is not allowed in the unit of work. |
| */ |
| public Vector registerAllObjects(Vector domainObjects) throws DatabaseException, OptimisticLockException { |
| Vector clones = new Vector(domainObjects.size()); |
| for (Enumeration objectsEnum = domainObjects.elements(); objectsEnum.hasMoreElements();) { |
| clones.addElement(registerObject(objectsEnum.nextElement())); |
| } |
| return clones; |
| } |
| |
| /** |
| * ADVANCED: |
| * Register the existing object with the unit of work. |
| * This is a advanced API that can be used if the application can guarantee the object exists on the database. |
| * When registerObject is called the unit of work determines existence through the descriptor's doesExist setting. |
| * |
| * @return The clone of the original object, the return value must be used for editing. |
| * Editing the original is not allowed in the unit of work. |
| */ |
| @Override |
| public Object registerExistingObject(Object existingObject) { |
| return this.registerExistingObject(existingObject, false); |
| } |
| |
| /** |
| * ADVANCED: |
| * Register the existing object with the unit of work. |
| * This is a advanced API that can be used if the application can guarantee the object exists on the database. |
| * When registerObject is called the unit of work determines existence through the descriptor's doesExist setting. |
| * |
| * @return The clone of the original object, the return value must be used for editing. |
| * Editing the original is not allowed in the unit of work. |
| */ |
| public Object registerExistingObject(Object existingObject, boolean isFromSharedCache) { |
| if (existingObject == null) { |
| return null; |
| } |
| ClassDescriptor descriptor = getDescriptor(existingObject); |
| if (descriptor == null) { |
| throw DescriptorException.missingDescriptor(existingObject.getClass().toString()); |
| } |
| if (this.isClassReadOnly(descriptor.getJavaClass(), descriptor)) { |
| return existingObject; |
| } |
| |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(existingObject, this); |
| Object registeredObject = this.registerExistingObject(implementation, descriptor, null, isFromSharedCache); |
| |
| // Bug # 3212057 - workaround JVM bug (MWN) |
| if (implementation != existingObject) { |
| return builder.wrapObject(registeredObject, this); |
| } else { |
| return registeredObject; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Register the existing object with the unit of work. |
| * This is a advanced API that can be used if the application can guarantee the object exists on the database. |
| * When registerObject is called the unit of work determines existence through the descriptor's doesExist setting. |
| * |
| * @return The clone of the original object, the return value must be used for editing. |
| * Editing the original is not allowed in the unit of work. |
| */ |
| public Object registerExistingObject(Object objectToRegister, ClassDescriptor descriptor, Object queryPrimaryKey, boolean isFromSharedCache) { |
| if (this.isClassReadOnly(descriptor.getJavaClass(), descriptor)) { |
| return objectToRegister; |
| } |
| if (isAfterWriteChangesButBeforeCommit()) { |
| throw ValidationException.illegalOperationForUnitOfWorkLifecycle(this.lifecycle, "registerExistingObject"); |
| } |
| if (descriptor.isDescriptorTypeAggregate()) { |
| throw ValidationException.cannotRegisterAggregateObjectInUnitOfWork(objectToRegister.getClass()); |
| } |
| CacheKey cacheKey = null; |
| Object objectToRegisterId = null; |
| Thread currentThread = Thread.currentThread(); |
| if (project.allowExtendedCacheLogging()) { |
| //Not null if objectToRegister exist in cache |
| Session rootSession = this.getRootSession(null).getParent() == null ? this.getRootSession(null) : this.getRootSession(null).getParent(); |
| cacheKey = ((org.eclipse.persistence.internal.sessions.IdentityMapAccessor)rootSession.getIdentityMapAccessor()).getCacheKeyForObject(objectToRegister); |
| objectToRegisterId = this.getId(objectToRegister); |
| if (cacheKey != null) { |
| log(SessionLog.FINEST, SessionLog.CACHE, "cache_hit", new Object[] {objectToRegister.getClass(), objectToRegisterId}); |
| } else { |
| log(SessionLog.FINEST, SessionLog.CACHE, "cache_miss", new Object[] {objectToRegister.getClass(), objectToRegisterId}); |
| } |
| if (cacheKey != null && currentThread.hashCode() != cacheKey.CREATION_THREAD_HASHCODE) { |
| log(SessionLog.FINEST, SessionLog.CACHE, "cache_thread_info", new Object[]{objectToRegister.getClass(), objectToRegisterId, |
| cacheKey.CREATION_THREAD_ID, cacheKey.CREATION_THREAD_NAME, |
| currentThread.getId(), currentThread.getName()}); |
| } |
| } |
| if (project.allowExtendedThreadLogging()) { |
| if (this.CREATION_THREAD_HASHCODE != currentThread.hashCode()) { |
| log(SessionLog.SEVERE, SessionLog.THREAD, "unit_of_work_thread_info", new Object[]{this.getName(), |
| this.CREATION_THREAD_ID, this.CREATION_THREAD_NAME, |
| currentThread.getId(), currentThread.getName()}); |
| if (project.allowExtendedThreadLoggingThreadDump()) { |
| log(SessionLog.SEVERE, SessionLog.THREAD, "unit_of_work_thread_info_thread_dump", new Object[]{ |
| this.CREATION_THREAD_ID, this.CREATION_THREAD_NAME, this.creationThreadStackTrace, |
| currentThread.getId(), currentThread.getName(), ConcurrencyUtil.SINGLETON.enrichGenerateThreadDumpForCurrentThread()}); |
| } |
| } |
| } |
| //CR#2272 |
| logDebugMessage(objectToRegister, "register_existing"); |
| Object registeredObject; |
| try { |
| startOperationProfile(SessionProfiler.Register); |
| registeredObject = checkIfAlreadyRegistered(objectToRegister, descriptor); |
| if (registeredObject == null) { |
| // Check if object is existing, if it is it must be cloned into the unit of work |
| // otherwise it is a new object |
| Object primaryKey = queryPrimaryKey; |
| if (primaryKey == null){ |
| primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(objectToRegister, this, true); |
| } |
| if (descriptor.shouldLockForClone()|| !isFromSharedCache || (descriptor.isProtectedIsolation() && !(objectToRegister instanceof PersistenceEntity))){ |
| // The primary key may be null for a new object in a nested unit of work (is existing in nested, new in parent). |
| if (primaryKey != null) { |
| // Always check the cache first. |
| registeredObject = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, objectToRegister, objectToRegister.getClass(), true, descriptor); |
| } |
| } else { |
| // perform a check of the UOW identitymap. This would be done by getFromIdentityMap |
| // but that method also calls back up to the shared cache in case it is not found locally. |
| // and we wish to avoid checking the shared cache twice. |
| CacheKey localCacheKey = getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, objectToRegister.getClass(), descriptor, false); |
| if (localCacheKey != null){ |
| registeredObject = localCacheKey.getObject(); |
| } |
| } |
| if (registeredObject == null) { |
| // This is a case where the object is not in the session cache, or the session lookup has been bypassed |
| // check object for cachekey otherwise |
| // a new cache-key is used as there is no original to use for locking. |
| // It read time must be set to avoid it being invalidated. |
| cacheKey = null; |
| if (objectToRegister instanceof PersistenceEntity){ |
| cacheKey = ((PersistenceEntity)objectToRegister)._persistence_getCacheKey(); |
| } |
| if (cacheKey == null){ |
| cacheKey = new CacheKey(primaryKey); |
| cacheKey.setReadTime(System.currentTimeMillis()); |
| cacheKey.setIsolated(true); // if the cache does not have a version then this must be built from the supplied version |
| } |
| registeredObject = cloneAndRegisterObject(objectToRegister, cacheKey, descriptor); |
| } |
| } |
| //bug3659327 |
| //fetch group manager control fetch group support |
| if (descriptor.hasFetchGroupManager()) { |
| //if the object is already registered in uow, but it's partially fetched (fetch group case) |
| if (descriptor.getFetchGroupManager().shouldWriteInto(objectToRegister, registeredObject)) { |
| //there might be cases when reverting/refreshing clone is needed. |
| descriptor.getFetchGroupManager().writePartialIntoClones(objectToRegister, registeredObject, this.getBackupClone(registeredObject, descriptor), this); |
| } |
| } |
| } finally { |
| endOperationProfile(SessionProfiler.Register); |
| } |
| return registeredObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Register the new container bean with the unit of work. |
| * Normally the registerObject method should be used for all registration of new and existing objects. |
| * This version of the register method can only be used for new container beans. |
| * |
| * @see #registerObject(Object) |
| */ |
| public Object registerNewContainerBean(Object newObject) { |
| if (newObject == null) { |
| return null; |
| } |
| |
| //CR#2272 |
| logDebugMessage(newObject, "register_new"); |
| |
| startOperationProfile(SessionProfiler.Register); |
| setShouldNewObjectsBeCached(true); |
| ClassDescriptor descriptor = getDescriptor(newObject); |
| if (descriptor == null) { |
| throw DescriptorException.missingDescriptor(newObject.getClass().toString()); |
| } |
| |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| |
| //Pine Beta. Removed Checking the containerBean collection. It is not required as these are new objects. |
| // Was removed to prevent issue where weblogic would re-use beans from the pool in a single transaction |
| // Ensure that the registered object is the one from the parent cache. |
| if (shouldPerformFullValidation()) { |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(newObject, this); |
| Object objectFromCache = this.parent.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, descriptor.getJavaClass(), descriptor); |
| if (objectFromCache != null) { |
| throw ValidationException.wrongObjectRegistered(newObject, objectFromCache); |
| } |
| } |
| |
| Object original = builder.buildNewInstance(); |
| builder.copyInto(newObject, original); |
| |
| Object clone = registerObject(original); |
| getContainerBeans().put(newObject, clone); |
| |
| endOperationProfile(SessionProfiler.Register); |
| |
| return newObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Register the new Bean with the unit of work. |
| * This will register the new Bean with cloning. |
| * Normally the registerObject method should be used for all registration of new and existing objects. |
| * This version of the register method can only be used for new objects. |
| */ |
| public Object registerNewContainerBeanForCMP(Object newObject) { |
| if (newObject == null) { |
| return null; |
| } |
| |
| //CR#2272 |
| logDebugMessage(newObject, "register_new_bean"); |
| |
| startOperationProfile(SessionProfiler.Register); |
| |
| Object clone = cloneAndRegisterNewObject(newObject, false); |
| |
| endOperationProfile(SessionProfiler.Register); |
| |
| return clone; |
| } |
| |
| /** |
| * ADVANCED: |
| * Register the new object with the unit of work. |
| * This will register the new object without cloning. |
| * Normally the registerObject method should be used for all registration of new and existing objects. |
| * This version of the register method can only be used for new objects. |
| * This method should only be used if a new object is desired to be registered without cloning. |
| * |
| * @see #registerObject(Object) |
| */ |
| @Override |
| public Object registerNewObject(Object newObject) { |
| if (newObject == null) { |
| return null; |
| } |
| ClassDescriptor descriptor = getDescriptor(newObject); |
| if (descriptor == null) { |
| throw DescriptorException.missingDescriptor(newObject.getClass().toString()); |
| } |
| |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(newObject, this); |
| |
| this.registerNewObject(implementation, descriptor); |
| |
| if (implementation == newObject) { |
| return newObject; |
| } else { |
| return builder.wrapObject(implementation, this); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Updated to allow passing in of the object's descriptor |
| * |
| * Register the new object with the unit of work. |
| * This will register the new object without cloning. |
| * Normally the registerObject method should be used for all registration of new and existing objects. |
| * This version of the register method can only be used for new objects. |
| * This method should only be used if a new object is desired to be registered without cloning. |
| * |
| * @see #registerObject(Object) |
| */ |
| protected Object registerNewObject(Object implementation, ClassDescriptor descriptor) { |
| if (isAfterWriteChangesButBeforeCommit()) { |
| throw ValidationException.illegalOperationForUnitOfWorkLifecycle(this.lifecycle, "registerNewObject"); |
| } |
| if (descriptor.isDescriptorTypeAggregate()) { |
| throw ValidationException.cannotRegisterAggregateObjectInUnitOfWork(implementation.getClass()); |
| } |
| try { |
| //CR#2272 |
| logDebugMessage(implementation, "register_new"); |
| |
| startOperationProfile(SessionProfiler.Register); |
| Object registeredObject = checkIfAlreadyRegistered(implementation, descriptor); |
| if (registeredObject == null) { |
| // Ensure that the registered object is the one from the parent cache. |
| if (shouldPerformFullValidation()) { |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(implementation, this); |
| Object objectFromCache = this.parent.getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, implementation.getClass(), descriptor); |
| if (objectFromCache != null) { |
| throw ValidationException.wrongObjectRegistered(implementation, objectFromCache); |
| } |
| } |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| // New objects should not have an original until merge. |
| Object original = null; |
| |
| Object backupClone = implementation; |
| if (!descriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy()) { |
| backupClone = builder.buildNewInstance(); |
| } |
| getCloneMapping().put(implementation, backupClone); |
| |
| // Check if the new objects should be cached. |
| registerNewObjectClone(implementation, original, descriptor); //this method calls registerNewObjectInIdentityMap |
| } |
| } finally { |
| endOperationProfile(SessionProfiler.Register); |
| } |
| |
| //as this is register new return the object passed in. |
| return implementation; |
| } |
| |
| /** |
| * INTERNAL: |
| * Discover any new objects referenced from registered objects and persist them. |
| * This is similar to persist, except that it traverses (all changed or new) objects |
| * during the commit to find any unregistered new objects and persists them. |
| * Only objects referenced by cascade persist mappings will be persisted, |
| * an error will be thrown from non-cascade persist mappings to new objects (detached existing object are ok...in thoery). |
| * This is specific to EJB 3.0 support. |
| * @param newObjects any new objects found must be added to this collection. |
| * @param cascadePersist determines if this call is cascading from a cascadePersist mapping or not. |
| */ |
| public void discoverAndPersistUnregisteredNewObjects(Object object, boolean cascadePersist, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects, Set cascadeErrors) { |
| if (object == null) { |
| return; |
| } |
| |
| if (cascadePersist && isObjectDeleted(object)) { |
| // It is deleted but reference by a cascade persist mapping, spec seems to state it should be undeleted, but seems wrong. |
| // TODO: Reconsider this. |
| undeleteObject(object); |
| } |
| |
| // EL Bug 343925 - Add all unregistered objects which are not marked as cascade persist |
| // to cascadeErrors so that all the registered objects and their mappings are iterated |
| // to discover if the object is marked with CascadeType.PERSIST using a different mapping |
| // of a different registered object. Throw IllegalStateException only after iterating through |
| // all the registered objects and their mappings is completed, and if cascadeErrors |
| // collection contains any unregistered object. |
| if (visitedObjects.containsKey(object) && !cascadeErrors.contains(object)) { |
| return; |
| } |
| visitedObjects.put(object, object); |
| |
| // If this object is deleted, avoid any discovery and return. |
| if (isObjectDeleted(object)) { |
| return; |
| } |
| |
| ClassDescriptor descriptor = getDescriptor(object); |
| // If the object is read-only or deleted then do not continue the traversal. |
| if (isClassReadOnly(object.getClass(), descriptor)) { |
| return; |
| } |
| if (!isObjectRegistered(object)) { |
| if (cascadePersist) { |
| // It is new and reference by a cascade persist mapping, persist it. |
| // This will also throw an exception if it is an unregistered existing object (which the spec seems to state). |
| registerNotRegisteredNewObjectForPersist(object, descriptor); |
| newObjects.put(object, object); |
| if (cascadeErrors.contains(object)) { |
| cascadeErrors.remove(object); |
| } |
| } else if (checkForUnregisteredExistingObject(object)) { |
| // Always ignore unregistered existing objects in JPA (when not cascade persist). |
| // If the object exists we need to keep a record of this object to ignore it, |
| // also need to stop iterating over it. |
| // Spec seems to say this is undefined. |
| unregisteredExistingObjects.put(object, object); |
| return; |
| } else { |
| // It is new but not referenced by a cascade persist mapping, throw an error. |
| cascadeErrors.add(object); |
| return; |
| } |
| } |
| descriptor.getObjectBuilder().cascadeDiscoverAndPersistUnregisteredNewObjects(object, newObjects, unregisteredExistingObjects, visitedObjects, this, cascadeErrors); |
| } |
| |
| /** |
| * INTERNAL: |
| * Register the new object with the unit of work. |
| * This will register the new object without cloning. |
| * Checks based on existence will be completed and the create will be cascaded based on the |
| * object's mappings cascade requirements. This is specific to EJB 3.0 support. |
| * @see #registerObject(Object) |
| */ |
| public void registerNewObjectForPersist(Object newObject, Map visitedObjects) { |
| if (newObject == null) { |
| return; |
| } |
| if(visitedObjects.containsKey(newObject)) { |
| return; |
| } |
| visitedObjects.put(newObject, newObject); |
| ClassDescriptor descriptor = getDescriptor(newObject); |
| if ((descriptor == null) || descriptor.isDescriptorTypeAggregate()) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { newObject })); |
| } |
| |
| startOperationProfile(SessionProfiler.Register); |
| try { |
| Object registeredObject = checkIfAlreadyRegistered(newObject, descriptor); |
| if (registeredObject == null) { |
| registerNotRegisteredNewObjectForPersist(newObject, descriptor); |
| } else if (this.isObjectDeleted(newObject)) { |
| //if object is deleted and a create is issued on the that object |
| // then the object must be transitioned back to existing and not deleted |
| this.undeleteObject(newObject); |
| } |
| descriptor.getObjectBuilder().cascadeRegisterNewForCreate(newObject, this, visitedObjects); |
| // After any cascade persists and assigning any sequence numbers, |
| // update any derived id attributes on the new object. |
| updateDerivedIds(newObject, descriptor); |
| } finally { |
| endOperationProfile(SessionProfiler.Register); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the object was deleted previously (in a flush). |
| */ |
| public boolean wasDeleted(Object original) { |
| // Implemented by subclass |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Called only by registerNewObjectForPersist method, |
| * and only if newObject is not already registered. |
| * Could be overridden in subclasses. |
| */ |
| protected void registerNotRegisteredNewObjectForPersist(Object newObject, ClassDescriptor descriptor) { |
| // Ensure that the registered object is not detached. |
| // Only check existence if validating, as only results in an earlier error. |
| if (shouldValidateExistence()) { |
| DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery(); |
| existQuery = (DoesExistQuery)existQuery.clone(); |
| existQuery.setObject(newObject); |
| existQuery.setDescriptor(descriptor); |
| existQuery.setIsExecutionClone(true); |
| if ((Boolean) executeQuery(existQuery)) { |
| throw ValidationException.cannotPersistExistingObject(newObject, this); |
| } |
| } |
| |
| logDebugMessage(newObject, "register_new_for_persist"); |
| |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| // New objects should not have an original until merge. |
| Object original = null; |
| |
| Object backupClone = newObject; |
| if (!descriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy()) { |
| backupClone = builder.buildNewInstance(); |
| } |
| getCloneMapping().put(newObject, backupClone); |
| assignSequenceNumber(newObject, descriptor); |
| |
| // Check if the new objects should be cached. |
| registerNewObjectClone(newObject, original, descriptor); //this method calls registerNewObjectInIdentityMap |
| } |
| |
| /** |
| * INTERNAL: |
| * Register the working copy of a new object and its original. |
| * The user must edit the working copy and the original is used to merge into the parent. |
| * This mapping is kept both ways because lookup is required in both directions. |
| */ |
| protected void registerNewObjectClone(Object clone, Object original, ClassDescriptor descriptor) { |
| // Check if the new objects should be cached. |
| registerNewObjectInIdentityMap(clone, original, descriptor); |
| |
| getNewObjectsCloneToOriginal().put(clone, original); |
| if (original != null) { |
| getNewObjectsOriginalToClone().put(original, clone); |
| } |
| |
| // run prePersist callbacks if any |
| if (descriptor.getEventManager().hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(clone); |
| event.setEventCode(DescriptorEventManager.PrePersistEvent); |
| event.setSession(this); |
| descriptor.getEventManager().executeEvent(event); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the new object to the cache if set to. |
| * This is useful for using mergeclone on new objects. |
| */ |
| protected void registerNewObjectInIdentityMap(Object clone, Object original, ClassDescriptor descriptor) { |
| if (shouldNewObjectsBeCached()) { |
| // Put new objects in the cache if it has a valid primary key, this allows for double new object merges, |
| // and cache hits on pk queries. |
| // PERF: Only need to extract key using object builder, it will now return null if the key is not valid. |
| Object key = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, this, true); |
| if (key != null) { |
| getIdentityMapAccessorInstance().putInIdentityMap(clone, key, null, 0, descriptor); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Register the object with the unit of work. |
| * All newly created root domain objects must be registered to be inserted on commit. |
| * Also any existing objects that will be edited and were not read from this unit of work |
| * must also be registered. |
| * Once registered any changes to the objects will be committed to the database on commit. |
| * |
| * @return the clone of the original object, the return value must be used for editing, |
| * |
| * ** Editing the original is not allowed in the unit of work. ** |
| */ |
| @Override |
| public Object registerObject(Object object) { |
| if (object == null) { |
| return null; |
| } |
| ClassDescriptor descriptor = getDescriptor(object); |
| if (descriptor == null) { |
| throw DescriptorException.missingDescriptor(object.getClass().toString()); |
| } |
| if (this.isClassReadOnly(descriptor.getJavaClass(), descriptor)) { |
| return object; |
| } |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(object, this); |
| boolean wasWrapped = implementation != object; |
| Object registeredObject = this.registerObject(implementation, descriptor); |
| if (wasWrapped) { |
| return builder.wrapObject(registeredObject, this); |
| } else { |
| return registeredObject; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Allows for calling method to provide the descriptor information for this |
| * object. Prevents double lookup of descriptor. |
| * |
| * |
| * Register the object with the unit of work. |
| * All newly created root domain objects must be registered to be inserted on commit. |
| * Also any existing objects that will be edited and were not read from this unit of work |
| * must also be registered. |
| * Once registered any changes to the objects will be committed to the database on commit. |
| * |
| * calling this method will also sort the objects into different different groups |
| * depending on if the object being registered is a bean or a regular Java |
| * object and if its updates are deferred, non-deferred or if all modifications |
| * are deferred. |
| * |
| * @return the clone of the original object, the return value must be used for editing, |
| */ |
| protected Object registerObject(Object object, ClassDescriptor descriptor) { |
| if (this.isClassReadOnly(descriptor.getJavaClass(), descriptor)) { |
| return object; |
| } |
| if (isAfterWriteChangesButBeforeCommit()) { |
| throw ValidationException.illegalOperationForUnitOfWorkLifecycle(this.lifecycle, "registerObject"); |
| } |
| |
| //CR#2272 |
| logDebugMessage(object, "register"); |
| |
| Object registeredObject; |
| try { |
| startOperationProfile(SessionProfiler.Register); |
| |
| registeredObject = internalRegisterObject(object, descriptor, false); |
| |
| } finally { |
| endOperationProfile(SessionProfiler.Register); |
| } |
| return registeredObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Register a new object from a nested unit of work into its parent. |
| */ |
| public void registerOriginalNewObjectFromNestedUnitOfWork(Object originalObject, Object backupClone, Object newInstance, ClassDescriptor descriptor) { |
| getCloneMapping().put(originalObject, backupClone); |
| registerNewObjectClone(originalObject, newInstance, descriptor); |
| } |
| |
| /** |
| * INTERNAL: |
| * Register this UnitOfWork against an external transaction controller |
| */ |
| public void registerWithTransactionIfRequired() { |
| if (this.parent.hasExternalTransactionController() && ! isSynchronized()) { |
| //TODO: Throw an exception in case the parent is already synchronized: |
| // DatabaseSession or ClientSession may have only one synchronized uow at a time. |
| boolean hasAlreadyStarted = this.parent.wasJTSTransactionInternallyStarted(); |
| this.parent.getExternalTransactionController().registerSynchronizationListener(this, this.parent); |
| |
| // CR#2998 - registerSynchronizationListener may toggle the wasJTSTransactionInternallyStarted |
| // flag. As a result, we must compare the states and if the state is changed, then we must set the |
| // setWasTransactionBegunPrematurely flag to ensure that we handle the transaction depth count |
| // appropriately |
| if (!hasAlreadyStarted && this.parent.wasJTSTransactionInternallyStarted()) { |
| // registerSynchronizationListener caused beginTransaction() called |
| // and an external transaction internally started. |
| this.setWasTransactionBegunPrematurely(true); |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Release the unit of work. This terminates this unit of work. |
| * Because the unit of work operates on its own object space (clones) no work is required. |
| * The unit of work should no longer be used or referenced by the application beyond this point |
| * so that it can be garbage collected. |
| * |
| * @see #commit() |
| */ |
| @Override |
| public void release() { |
| if (isDead()) { |
| return; |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "release_unit_of_work"); |
| if (this.eventManager != null) { |
| this.eventManager.preReleaseUnitOfWork(); |
| } |
| |
| RuntimeException exception = null; |
| // If already succeeded at a writeChanges(), then transaction still open. |
| // As already issued sql must at least mark the external transaction for rollback only. |
| if (this.lifecycle == CommitTransactionPending) { |
| if (hasModifications() || wasTransactionBegunPrematurely()) { |
| try { |
| rollbackTransaction(false); |
| } catch (RuntimeException ex) { |
| exception = ex; |
| } |
| setWasTransactionBegunPrematurely(false); |
| } |
| } else if (wasTransactionBegunPrematurely() && (!this.isNestedUnitOfWork)) { |
| rollbackTransaction(); |
| setWasTransactionBegunPrematurely(false); |
| } |
| releaseWriteLocks(); |
| setDead(); |
| if (shouldClearForCloseOnRelease()) { |
| //uow still could be used for instantiating of ValueHolders after it's released. |
| clearForClose(false); |
| } |
| // To be safe clean up as much state as possible. |
| this.batchQueries = null; |
| this.parent.releaseUnitOfWork(this); |
| if (this.eventManager != null) { |
| this.eventManager.postReleaseUnitOfWork(); |
| } |
| incrementProfile(SessionProfiler.UowReleased); |
| if (exception != null) { |
| throw exception; |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Empties the set of read-only classes. |
| * It is illegal to call this method on nested UnitOfWork objects. A nested UnitOfWork |
| * cannot have a subset of its parent's set of read-only classes. |
| * Also removes classes which are read only because their descriptors are readonly |
| */ |
| @Override |
| public void removeAllReadOnlyClasses() throws ValidationException { |
| if (this.isNestedUnitOfWork) { |
| throw ValidationException.cannotRemoveFromReadOnlyClassesInNestedUnitOfWork(); |
| } |
| getReadOnlyClasses().clear(); |
| } |
| |
| /** |
| * ADVANCED: |
| * Remove optimistic read lock from the object |
| * See forceUpdateToVersionField(Object) |
| */ |
| @Override |
| public void removeForceUpdateToVersionField(Object lockObject) { |
| getOptimisticReadLockObjects().remove(lockObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove a privately owned object from the privateOwnedObjects Map. |
| * The UnitOfWork needs to keep track of privately owned objects in order to |
| * detect and remove private owned objects which are de-referenced. |
| * When an object (which is referenced) is removed from the privateOwnedObjects Map, |
| * it is no longer considered for removal from ChangeSets and the UnitOfWork identitymap. |
| */ |
| public void removePrivateOwnedObject(DatabaseMapping mapping, Object privateOwnedObject) { |
| if (this.privateOwnedObjects != null) { |
| Set objectsForMapping = this.privateOwnedObjects.get(mapping); |
| if (objectsForMapping != null){ |
| objectsForMapping.remove(privateOwnedObject); |
| if (objectsForMapping.isEmpty()) { |
| this.privateOwnedObjects.remove(mapping); |
| } |
| } |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Removes a Class from the receiver's set of read-only classes. |
| * It is illegal to try to send this method to a nested UnitOfWork. |
| */ |
| @Override |
| public void removeReadOnlyClass(Class theClass) throws ValidationException { |
| if (!canChangeReadOnlySet()) { |
| throw ValidationException.cannotModifyReadOnlyClassesSetAfterUsingUnitOfWork(); |
| } |
| if (this.isNestedUnitOfWork) { |
| throw ValidationException.cannotRemoveFromReadOnlyClassesInNestedUnitOfWork(); |
| } |
| getReadOnlyClasses().remove(theClass); |
| |
| } |
| |
| /** |
| * PUBLIC: |
| * Revert all changes made to any registered object. |
| * Clear all deleted and new objects. |
| * Revert should not be confused with release which it the normal compliment to commit. |
| * Revert is more similar to commit and resume, however reverts all changes and resumes. |
| * If you do not require to resume the unit of work release should be used instead. |
| * |
| * @see #commitAndResume() |
| * @see #release() |
| */ |
| @Override |
| public void revertAndResume() { |
| if (isAfterWriteChangesButBeforeCommit()) { |
| throw ValidationException.illegalOperationForUnitOfWorkLifecycle(this.lifecycle, "revertAndResume"); |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "revert_unit_of_work"); |
| |
| MergeManager manager = new MergeManager(this); |
| manager.mergeOriginalIntoWorkingCopy(); |
| manager.setForRefresh(true); |
| manager.cascadeAllParts(); |
| for (Iterator cloneEnum = new IdentityHashMap(getCloneMapping()).keySet().iterator(); cloneEnum.hasNext();) { |
| Object clone = cloneEnum.next(); |
| |
| // Revert each clone. |
| manager.mergeChanges(clone, null, this); |
| ClassDescriptor descriptor = getDescriptor(clone); |
| |
| //revert the tracking policy |
| descriptor.getObjectChangePolicy().revertChanges(clone, descriptor, this, getCloneMapping(), true); |
| } |
| |
| // PERF: Avoid initialization of new objects if none. |
| if (hasNewObjects()) { |
| for (Iterator cloneEnum = getNewObjectsCloneToOriginal().keySet().iterator(); |
| cloneEnum.hasNext();) { |
| Object clone = cloneEnum.next(); |
| |
| // De-register the object. |
| getCloneMapping().remove(clone); |
| } |
| if (getUnitOfWorkChangeSet() != null) { |
| ((UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).getNewObjectChangeSets().clear(); |
| } |
| } |
| |
| // Clear new and deleted objects. |
| setNewObjectsCloneToOriginal(null); |
| setNewObjectsOriginalToClone(null); |
| // Reset the all clones collection |
| this.allClones = null; |
| // 2612538 - the default size of Map (32) is appropriate |
| setObjectsDeletedDuringCommit(new IdentityHashMap()); |
| setDeletedObjects(new IdentityHashMap()); |
| setRemovedObjects(new IdentityHashMap()); |
| setUnregisteredNewObjects(new IdentityHashMap()); |
| if (this.isNestedUnitOfWork) { |
| discoverAllUnregisteredNewObjectsInParent(); |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work"); |
| } |
| |
| /** |
| * PUBLIC: |
| * Revert the object's attributes from the parent. |
| * This also reverts the object privately-owned parts. |
| * |
| * @return the object reverted. |
| * @see #shallowRevertObject(Object) |
| * @see #deepRevertObject(Object) |
| */ |
| @Override |
| public Object revertObject(Object clone) { |
| return revertObject(clone, MergeManager.CASCADE_PRIVATE_PARTS); |
| } |
| |
| /** |
| * INTERNAL: |
| * Revert the object's attributes from the parent. |
| * This uses merging to merge the object changes. |
| */ |
| public Object revertObject(Object clone, int cascadeDepth) { |
| if (clone == null) { |
| return null; |
| } |
| |
| //CR#2272 |
| logDebugMessage(clone, "revert"); |
| |
| ClassDescriptor descriptor = getDescriptor(clone); |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(clone, this); |
| |
| MergeManager manager = new MergeManager(this); |
| manager.mergeOriginalIntoWorkingCopy(); |
| manager.setForRefresh(true); |
| manager.setCascadePolicy(cascadeDepth); |
| try { |
| manager.mergeChanges(implementation, null, this); |
| } catch (RuntimeException exception) { |
| return handleException(exception); |
| } |
| if (cascadeDepth != MergeManager.NO_CASCADE) { |
| builder.instantiateEagerMappings(clone, this); |
| } |
| return clone; |
| } |
| |
| /** |
| * INTERNAL: |
| * This is internal to the uow, transactions should not be used explicitly in a uow. |
| * The uow shares its parents transactions. |
| */ |
| @Override |
| public void rollbackTransaction() throws DatabaseException { |
| incrementProfile(SessionProfiler.UowRollbacks); |
| this.parent.rollbackTransaction(); |
| } |
| |
| /** |
| * INTERNAL: |
| * rollbackTransaction() with a twist for external transactions. |
| * <p> |
| * writeChanges() is called outside the JTA beforeCompletion(), so the |
| * accompanying exception won't propagate up and cause a rollback by itself. |
| * <p> |
| * Instead must mark the transaction for rollback only here. |
| * <p> |
| * If internally started external transaction or no external transaction |
| * can still rollback normally. |
| * @param intendedToCommitTransaction whether we were inside a commit or just trying to |
| * write out changes early. |
| */ |
| protected void rollbackTransaction(boolean intendedToCommitTransaction) throws DatabaseException { |
| if (!intendedToCommitTransaction && this.parent.hasExternalTransactionController() && !this.parent.wasJTSTransactionInternallyStarted()) { |
| this.parent.getExternalTransactionController().markTransactionForRollback(); |
| } |
| rollbackTransaction(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Scans the UnitOfWork identity map for conforming instances. |
| * <p> |
| * Later this method can be made recursive to check all parent units of |
| * work also. |
| * @param selectionCriteria must be cloned and specially prepared for conforming |
| * @return Map to facilitate merging with conforming instances |
| * returned from a query on the database. |
| */ |
| public Map<Object, Object> scanForConformingInstances(Expression selectionCriteria, Class referenceClass, AbstractRecord arguments, ObjectLevelReadQuery query) { |
| // for bug 3568141 use the painstaking shouldTriggerIndirection if set |
| int policy = query.getInMemoryQueryIndirectionPolicyState(); |
| if (policy != InMemoryQueryIndirectionPolicy.SHOULD_TRIGGER_INDIRECTION) { |
| policy = InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_NOT_CONFORMED; |
| } |
| Map<Object, Object> indexedInterimResult = new IdentityHashMap<>(); |
| try { |
| List fromCache = null; |
| if (selectionCriteria != null) { |
| // assume objects that have the compared relationship |
| // untriggered do not conform as they have not been changed. |
| // bug 2637555 |
| fromCache = getIdentityMapAccessor().getAllFromIdentityMap(selectionCriteria, referenceClass, arguments, policy); |
| for (Object object : fromCache) { |
| if (!isObjectDeleted(object)) { |
| indexedInterimResult.put(object, object); |
| } |
| } |
| } |
| |
| // Add any new objects that conform to the query. |
| List newObjects = null; |
| newObjects = getAllFromNewObjects(selectionCriteria, referenceClass, arguments, policy); |
| for (Object object : newObjects) { |
| if (!isObjectDeleted(object)) { |
| indexedInterimResult.put(object, object); |
| } |
| } |
| } catch (QueryException exception) { |
| if (getShouldThrowConformExceptions() == THROW_ALL_CONFORM_EXCEPTIONS) { |
| throw exception; |
| } |
| } |
| return indexedInterimResult; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to set the collections of all objects in the UnitOfWork. |
| */ |
| protected void setAllClonesCollection(Map objects) { |
| this.allClones = objects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the clone mapping. |
| * The clone mapping contains clone of all registered objects, |
| * this is required to store the original state of the objects when registered |
| |
| * so that only what is changed will be committed to the database and the parent, |
| * (this is required to support parallel unit of work). |
| */ |
| protected void setCloneMapping(Map cloneMapping) { |
| this.cloneMapping = cloneMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * This is only used for EJB entity beans to manage beans accessed in a transaction context. |
| */ |
| protected void setContainerBeans(Map containerBeans) { |
| this.containerBeans = containerBeans; |
| } |
| |
| /** |
| * INTERNAL: |
| * This is only used for EJB entity beans to manage beans accessed in a transaction context. |
| */ |
| protected void setContainerUnitOfWork(UnitOfWorkImpl containerUnitOfWork) { |
| this.containerUnitOfWork = containerUnitOfWork; |
| } |
| |
| /** |
| * INTERNAL: |
| * set UoW lifecycle state variable to DEATH |
| */ |
| public void setDead() { |
| setLifecycle(Death); |
| } |
| |
| /** |
| * INTERNAL: |
| * The deleted objects stores any objects removed during the unit of work. |
| * On commit they will all be removed from the database. |
| */ |
| protected void setDeletedObjects(Map deletedObjects) { |
| this.deletedObjects = deletedObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * The life cycle tracks if the unit of work is active and is used for JTS. |
| */ |
| protected void setLifecycle(int lifecycle) { |
| this.lifecycle = lifecycle; |
| } |
| |
| /** |
| * INTERNAL: |
| * A reference to the last used merge manager. This is used to track locked |
| * objects. |
| */ |
| public void setMergeManager(MergeManager mergeManager) { |
| this.lastUsedMergeManager = mergeManager; |
| } |
| |
| /** |
| * INTERNAL: |
| * The new objects stores any objects newly created during the unit of work. |
| * On commit they will all be inserted into the database. |
| */ |
| protected void setNewObjectsCloneToOriginal(Map newObjects) { |
| this.newObjectsCloneToOriginal = newObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * The new objects stores any objects newly created during the unit of work. |
| * On commit they will all be inserted into the database. |
| */ |
| protected void setNewObjectsOriginalToClone(Map newObjects) { |
| this.newObjectsOriginalToClone = newObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the objects that have been deleted. |
| */ |
| public void setObjectsDeletedDuringCommit(Map deletedObjects) { |
| objectsDeletedDuringCommit = deletedObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the parent. |
| * This is a unit of work if nested, otherwise a database session or client session. |
| */ |
| public void setParent(AbstractSession parent) { |
| this.parent = parent; |
| } |
| |
| /** |
| * INTERNAL: |
| * set UoW lifecycle state variable to PENDING_MERGE |
| */ |
| public void setPendingMerge() { |
| setLifecycle(MergePending); |
| } |
| |
| /** |
| * @param preDeleteComplete the preDeleteComplete to set |
| */ |
| public void setPreDeleteComplete(boolean preDeleteComplete) { |
| this.preDeleteComplete = preDeleteComplete; |
| } |
| |
| /** |
| * INTERNAL: |
| * Gives a new set of read-only classes to the receiver. |
| * This set of classes given are checked that subclasses of a read-only class are also |
| * in the read-only set provided. |
| */ |
| public void setReadOnlyClasses(List<Class> classes) { |
| if (classes.isEmpty()) { |
| this.readOnlyClasses = null; |
| return; |
| } |
| int size = classes.size(); |
| this.readOnlyClasses = new HashSet<>(size); |
| for (int index = 0; index < size; index++) { |
| this.readOnlyClasses.add(classes.get(index)); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The removed objects stores any newly registered objects removed during the nested unit of work. |
| * On commit they will all be removed from the parent unit of work. |
| */ |
| protected void setRemovedObjects(Map removedObjects) { |
| this.removedObjects = removedObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set if this UnitofWork should be resumed after the end of the transaction |
| * Used when UnitOfWork is synchronized with external transaction control |
| */ |
| public void setResumeUnitOfWorkOnTransactionCompletion(boolean resumeUnitOfWork) { |
| this.resumeOnTransactionCompletion = resumeUnitOfWork; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set if this UnitofWork should discover new objects on commit. |
| */ |
| public boolean shouldDiscoverNewObjects() { |
| return this.shouldDiscoverNewObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set if this UnitofWork should discover new objects on commit. |
| */ |
| public void setShouldDiscoverNewObjects(boolean shouldDiscoverNewObjects) { |
| this.shouldDiscoverNewObjects = shouldDiscoverNewObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| * True if the value holder for the joined attribute should be triggered. |
| * Required by ejb30 fetch join. |
| */ |
| public void setShouldCascadeCloneToJoinedRelationship(boolean shouldCascadeCloneToJoinedRelationship) { |
| this.shouldCascadeCloneToJoinedRelationship = shouldCascadeCloneToJoinedRelationship; |
| } |
| |
| /** |
| * INTERNAL: |
| * Calculate whether we should read directly from the database to the UOW. |
| * This may be necessary in subclasses of UnitOfWork that have special behavior |
| * @see RepeatableWriteUnitOfWork |
| */ |
| public boolean shouldForceReadFromDB(ObjectBuildingQuery query, Object primaryKey){ |
| return false; |
| } |
| |
| /** |
| * ADVANCED: |
| * By default new objects are not cached until the exist on the database. |
| * Occasionally if mergeClone is used on new objects and is required to allow multiple merges |
| * on the same new object, then if the new objects are not cached, each mergeClone will be |
| * interpretted as a different new object. |
| * By setting new objects to be cached mergeClone can be performed multiple times before commit. |
| * New objects cannot be cached unless they have a valid assigned primary key before being registered. |
| * New object with non-null invalid primary keys such as 0 or '' can cause problems and should not be used with this option. |
| */ |
| @Override |
| public void setShouldNewObjectsBeCached(boolean shouldNewObjectsBeCached) { |
| this.shouldNewObjectsBeCached = shouldNewObjectsBeCached; |
| } |
| |
| /** |
| * ADVANCED: |
| * By default deletes are performed last in a unit of work. |
| * Sometimes you may want to have the deletes performed before other actions. |
| */ |
| @Override |
| public void setShouldPerformDeletesFirst(boolean shouldPerformDeletesFirst) { |
| this.shouldPerformDeletesFirst = shouldPerformDeletesFirst; |
| } |
| |
| /** |
| * ADVANCED: |
| * Conforming queries can be set to provide different levels of detail about the |
| * exceptions they encounter |
| * There are two levels: |
| * DO_NOT_THROW_CONFORM_EXCEPTIONS = 0; |
| * THROW_ALL_CONFORM_EXCEPTIONS = 1; |
| */ |
| @Override |
| public void setShouldThrowConformExceptions(int shouldThrowExceptions) { |
| this.shouldThrowConformExceptions = shouldThrowExceptions; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set smart merge flag. This feature is used in WL to merge dependent values without SessionAccessor |
| */ |
| public static void setSmartMerge(boolean option) { |
| SmartMerge = option; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set isSynchronized flag to indicate that this session is a synchronized unit of work. |
| */ |
| @Override |
| public void setSynchronized(boolean synched) { |
| super.setSynchronized(synched); |
| this.parent.setSynchronized(synched); |
| } |
| |
| /** |
| * INTERNAL: |
| * Sets the current UnitOfWork change set to be the one passed in. |
| */ |
| public void setUnitOfWorkChangeSet(UnitOfWorkChangeSet unitOfWorkChangeSet) { |
| this.unitOfWorkChangeSet = unitOfWorkChangeSet; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to set the unregistered existing objects vector used when validation has been turned off. |
| * @param newUnregisteredExistingObjects Map |
| */ |
| protected void setUnregisteredExistingObjects(Map newUnregisteredExistingObjects) { |
| unregisteredExistingObjects = newUnregisteredExistingObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void setUnregisteredNewObjects(Map newObjects) { |
| unregisteredNewObjects = newObjects; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void setUnregisteredNewObjectsInParent(Map newObjects) { |
| unregisteredNewObjectsInParent = newObjects; |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not referred after commit, |
| * ensures that objects from the parent cache are not referred in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violate in the unit of work. |
| */ |
| @Override |
| public void setValidationLevel(int validationLevel) { |
| this.validationLevel = validationLevel; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set a flag in the root UOW to indicate that a pess. locking or non-selecting SQL query was executed |
| * and forced a transaction to be started. |
| */ |
| public void setWasTransactionBegunPrematurely(boolean wasTransactionBegunPrematurely) { |
| if (this.isNestedUnitOfWork) { |
| ((UnitOfWorkImpl)this.parent).setWasTransactionBegunPrematurely(wasTransactionBegunPrematurely); |
| } |
| this.wasTransactionBegunPrematurely = wasTransactionBegunPrematurely; |
| } |
| |
| /** |
| * PUBLIC: |
| * Merge the attributes of the clone into the unit of work copy. |
| * This can be used for objects that are returned from the client through |
| * RMI serialization (or other serialization mechanisms), because the RMI object will |
| * be a clone this will merge its attributes correctly to preserve object identity |
| * within the unit of work and record its changes. |
| * |
| * Only direct attributes are merged. |
| * |
| * @return the registered version for the clone being merged. |
| * @see #mergeClone(Object) |
| * @see #deepMergeClone(Object) |
| */ |
| @Override |
| public Object shallowMergeClone(Object rmiClone) { |
| return mergeClone(rmiClone, MergeManager.NO_CASCADE, false); |
| } |
| |
| /** |
| * PUBLIC: |
| * Revert the object's attributes from the parent. |
| * This only reverts the object's direct attributes. |
| * |
| * @return the object reverted. |
| * @see #revertObject(Object) |
| * @see #deepRevertObject(Object) |
| */ |
| @Override |
| public Object shallowRevertObject(Object clone) { |
| return revertObject(clone, MergeManager.NO_CASCADE); |
| } |
| |
| /** |
| * ADVANCED: |
| * Unregister the object with the unit of work. |
| * This can be used to delete an object that was just created and is not yet persistent. |
| * Delete object can also be used, but will result in inserting the object and then deleting it. |
| * The method will only unregister the clone, none of its parts. |
| */ |
| @Override |
| public void shallowUnregisterObject(Object clone) { |
| unregisterObject(clone, DescriptorIterator.NoCascading); |
| } |
| |
| /** |
| * INTERNAL: |
| * True if the value holder for the joined attribute should be triggered. |
| * Required by ejb30 fetch join. |
| */ |
| public boolean shouldCascadeCloneToJoinedRelationship() { |
| return shouldCascadeCloneToJoinedRelationship; |
| } |
| |
| /** |
| * ADVANCED: |
| * By default new objects are not cached until the exist on the database. |
| * Occasionally if mergeClone is used on new objects and is required to allow multiple merges |
| * on the same new object, then if the new objects are not cached, each mergeClone will be |
| * interpretted as a different new object. |
| * By setting new objects to be cached mergeClone can be performed multiple times before commit. |
| * New objects cannot be cached unless they have a valid assigned primary key before being registered. |
| * New object with non-null invalid primary keys such as 0 or '' can cause problems and should not be used with this option. |
| */ |
| @Override |
| public boolean shouldNewObjectsBeCached() { |
| return shouldNewObjectsBeCached; |
| } |
| |
| /** |
| * Return the default to determine if does-exist should be performed on persist. |
| */ |
| public boolean shouldValidateExistence() { |
| return shouldValidateExistence; |
| } |
| |
| /** |
| * Set the default to determine if does-exist should be performed on persist. |
| */ |
| public void setShouldValidateExistence(boolean shouldValidateExistence) { |
| this.shouldValidateExistence = shouldValidateExistence; |
| } |
| |
| /** |
| * ADVANCED: |
| * By default all objects are inserted and updated in the database before |
| * any object is deleted. If this flag is set to true, deletes will be |
| * performed before inserts and updates |
| */ |
| @Override |
| public boolean shouldPerformDeletesFirst() { |
| return shouldPerformDeletesFirst; |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not referred after commit, |
| * ensures that objects from the parent cache are not refered in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violate in the unit of work. |
| */ |
| @Override |
| public boolean shouldPerformFullValidation() { |
| return getValidationLevel() == Full; |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not referred after commit, |
| * ensures that objects from the parent cache are not refered in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violated in the unit of work. |
| */ |
| @Override |
| public boolean shouldPerformNoValidation() { |
| return getValidationLevel() == None; |
| } |
| |
| /** |
| * ADVANCED: |
| * The unit of work performs validations such as, |
| * ensuring multiple copies of the same object don't exist in the same unit of work, |
| * ensuring deleted objects are not refered after commit, |
| * ensures that objects from the parent cache are not refered in the unit of work cache. |
| * The level of validation can be increased or decreased for debugging purposes or under |
| * advanced situation where the application requires/desires to violate clone identity in the unit of work. |
| * It is strongly suggested that clone identity not be violate in the unit of work. |
| */ |
| @Override |
| public boolean shouldPerformPartialValidation() { |
| return getValidationLevel() == Partial; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true if this UnitofWork should be resumed after the end of the transaction |
| * Used when UnitOfWork is synchronized with external transaction control |
| */ |
| public boolean shouldResumeUnitOfWorkOnTransactionCompletion(){ |
| return this.resumeOnTransactionCompletion; |
| } |
| |
| /** |
| * INTERNAL: |
| * This is a JPA setting that is off by default in regular EclipseLink. It's |
| * used to avoid updating the shared cache when the cacheStoreMode property |
| * is set to BYPASS. |
| * @see org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork |
| */ |
| public boolean shouldStoreBypassCache() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Store the ModifyAllQuery's from the UoW in the list. They are always |
| * deferred to commit time |
| */ |
| public void storeModifyAllQuery(DatabaseQuery query) { |
| if (this.modifyAllQueries == null) { |
| this.modifyAllQueries = new ArrayList<>(); |
| } |
| |
| this.modifyAllQueries.add((ModifyAllQuery)query); |
| } |
| |
| /** |
| * INTERNAL: |
| * Store the deferred UpdateAllQuery's from the UoW in the list for execution. |
| */ |
| public void storeDeferredModifyAllQuery(DatabaseQuery query, AbstractRecord translationRow) { |
| if (deferredModifyAllQueries == null) { |
| deferredModifyAllQueries = new ArrayList(); |
| } |
| deferredModifyAllQueries.add(new Object[]{query, translationRow}); |
| } |
| |
| /** |
| * INTERNAL |
| * Synchronize the clones and update their backup copies. |
| * Called after commit and commit and resume. |
| */ |
| public void synchronizeAndResume() { |
| // For pessimistic locking all locks were released by commit. |
| this.pessimisticLockedObjects = null; |
| if (hasProperties()) { |
| getProperties().remove(LOCK_QUERIES_PROPERTY); |
| } |
| |
| resumeUnitOfWork(); |
| |
| // The collections of clones may change in the new UnitOfWork |
| this.allClones = null; |
| this.removedObjects = null; |
| //Reset lifecycle |
| this.lifecycle = Birth; |
| this.isSynchronized = false; |
| this.unregisteredNewObjectsInParent = null; |
| if (this.isNestedUnitOfWork) { |
| discoverAllUnregisteredNewObjectsInParent(); |
| } |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Resume the unit of work state after a flush, or resume operation. |
| * This will occur on commitAndResume, JPA commit and JPA flush. |
| */ |
| public void resumeUnitOfWork() { |
| // Resume new objects. |
| if (hasNewObjects() && !this.isNestedUnitOfWork) { |
| Iterator<Map.Entry<Object, Object>> newEntries = this.newObjectsCloneToOriginal.entrySet().iterator(); |
| Map cloneToOriginals = getCloneToOriginals(); |
| while (newEntries.hasNext()) { |
| Map.Entry<Object, Object> entry = newEntries.next(); |
| Object clone = entry.getKey(); |
| Object original = entry.getValue(); |
| if (original != null) { |
| // No longer new to this unit of work, so need to store original. |
| cloneToOriginals.put(clone, original); |
| } |
| } |
| this.newObjectsCloneToOriginal = null; |
| this.newObjectsOriginalToClone = null; |
| } |
| this.unregisteredExistingObjects = null; |
| this.unregisteredNewObjects = null; |
| |
| Map cloneMapping = getCloneMapping(); |
| // Clear all changes, reset backup clones. |
| // PERF: only clear objects that changed. |
| // The change sets include new objects as well. |
| if (this.unitOfWorkChangeSet != null) { |
| for (Map<ObjectChangeSet, ObjectChangeSet> objectChanges : this.unitOfWorkChangeSet.getObjectChanges().values()) { |
| for (ObjectChangeSet changeSet : objectChanges.values()) { |
| Object clone = changeSet.getUnitOfWorkClone(); |
| ClassDescriptor descriptor = this.getDescriptor(clone); |
| // Build backup clone for DeferredChangeDetectionPolicy or ObjectChangeTrackingPolicy, |
| // but not for AttributeChangeTrackingPolicy. |
| descriptor.getObjectChangePolicy().revertChanges(clone, descriptor, this, cloneMapping, false); |
| } |
| } |
| } |
| |
| // Resume deleted objects. |
| // bug 4730595: fix puts deleted objects in the UnitOfWorkChangeSet as they are removed. |
| this.deletedObjects = null; |
| // Unregister all deleted objects, |
| // keep them along with their original and backup values in unregisteredDeletedObjectsCloneToBackupAndOriginal. |
| if (hasObjectsDeletedDuringCommit()) { |
| if (this.unregisteredDeletedObjectsCloneToBackupAndOriginal == null) { |
| this.unregisteredDeletedObjectsCloneToBackupAndOriginal = new IdentityHashMap(this.objectsDeletedDuringCommit.size()); |
| } |
| Iterator<Object> iterator = this.objectsDeletedDuringCommit.keySet().iterator(); |
| Map cloneToOriginals = getCloneToOriginals(); |
| while (iterator.hasNext()) { |
| Object deletedObject = iterator.next(); |
| Object[] backupAndOriginal = {cloneMapping.get(deletedObject), cloneToOriginals.get(deletedObject)}; |
| this.unregisteredDeletedObjectsCloneToBackupAndOriginal.put(deletedObject, backupAndOriginal); |
| // If object exists in IM remove it from the IM and also from clone mapping. |
| getIdentityMapAccessorInstance().removeFromIdentityMap(deletedObject); |
| cloneMapping.remove(deletedObject); |
| } |
| } |
| this.objectsDeletedDuringCommit = null; |
| |
| // Clean up, new objects are now existing. |
| this.unitOfWorkChangeSet = null; |
| this.changeTrackedHardList = null; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used to transition an object from the deleted objects list |
| * to be simply be register. |
| */ |
| protected void undeleteObject(Object object) { |
| getDeletedObjects().remove(object); |
| if (this.parent.isUnitOfWork()) { |
| ((UnitOfWorkImpl)this.parent).undeleteObject(object); |
| } |
| } |
| /** |
| * PUBLIC: |
| * Unregister the object with the unit of work. |
| * This can be used to delete an object that was just created and is not yet persistent. |
| * Delete object can also be used, but will result in inserting the object and then deleting it. |
| * The method will only unregister the object and its privately owned parts |
| */ |
| @Override |
| public void unregisterObject(Object clone) { |
| unregisterObject(clone, DescriptorIterator.CascadePrivateParts); |
| } |
| |
| /** |
| * INTERNAL: |
| * Unregister the object with the unit of work. |
| * This can be used to delete an object that was just created and is not yet persistent. |
| * Delete object can also be used, but will result in inserting the object and then deleting it. |
| */ |
| public void unregisterObject(Object clone, int cascadeDepth) { |
| unregisterObject(clone, cascadeDepth, false); |
| } |
| /** |
| * INTERNAL: |
| * Unregister the object with the unit of work. |
| * This can be used to delete an object that was just created and is not yet persistent. |
| * Delete object can also be used, but will result in inserting the object and then deleting it. |
| */ |
| public void unregisterObject(Object clone, int cascadeDepth, boolean forDetach) { |
| // Allow register to be called with null and just return true |
| if (clone == null) { |
| return; |
| } |
| //CR#2272 |
| logDebugMessage(clone, "unregister"); |
| Object implementation = getDescriptor(clone).getObjectBuilder().unwrapObject(clone, this); |
| |
| // This define an inner class for process the itteration operation, don't be scared, its just an inner class. |
| DescriptorIterator iterator = new DescriptorIterator() { |
| @Override |
| public void iterate(Object object) { |
| if (isClassReadOnly(object.getClass(), getCurrentDescriptor())) { |
| setShouldBreak(true); |
| return; |
| } |
| |
| // Check if object exists in the IM. |
| Object primaryKey = getCurrentDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, UnitOfWorkImpl.this, true); |
| if (primaryKey != null) { |
| // If object exists in IM remove it from the IM and also from clone mapping. |
| getIdentityMapAccessorInstance().removeFromIdentityMap(primaryKey, object.getClass(), getCurrentDescriptor(), object); |
| } |
| getCloneMapping().remove(object); |
| |
| //remove from deleted objects. |
| if (hasDeletedObjects()) { |
| getDeletedObjects().remove(object); |
| } |
| |
| // Remove object from the new object cache |
| // PERF: Avoid initialization of new objects if none. |
| if (hasNewObjects()) { |
| Object original = getNewObjectsCloneToOriginal().remove(object); |
| if (original != null) { |
| getNewObjectsOriginalToClone().remove(original); |
| } |
| // Also need to remove the original merged object. |
| if (UnitOfWorkImpl.this.newObjectsCloneToMergeOriginal != null) { |
| original = UnitOfWorkImpl.this.newObjectsCloneToMergeOriginal.remove(object); |
| if (original != null) { |
| getNewObjectsOriginalToClone().remove(original); |
| } |
| } |
| } |
| } |
| }; |
| |
| iterator.setSession(this); |
| iterator.setCascadeDepth(cascadeDepth); |
| iterator.setForDetach(forDetach); |
| if (forDetach){ |
| CascadeCondition detached = iterator.new CascadeCondition(){ |
| @Override |
| public boolean shouldNotCascade(DatabaseMapping mapping){ |
| return ! (mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping)mapping).isCascadeDetach()); |
| } |
| }; |
| iterator.setCascadeCondition(detached); |
| iterator.setShouldIterateOverUninstantiatedIndirectionObjects(false); |
| } |
| iterator.setShouldIterateOnFetchGroupAttributesOnly(true); |
| iterator.startIterationOn(implementation); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method is used internally to update the tracked objects if required |
| */ |
| public void updateChangeTrackersIfRequired(Object objectToWrite, ObjectChangeSet changeSetToWrite, UnitOfWorkImpl uow, ClassDescriptor descriptor) { |
| //this is a no op in this unitOfWork Class see subclasses for implementation. |
| } |
| |
| /** |
| * INTERNAL: |
| * On persist and flush operations we must update any derived id fields. |
| */ |
| protected Object updateDerivedIds(Object clone, ClassDescriptor descriptor) { |
| Object key = null; |
| |
| if (descriptor.hasDerivedId()) { |
| for (DatabaseMapping derivesIdMapping : descriptor.getDerivesIdMappinps()) { |
| DatabaseMapping derivedIdMapping = derivesIdMapping.getDerivedIdMapping(); |
| |
| // If there is no derived id mapping, then there is no update required. Case #1a-#6a |
| // from the JPA spec. |
| if (derivedIdMapping != null) { |
| ClassDescriptor parentDescriptor = derivesIdMapping.getReferenceDescriptor(); |
| Object parentClone = derivesIdMapping.getRealAttributeValueFromObject(clone, this); |
| |
| // If the parent clone is null, we don't have any work to do, continue to the next |
| // mapping. Some mappings may be part of a composite primary key that allows for a |
| // null setting or the mapping may just not be set. |
| if (parentClone != null) { |
| // Recurse up the chain to figure out the key. The first dependent will figure |
| // it out and pass it to its sub-dependents (keeping it the same) |
| if (parentDescriptor.hasDerivedId()) { |
| key = updateDerivedIds(parentClone, parentDescriptor); |
| } else { |
| key = parentDescriptor.getCMPPolicy().createPrimaryKeyInstance(parentClone, this); |
| } |
| |
| if (derivesIdMapping.hasMapsIdValue()) { |
| // Case #1b, #2b and #3b from the JPA spec. The derived id is within our |
| // embedded id. We need to deal with that object and its mapping within the clone. |
| Object aggregateClone = derivedIdMapping.getRealAttributeValueFromObject(clone, this); |
| |
| // If the aggregate clone is null, create one and set it on the clone. |
| if (aggregateClone == null) { |
| aggregateClone = derivedIdMapping.getReferenceDescriptor().getObjectBuilder().buildNewInstance(); |
| derivedIdMapping.setRealAttributeValueInObject(clone, aggregateClone); |
| } |
| |
| // Now get the actual derived id mapping from the aggregate and populate it on the aggregate clone. |
| DatabaseMapping aggregateMapping = derivedIdMapping.getReferenceDescriptor().getObjectBuilder().getMappingForAttributeName(derivesIdMapping.getMapsIdValue()); |
| aggregateMapping.setRealAttributeValueInObject(aggregateClone, key); |
| |
| // The key should be the aggregate clone when we are done. |
| key = aggregateClone; |
| } else { |
| // Case #4b, #5b, #6b from the JPA spec. Our id mapping is the derived id. |
| // We will deal with the clone provided. |
| derivedIdMapping.setRealAttributeValueInObject(clone, key); |
| } |
| } |
| } |
| } |
| } |
| |
| // Return the key once we have had an opportunity to update all the |
| // parts of it. |
| return key; |
| } |
| |
| /** |
| * ADVANCED: |
| * This can be used to help debugging an object-space corruption. |
| * An object-space corruption is when your application has incorrectly related a clone to an original object. |
| * This method will validate that all registered objects are in a correct state and throw |
| * an error if not, it will contain the full stack of object references in the error message. |
| * If you call this method after each register or change you perform it will pin-point where the error was made. |
| */ |
| @Override |
| public void validateObjectSpace() { |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "validate_object_space"); |
| // This define an inner class for process the iteration operation, don't be scared, its just an inner class. |
| DescriptorIterator iterator = new DescriptorIterator() { |
| @Override |
| public void iterate(Object object) { |
| try { |
| if (isClassReadOnly(object.getClass(), getCurrentDescriptor())) { |
| setShouldBreak(true); |
| return; |
| } else { |
| getBackupClone(object, getCurrentDescriptor()); |
| } |
| } catch (EclipseLinkException exception) { |
| log(SessionLog.FINEST, SessionLog.TRANSACTION, "stack_of_visited_objects_that_refer_to_the_corrupt_object", getVisitedStack()); |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "corrupt_object_referenced_through_mapping", getCurrentMapping()); |
| throw exception; |
| } |
| } |
| }; |
| |
| iterator.setSession(this); |
| for (Iterator clonesEnum = getCloneMapping().keySet().iterator(); clonesEnum.hasNext();) { |
| iterator.startIterationOn(clonesEnum.next()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates if a transaction was begun by a pessimistic locking or non-selecting query. |
| * Traverse to the root UOW to get value. |
| */ |
| |
| // * 2.5.1.8 Nov 17, 2000 JED |
| // * Prs 25751 Changed to make this method public |
| public boolean wasTransactionBegunPrematurely() { |
| if (this.isNestedUnitOfWork) { |
| return ((UnitOfWorkImpl)this.parent).wasTransactionBegunPrematurely(); |
| } |
| return wasTransactionBegunPrematurely; |
| } |
| |
| /** |
| * INTERNAL: |
| * A query execution failed due to an invalid query. |
| * Re-connect and retry the query. |
| */ |
| @Override |
| public Object retryQuery(DatabaseQuery query, AbstractRecord row, DatabaseException databaseException, int retryCount, AbstractSession executionSession) { |
| return getParent().retryQuery(query, row, databaseException, retryCount, executionSession); |
| } |
| |
| /** |
| * ADVANCED: Writes all changes now before commit(). |
| * The commit process will begin and all changes will be written out to the datastore, but the datastore transaction will not |
| * be committed, nor will changes be merged into the global cache. |
| * <p> |
| * A subsequent commit (on UnitOfWork or global transaction) will be required to finalize the commit process. |
| * <p> |
| * As the commit process has begun any attempt to register objects, or execute object-level queries will |
| * generate an exception. Report queries, non-caching queries, and data read/modify queries are allowed. |
| * <p> |
| * On exception any global transaction will be rolled back or marked rollback only. No recovery of this UnitOfWork will be possible. |
| * <p> |
| * Can only be called once. It can not be used to write out changes in an incremental fashion. |
| * <p> |
| * Use to partially commit a transaction outside of a JTA transaction's callbacks. Allows you to get back any exception directly. |
| * <p> |
| * Use to commit a UnitOfWork in two stages. |
| */ |
| @Override |
| public void writeChanges() { |
| if (!isActive()) { |
| throw ValidationException.inActiveUnitOfWork("writeChanges"); |
| } |
| if (isAfterWriteChangesButBeforeCommit()) { |
| throw ValidationException.cannotWriteChangesTwice(); |
| } |
| if (this.isNestedUnitOfWork) { |
| throw ValidationException.writeChangesOnNestedUnitOfWork(); |
| } |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_flush"); |
| mergeBmpAndWsEntities(); |
| if (this.eventManager != null) { |
| this.eventManager.preCommitUnitOfWork(); |
| } |
| setLifecycle(CommitPending); |
| try { |
| commitToDatabaseWithChangeSet(false); |
| //bug:5526260 - flush batch mechanisms |
| writesCompleted(); |
| } catch (RuntimeException exception) { |
| setLifecycle(WriteChangesFailed); |
| throw exception; |
| } |
| setLifecycle(CommitTransactionPending); |
| log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_flush"); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method notifies the accessor that a particular sets of writes has |
| * completed. This notification can be used for such thing as flushing the |
| * batch mechanism |
| */ |
| @Override |
| public void writesCompleted() { |
| this.parent.writesCompleted(); |
| } |
| |
| /** |
| * log the message and debug info if option is set. (reduce the duplicate codes) |
| */ |
| private void logDebugMessage(Object object, String debugMessage) { |
| log(SessionLog.FINEST, SessionLog.TRANSACTION, debugMessage, object); |
| } |
| |
| /** |
| * INTERNAL: |
| * When in transaction batch read objects must use query local |
| * to the unit of work. |
| */ |
| public Map<ReadQuery, ReadQuery> getBatchQueries() { |
| if (batchQueries == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| batchQueries = createMap(); |
| } |
| return batchQueries; |
| } |
| |
| /** |
| * INTERNAL: |
| * When in transaction batch read objects must use query local |
| * to the unit of work. |
| */ |
| public void setBatchQueries(Map<ReadQuery, ReadQuery> batchQueries) { |
| this.batchQueries = batchQueries; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public Map getPessimisticLockedObjects() { |
| if (pessimisticLockedObjects == null) { |
| // 2612538 - the default size of Map (32) is appropriate |
| pessimisticLockedObjects = new IdentityHashMap(); |
| } |
| return pessimisticLockedObjects; |
| } |
| |
| public void addToChangeTrackedHardList(Object obj){ |
| if (this.referenceMode != ReferenceMode.HARD){ |
| this.getChangeTrackedHardList().add(obj); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public void addPessimisticLockedClone(Object clone) { |
| log(SessionLog.FINEST, SessionLog.TRANSACTION, "tracking_pl_object", clone, this.hashCode()); |
| getPessimisticLockedObjects().put(clone, clone); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add a privately owned object to the privateOwnedObjectsMap. |
| * The UnitOfWork needs to keep track of privately owned objects in order to |
| * detect and remove private owned objects which are de-referenced. |
| */ |
| public void addPrivateOwnedObject(DatabaseMapping mapping, Object privateOwnedObject) { |
| // only allow mapped, non-null objects to be added |
| if (privateOwnedObject != null && getDescriptor(privateOwnedObject) != null) { |
| Map<DatabaseMapping, Set> privateOwnedObjects = getPrivateOwnedObjects(); |
| Set objectsForMapping = privateOwnedObjects.get(mapping); |
| if (objectsForMapping == null) { |
| objectsForMapping = new IdentityHashSet(); |
| privateOwnedObjects.put(mapping, objectsForMapping); |
| } |
| objectsForMapping.add(privateOwnedObject); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the clone has been pessimistic locked in this unit of work. |
| */ |
| public boolean isPessimisticLocked(Object clone) { |
| return (this.pessimisticLockedObjects != null )&& this.pessimisticLockedObjects.containsKey(clone); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if there are any pessimistic locked objects in this unit of work, false otherwise. |
| */ |
| public boolean hasPessimisticLockedObjects() { |
| return (this.pessimisticLockedObjects != null) && (this.pessimisticLockedObjects.size() != 0); |
| } |
| |
| /** |
| * @return the preDeleteComplete |
| */ |
| public boolean isPreDeleteComplete() { |
| return preDeleteComplete; |
| } |
| |
| /** |
| * INTERNAL: |
| * True if either DataModifyQuery or ModifyAllQuery was executed. |
| * In absense of transaction the query execution starts one, therefore |
| * the flag may only be true in transaction, it's reset on commit or rollback. |
| */ |
| public void setWasNonObjectLevelModifyQueryExecuted(boolean wasNonObjectLevelModifyQueryExecuted) { |
| this.wasNonObjectLevelModifyQueryExecuted = wasNonObjectLevelModifyQueryExecuted; |
| } |
| |
| /** |
| * INTERNAL: |
| * True if either DataModifyQuery or ModifyAllQuery was executed. |
| */ |
| public boolean wasNonObjectLevelModifyQueryExecuted() { |
| return wasNonObjectLevelModifyQueryExecuted; |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether readObject should return the object read from the db |
| * in case there is no object in uow cache (as opposed to fetching the object from |
| * parent's cache). Note that wasNonObjectLevelModifyQueryExecuted()==true implies inTransaction()==true. |
| */ |
| public boolean shouldReadFromDB() { |
| return wasNonObjectLevelModifyQueryExecuted(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Release the read connection to the read connection pool. |
| */ |
| @Override |
| public void releaseReadConnection(Accessor connection) { |
| //bug 4668234 -- used to only release connections on server sessions but should always release |
| this.parent.releaseReadConnection(connection); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method will clear all registered objects from this UnitOfWork. |
| * If parameter value is 'true' then the cache(s) are cleared, too. |
| */ |
| public void clear(boolean shouldClearCache) { |
| this.cloneToOriginals = null; |
| this.cloneMapping = null; |
| this.newObjectsCloneToOriginal = null; |
| this.newObjectsOriginalToClone = null; |
| this.deletedObjects = null; |
| this.allClones = null; |
| this.objectsDeletedDuringCommit = null; |
| this.removedObjects = null; |
| this.unregisteredNewObjects = null; |
| this.unregisteredExistingObjects = null; |
| this.newAggregates = null; |
| this.unitOfWorkChangeSet = null; |
| this.pessimisticLockedObjects = null; |
| this.optimisticReadLockObjects = null; |
| this.batchQueries = null; |
| this.privateOwnedObjects = null; |
| this.newObjectsCloneToMergeOriginal = null; |
| if(shouldClearCache) { |
| clearIdentityMapCache(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Clear the identityMaps |
| */ |
| private void clearIdentityMapCache() { |
| getIdentityMapAccessor().initializeIdentityMaps(); |
| if (this.parent instanceof IsolatedClientSession) { |
| this.parent.getIdentityMapAccessor().initializeIdentityMaps(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Call this method if the uow will no longer be used for committing transactions: |
| * all the change sets will be dereferenced, and (optionally) the cache cleared. |
| * If the uow is not released, but rather kept around for ValueHolders, then identity maps shouldn't be cleared: |
| * the parameter value should be 'false'. The lifecycle set to Birth so that uow ValueHolder still could be used. |
| * Alternatively, if called from release method then everything should go and therefore parameter value should be 'true'. |
| * In this case lifecycle won't change - uow.release (optionally) calls this method when it (uow) is already dead. |
| * The reason for calling this method from release is to free maximum memory right away: |
| * the uow might still be referenced by objects using UOWValueHolders (though they shouldn't be around |
| * they still might). |
| * We defer a clear() call to release() if the uow lifecycle is 1,2 or 4 (*Pending). |
| */ |
| public void clearForClose(boolean shouldClearCache) { |
| clear(shouldClearCache); |
| if (isActive()) { |
| //Reset lifecycle |
| this.lifecycle = Birth; |
| this.isSynchronized = false; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether clearForClose method should be called by release method. |
| */ |
| public boolean shouldClearForCloseOnRelease() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Copy statements counts into UOW properties. |
| */ |
| private void copyStatementsCountIntoProperties(){ |
| Accessor accessor = null; |
| try { |
| accessor = getAccessor(); |
| } catch(DatabaseException exception){ |
| //ignore for bug 290703 |
| } |
| if(accessor!=null && accessor instanceof DatasourceAccessor){ |
| getProperties().put(DatasourceAccessor.READ_STATEMENTS_COUNT_PROPERTY, ((DatasourceAccessor) accessor).getReadStatementsCount()); |
| getProperties().put(DatasourceAccessor.WRITE_STATEMENTS_COUNT_PROPERTY, ((DatasourceAccessor) accessor).getWriteStatementsCount()); |
| getProperties().put(DatasourceAccessor.STOREDPROCEDURE_STATEMENTS_COUNT_PROPERTY, ((DatasourceAccessor) accessor).getStoredProcedureStatementsCount()); |
| } |
| } |
| |
| /** |
| * This method is used internally to create a map to hold the persistenceContexts. A weak map is returned if ReferenceMode is weak. |
| */ |
| protected Map createMap(){ |
| if (this.referenceMode != null && this.referenceMode != ReferenceMode.HARD) return new IdentityWeakHashMap(); |
| return new IdentityHashMap(); |
| } |
| /** |
| * This method is used internally to create a map to hold the persistenceContexts. A weak map is returned if ReferenceMode is weak. |
| * |
| */ |
| protected Map createMap(int size){ |
| if (this.referenceMode != null && this.referenceMode != ReferenceMode.HARD) return new IdentityWeakHashMap(size); |
| return new IdentityHashMap(size); |
| } |
| /** |
| * This method is used internally to clone a map that holds the persistenceContexts. A weak map is returned if ReferenceMode is weak. |
| * |
| */ |
| |
| protected Map cloneMap(Map map){ |
| // bug 270413. This method is needed to avoid the class cast exception when the reference mode is weak. |
| if (this.referenceMode != null && this.referenceMode != ReferenceMode.HARD) return (IdentityWeakHashMap)((IdentityWeakHashMap)map).clone(); |
| return (IdentityHashMap)((IdentityHashMap)map).clone(); |
| } |
| |
| |
| public ReferenceMode getReferenceMode() { |
| return referenceMode; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the list of object with changes. |
| * This is used in weak reference mode to avoid garbage collection of changed objects. |
| */ |
| public Set<Object> getChangeTrackedHardList() { |
| if (this.changeTrackedHardList == null) { |
| this.changeTrackedHardList = new IdentityHashSet(); |
| } |
| return this.changeTrackedHardList; |
| } |
| |
| /** |
| * Get an instance, whose state may be lazily fetched. |
| * If the requested instance does not exist in the database, null is returned, or the object will fail when accessed. |
| * The instance will be lazy when it does not exist in the cache, and supports fetch groups. |
| * @param id The primary key of the object, either as a List, singleton, IdClass or an instance of the object. |
| */ |
| @Override |
| public Object getReference(Class theClass, Object id) { |
| ClassDescriptor descriptor = getDescriptor(theClass); |
| if (descriptor == null || descriptor.isDescriptorTypeAggregate()) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[] { theClass })); |
| } |
| Object reference; |
| if (id == null) { //gf721 - check for null PK |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_pk")); |
| } |
| Object primaryKey; |
| if (id instanceof List) { |
| if (descriptor.getCachePolicy().getCacheKeyType() == CacheKeyType.ID_VALUE) { |
| if (((List)id).isEmpty()) { |
| primaryKey = null; |
| } else { |
| primaryKey = ((List)id).get(0); |
| } |
| } else { |
| primaryKey = new CacheId(((List)id).toArray()); |
| } |
| } else if (id instanceof CacheId) { |
| primaryKey = id; |
| } else { |
| if (descriptor.getCMPPolicy() != null) { |
| if (descriptor.getCMPPolicy().getPKClass() != null && !descriptor.getCMPPolicy().getPKClass().isAssignableFrom(id.getClass())) { |
| throw new IllegalArgumentException(ExceptionLocalization.buildMessage("invalid_pk_class", new Object[] { descriptor.getCMPPolicy().getPKClass(), id.getClass() })); |
| } |
| primaryKey = descriptor.getCMPPolicy().createPrimaryKeyFromId(id, this); |
| } else { |
| if (!id.getClass().equals(theClass)) { |
| primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(id, this); |
| } else { |
| primaryKey = id; |
| } |
| } |
| } |
| // If the class supports fetch groups then return a un-fetched instance. |
| if (descriptor.hasFetchGroupManager()) { |
| reference = getIdentityMapAccessor().getFromIdentityMap(primaryKey, theClass); |
| if (reference == null) { |
| if ((id instanceof List) || (id instanceof CacheId) || (descriptor.getCMPPolicy() == null)) { |
| AbstractRecord row = descriptor.getObjectBuilder().buildRowFromPrimaryKeyValues(primaryKey, this); |
| reference = descriptor.getObjectBuilder().buildNewInstance(); |
| descriptor.getObjectBuilder().buildPrimaryKeyAttributesIntoObject(reference, row, new ReadObjectQuery(), this); |
| } else { |
| reference = descriptor.getCMPPolicy().createBeanUsingKey(id, this); |
| } |
| descriptor.getFetchGroupManager().getIdEntityFetchGroup().setOnEntity(reference, this); |
| reference = registerExistingObject(reference); |
| } |
| } else { |
| ReadObjectQuery query = new ReadObjectQuery(descriptor.getJavaClass()); |
| query.setSelectionId(primaryKey); |
| query.conformResultsInUnitOfWork(); |
| query.setIsExecutionClone(true); |
| reference = executeQuery(query); |
| } |
| return reference; |
| } |
| |
| /** |
| * INTERNAL: |
| * 272022: Avoid releasing locks on the wrong server thread. |
| * If the current thread and the active thread on the mutex do not match - switch them |
| * Before we release acquired locks (do the same as we do for mergeClonesBeforeCompletion()) |
| * Check that the current thread is the active thread on all lock managers by |
| * checking the cached lockThread on the mergeManager. |
| * If we find that these 2 threads are different - then all threads in the acquired locks list are different. |
| * Switch the activeThread on the mutex to this current thread for each lock. |
| * @return true if threads were switched |
| */ |
| public boolean verifyMutexThreadIntegrityBeforeRelease() { |
| if (this.lastUsedMergeManager != null) { // mergeManager may be null in a com.ibm.tx.jta.RegisteredSyncs.coreDistributeAfter() afterCompletion() callback |
| Thread currentThread = Thread.currentThread(); |
| Thread lockThread = this.lastUsedMergeManager.getLockThread(); |
| if (currentThread != lockThread) { |
| if (ConcurrencyManager.getDeferredLockManager(lockThread) != null){ |
| // check for transitioned old deferred lock manager and switch to the new thread. |
| ConcurrencyManager.DEFERRED_LOCK_MANAGERS.put( |
| currentThread, ConcurrencyManager.DEFERRED_LOCK_MANAGERS.remove(lockThread)); |
| } |
| ArrayList<CacheKey> locks = this.getMergeManager().getAcquiredLocks(); |
| if (null != locks) { |
| Iterator<CacheKey> locksIterator = locks.iterator(); |
| log(SessionLog.FINER, AbstractSessionLog.CACHE, "active_thread_is_different_from_current_thread", |
| lockThread, getMergeManager(), currentThread); |
| while (locksIterator.hasNext()) { |
| ConcurrencyManager lockMutex = locksIterator.next(); |
| if (null != lockMutex) { |
| Thread activeThread = lockMutex.getActiveThread(); |
| // check for different acquire and release threads |
| if (currentThread != activeThread) { |
| // Switch activeThread to currentThread - we will release the lock later |
| lockMutex.setActiveThread(currentThread); |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| public DatabaseValueHolder createCloneQueryValueHolder(ValueHolderInterface attributeValue, Object clone, AbstractRecord row, ForeignReferenceMapping mapping) { |
| return new UnitOfWorkQueryValueHolder(attributeValue, clone, mapping, row, this); |
| } |
| |
| @Override |
| public DatabaseValueHolder createCloneTransformationValueHolder(ValueHolderInterface attributeValue, Object original, Object clone, AbstractTransformationMapping mapping) { |
| return new UnitOfWorkTransformerValueHolder(attributeValue, original, clone, mapping, this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return deleted objects that have reference to other deleted objects. |
| * This is need to delete cycles of objects in the correct order. |
| */ |
| public Map<Object, Set<Object>> getDeletionDependencies() { |
| if (this.deletionDependencies == null) { |
| this.deletionDependencies = new HashMap<>(); |
| } |
| return this.deletionDependencies; |
| } |
| |
| /** |
| * INTERNAL: |
| * Record deleted objects that have reference to other deleted objects. |
| * This is need to delete cycles of objects in the correct order. |
| */ |
| public void addDeletionDependency(Object target, Object source) { |
| if (this.deletionDependencies == null) { |
| this.deletionDependencies = new HashMap<>(); |
| } |
| Set<Object> dependencies = this.deletionDependencies.get(target); |
| if (dependencies == null) { |
| dependencies = new HashSet<>(); |
| this.deletionDependencies.put(target, dependencies); |
| } |
| dependencies.add(source); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return references to other deleted objects for this deleted object. |
| * This is need to delete cycles of objects in the correct order. |
| */ |
| public Set<Object> getDeletionDependencies(Object deletedObject) { |
| if (this.deletionDependencies == null) { |
| return null; |
| } |
| return this.deletionDependencies.get(deletedObject); |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the commit order. |
| */ |
| @Override |
| public CommitOrderType getCommitOrder() { |
| return commitOrder; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the commit order. |
| */ |
| @Override |
| public void setCommitOrder(CommitOrderType order) { |
| this.commitOrder = order; |
| } |
| |
| } |