/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * 07/15/2011-2.2.1 Guy Pelletier * - 349424: persists during an preCalculateUnitOfWorkChangeSet event are lost ******************************************************************************/ package org.eclipse.persistence.internal.sessions.remote; import java.util.*; import org.eclipse.persistence.config.ReferenceMode; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.descriptors.DescriptorQueryManager; import org.eclipse.persistence.exceptions.*; import org.eclipse.persistence.internal.sessions.*; import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.helper.*; import org.eclipse.persistence.platform.database.DatabasePlatform; import org.eclipse.persistence.queries.DatabaseQuery; import org.eclipse.persistence.queries.ObjectLevelReadQuery; import org.eclipse.persistence.sessions.SessionProfiler; import org.eclipse.persistence.sessions.remote.*; import org.eclipse.persistence.logging.SessionLog; /** * Counter part of the unit of work which exists on the client side. */ public class RemoteUnitOfWork extends RepeatableWriteUnitOfWork { protected List newObjectsCache; protected List unregisteredNewObjectsCache; protected boolean isOnClient; protected transient RemoteSessionController parentSessionController; protected boolean isFlush; public RemoteUnitOfWork() { } public RemoteUnitOfWork(RemoteUnitOfWork parent) { this(parent, null); } public RemoteUnitOfWork(DistributedSession parent) { this(parent, null); } public RemoteUnitOfWork(RemoteUnitOfWork parent, ReferenceMode referenceMode) { super(parent, referenceMode); this.isOnClient = true; this.discoverUnregisteredNewObjectsWithoutPersist = true; } public RemoteUnitOfWork(DistributedSession parent, ReferenceMode referenceMode) { super(parent, referenceMode); this.isOnClient = true; this.discoverUnregisteredNewObjectsWithoutPersist = true; } public boolean isFlush() { return isFlush; } public void setIsFlush(boolean isFlush) { this.isFlush = isFlush; } /** * 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() */ public void beginEarlyTransaction() throws DatabaseException { // Acquire the mutex so session knows it is in a transaction. getParent().getTransactionMutex().acquire(); startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); // This needs a special call for remote, to ensure subsequent queries isolate their data to a unit of work on the server. ((DistributedSession)getParent()).getRemoteConnection().beginEarlyTransaction(); endOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); setWasTransactionBegunPrematurely(true); } /** * The nested unit of work must also be remote. */ @Override public UnitOfWorkImpl acquireUnitOfWork() { return acquireUnitOfWork(null); } /** * The nested unit of work must also be remote. */ @Override public UnitOfWorkImpl acquireUnitOfWork(ReferenceMode referenceMode) { log(SessionLog.FINER, SessionLog.TRANSACTION, "acquire_unit_of_work"); setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() + 1); RemoteUnitOfWork ruow = new RemoteUnitOfWork(this, referenceMode); ruow.discoverAllUnregisteredNewObjectsInParent(); return ruow; } /** * This is done to maintain correspondence between local new objects and returned new objects from serialization. * Object correspondence is maintained by comparing primary keys but for new objects it is possible that primary * key value is null as it is still not inserted. The returned new objects from serialization will have primary * key value which will be inserted into corresponding local new objects. */ protected List collectNewObjects() { if ((this.newObjectsCloneToOriginal == null) || this.newObjectsCloneToOriginal.isEmpty()) { return null; } List newObjects = new ArrayList(this.newObjectsCloneToOriginal.size()); for (Object newObject : this.newObjectsCloneToOriginal.keySet()) { newObjects.add(newObject); } return newObjects; } /** * This is done to maintain correspondence between local unregistered new objects and returned unregistered new * objects from serialization. Object correspondence is maintained by comparing primary keys but for unregistered * new objects it is possible that primary key value is null as it is still not inserted. The returned unregistered * new objects from serialization will have primary key value which will be inserted into corresponding local new * objects. */ protected List collectUnregisteredNewObjects() { discoverAllUnregisteredNewObjects(); return new ArrayList(getUnregisteredNewObjects().values()); } /** * The remote unit of work returned after its commit on the server is merged with remote unit of work * on the remote side. */ protected void commitIntoRemoteUnitOfWork() { UnitOfWorkImpl parent = ((UnitOfWorkImpl)getParent()); // Must merge the transaction flag. parent.setWasTransactionBegunPrematurely(wasTransactionBegunPrematurely()); MergeManager manager = new MergeManager(this); manager.mergeWorkingCopyIntoRemote(); // Must clone the clone mapping because entries can be added to it during the merging, // and that can lead to concurrency problems. Iterator cloneIterator = new IdentityHashMap(getCloneMapping()).keySet().iterator(); Map clones = new IdentityHashMap(this.cloneMapping.size()); // Iterate over each clone and let the object build merge to clones into the originals. while (cloneIterator.hasNext()) { Object remoteClone = cloneIterator.next(); manager.mergeChanges(remoteClone, null, this); Object clone = manager.getTargetVersionOfSourceObject(remoteClone, parent.getDescriptor(remoteClone), parent); clones.put(remoteClone, clone); } // Reset the remote change set to be the local one, need to reset all clones to local copy, // and reset transient variables. parent.setUnitOfWorkChangeSet(this.unitOfWorkChangeSet); fixRemoteChangeSet(this.unitOfWorkChangeSet, clones, parent); ((RemoteUnitOfWork)parent).setCumulativeUOWChangeSet(this.cumulativeUOWChangeSet); fixRemoteChangeSet(this.cumulativeUOWChangeSet, clones, parent); // Set the deleted objects into the parent. if (this.objectsDeletedDuringCommit != null) { Map newDeletedObjects = new IdentityHashMap(); for (Object deletedObject : this.objectsDeletedDuringCommit.keySet()) { Object primaryKey = getId(deletedObject); Object clone = clones.get(deletedObject); if (clone == null) { clone = parent.getIdentityMapAccessor().getFromIdentityMap(primaryKey, deletedObject.getClass()); if (clone == null) { clone = deletedObject; } } newDeletedObjects.put(clone, primaryKey); parent.getIdentityMapAccessor().removeFromIdentityMap(primaryKey, clone.getClass()); } parent.setObjectsDeletedDuringCommit(newDeletedObjects); } } /** * Simulate a flush, current just begins a transaction and commits. */ @Override public void writeChanges() { if (!isOnClient()) { super.writeChanges(); return; } // Check for a nested flush and return early if we are in one if (this.isWithinFlush()) { log(SessionLog.WARNING, SessionLog.TRANSACTION, "nested_entity_manager_flush_not_executed_pre_query_changes_may_be_pending", getClass().getSimpleName()); return; } log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_flush"); // 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) { // The change set may already exist if using change tracking. if (this.unitOfWorkChangeSet == null) { this.unitOfWorkChangeSet = new UnitOfWorkChangeSet(this); } calculateChanges(getCloneMapping(), this.unitOfWorkChangeSet, true, true); hasChanges = hasModifications(); } if (!hasChanges) { log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_flush"); return; } if (!wasTransactionBegunPrematurely()) { beginEarlyTransaction(); } // New objects cache is created to maintain the correspondence when they are returned back as different copy setNewObjectsCache(collectNewObjects()); // Unregistered new objects cache is created to maintain the correspondence when they are returned back as different copy setUnregisteredNewObjectsCache(collectUnregisteredNewObjects()); // Commit on the server RemoteUnitOfWork remoteUnitOfWork; try { setIsFlush(true); startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); remoteUnitOfWork = ((DistributedSession)getParent()).getRemoteConnection().commitRootUnitOfWork(this); endOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); } finally { setIsFlush(false); } // Make the returned remote unit of work a nested unit of work and merge it with the local remote unit of work remoteUnitOfWork.setParent(this); remoteUnitOfWork.setProject(getProject()); remoteUnitOfWork.prepareForMergeIntoRemoteUnitOfWork(); remoteUnitOfWork.commitIntoRemoteUnitOfWork(); log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_flush"); resumeUnitOfWork(); log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work"); } /** * Starts committing the remote unit of work. * This must serialize the unit of work across to the server, * commit the unit of work on the server, * serialize it back and merge any server-side changes (such as sequence numbers) it into itself, * then merge into the parent remote session. */ @Override public void commitRootUnitOfWork() { if (!isOnClient()) { if (isSynchronized()) { // If we started the JTS transaction then we have to commit it as well. if (getParent().wasJTSTransactionInternallyStarted()) { commitInternallyStartedExternalTransaction(); } // Do not commit until the JTS wants to. return; } if (this.eventManager != null) { this.eventManager.preCommitUnitOfWork(); } super.commitRootUnitOfWork(); // On the server the normal commit is done. if (this.eventManager != null) { this.eventManager.postCommitUnitOfWork(); } return; } // 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) { // The change set may already exist if using change tracking. if (this.unitOfWorkChangeSet == null) { this.unitOfWorkChangeSet = new UnitOfWorkChangeSet(this); } calculateChanges(getCloneMapping(), this.unitOfWorkChangeSet, true, true); hasChanges = hasModifications(); } if (!hasChanges && (this.cumulativeUOWChangeSet == null) && (this.classesToBeInvalidated == null)) { // If no changes, avoid the remote commit, just return. // CR#... need to commit the transaction if begun early. if (wasTransactionBegunPrematurely()) { // 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; } } return; } // New objects cache is created to maintain the correspondence when they are returned back as different copy setNewObjectsCache(collectNewObjects()); // Unregistered new objects cache is created to maintain the correspondence when they are returned back as different copy setUnregisteredNewObjectsCache(collectUnregisteredNewObjects()); // Commit on the server RemoteUnitOfWork remoteUnitOfWork; try { startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); remoteUnitOfWork = ((DistributedSession)getParent()).getRemoteConnection().commitRootUnitOfWork(this); endOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); } catch (RuntimeException exception) { // Must ensure remote session transaction mutex is correct. if (wasTransactionBegunPrematurely()) { getParent().getTransactionMutex().release(); } // If an exception occurred, the unit of work will have rolledback the early transaction // so must record this incase the unit of work re-commits. setWasTransactionBegunPrematurely(false); throw exception; } // Must ensure remote session transaction mutex is correct. if (wasTransactionBegunPrematurely()) { setWasTransactionBegunPrematurely(false); setWasNonObjectLevelModifyQueryExecuted(false); getParent().getTransactionMutex().release(); } // Make the returned remote unit of work a nested unit of work and merge it with the local remote unit of work remoteUnitOfWork.setParent(this); remoteUnitOfWork.setProject(getProject()); remoteUnitOfWork.prepareForMergeIntoRemoteUnitOfWork(); remoteUnitOfWork.commitIntoRemoteUnitOfWork(); // Now commit this unit of work to the parent remote session commitRootUnitOfWorkOnClient(); } /** * INTERNAL: * Changes are calculated on the client, so avoid recalculating them on the server. */ @Override public UnitOfWorkChangeSet calculateChanges(Map registeredObjects, UnitOfWorkChangeSet changeSet, boolean assignSequences, boolean shouldCloneMap) { if (!this.isOnClient) { // The changes are calculated on the client, so don't do them again on the server. return changeSet; } return super.calculateChanges(registeredObjects, changeSet, assignSequences, shouldCloneMap); } /** * INTERNAL: * Resume is not required on the server. */ @Override public void resumeUnitOfWork() { if (!this.isOnClient) { // Avoid the resume on the server, only the client should resume. return; } super.resumeUnitOfWork(); } /** * Merges remote unit of work to parent remote session. */ protected void commitRootUnitOfWorkOnClient() { collectAndPrepareObjectsForNestedMerge(); //calculate the change set here as we have special behavior for remote // in that the new changesets must be updated within the UOWChangeSet as the // primary keys have already been assigned. This was modified for updating // new object change set behavior. UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)getUnitOfWorkChangeSet(); if (uowChangeSet == null) { //may be using the old commit process usesOldCommit() uowChangeSet = new UnitOfWorkChangeSet(this); setUnitOfWorkChangeSet(uowChangeSet); calculateChanges(getCloneMapping(), uowChangeSet, false, true); this.allClones = null; } for (Map newList : uowChangeSet.getNewObjectChangeSets().values()) { Iterator newChangeSets = new IdentityHashMap(newList).keySet().iterator(); while (newChangeSets.hasNext()) { uowChangeSet.putNewObjectInChangesList((ObjectChangeSet)newChangeSets.next(), this); } } //add the deleted objects if (this.objectsDeletedDuringCommit != null) { for (Object deletedObject : this.objectsDeletedDuringCommit.keySet()) { uowChangeSet.addDeletedObject(deletedObject, this); } } mergeChangesIntoParent(); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The named query can be defined on the remote session or the server-side session. * * @see #addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName) throws DatabaseException { return executeQuery(queryName, new Vector(1)); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * The query is executed on the server-side session. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Class domainClass) throws DatabaseException { return executeQuery(queryName, domainClass, new Vector(1)); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * The class is the descriptor in which the query was pre-defined. * * @see DescriptorQueryManager#addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Class domainClass, Vector argumentValues) throws DatabaseException { DistributedSession remoteSession = null; if (getParent().isRemoteSession()) { remoteSession = (DistributedSession)getParent(); } else {//must be remote unit of work RemoteUnitOfWork uow = (RemoteUnitOfWork)getParent(); while (uow.getParent().isRemoteUnitOfWork()) { uow = (RemoteUnitOfWork)uow.getParent(); } remoteSession = (DistributedSession)uow.getParent(); } startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); Transporter transporter = remoteSession.getRemoteConnection().remoteExecuteNamedQuery(queryName, domainClass, argumentValues); endOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); transporter.getQuery().setSession(this); return transporter.getQuery().extractRemoteResult(transporter); } /** * PUBLIC: * Execute the pre-defined query by name and return the result. * Queries can be pre-defined and named to allow for their reuse. * * @see #addQuery(String, DatabaseQuery) */ @Override public Object executeQuery(String queryName, Vector argumentValues) throws DatabaseException { if (containsQuery(queryName)) { return super.executeQuery(queryName, argumentValues); } return executeQuery(queryName, null, argumentValues); } /** * Return the table descriptor specified for the class. */ @Override public ClassDescriptor getDescriptor(Class domainClass) { return getParent().getDescriptor(domainClass); } /** * Return the table descriptor specified for the class. */ @Override public ClassDescriptor getDescriptorForAlias(String alias) { return getParent().getDescriptorForAlias(alias); } /** * Returns a new object cache */ public List getNewObjectsCache() { return newObjectsCache; } /** * INTERNAL: * Method returns the parent RemoteSessionController for this Remote UnitOfWork * Used to retrieve Valueholders that were used on the client */ public RemoteSessionController getParentSessionController() { return this.parentSessionController; } /** * INTERNAL: * Return the database platform currently connected to. * The platform is used for database specific behavior. */ @Override public DatabasePlatform getPlatform() { return getParent().getPlatform(); } /** * INTERNAL: * Return the database platform currently connected to. * The platform is used for database specific behavior. */ @Override public Platform getDatasourcePlatform() { return getParent().getDatasourcePlatform(); } /** * Returns an unregistered new object cache */ public List getUnregisteredNewObjectsCache() { return unregisteredNewObjectsCache; } /** * INTERNAL: * Return the results from exeucting the database query. * the arguments should be a database row with raw data values. */ @Override public Object internalExecuteQuery(DatabaseQuery query, AbstractRecord Record) throws DatabaseException, QueryException { if (isOnClient()) { //assert !getCommitManager().isActive(); // This will either throw an exception or do a logic only operation // (i.e. mark for later deletion if a deleteObjet query). boolean objectLevelRead = (query.isObjectLevelReadQuery() && !query.isReportQuery() && query.shouldMaintainCache()); if (objectLevelRead) { ObjectLevelReadQuery readQuery = (ObjectLevelReadQuery)query; if (isAfterWriteChangesButBeforeCommit()) { throw ValidationException.illegalOperationForUnitOfWorkLifecycle(getLifecycle(), "executeQuery(ObjectLevelReadQuery)"); } Object result = readQuery.checkEarlyReturn(this, Record); if (result != null) { if (result == InvalidObject.instance) { return null; } return result; } // Must use the uow connection in these cases. // can be certain that commit manager not active as on client. if (readQuery.isLockQuery(this) && !wasTransactionBegunPrematurely()) { beginEarlyTransaction(); } } else if (query.isObjectLevelModifyQuery()) { // Delete object queries must be processed locally. return query.executeInUnitOfWork(this, Record); } // Starting a transaction early when on a remote UnitOfWork starts // a transaction on the server side client session, and all queries // when they arrive they will go down the write connection. /* Fix to allow executing non-selecting SQL in a UnitOfWork. - RB */ if ((!getCommitManager().isActive()) && query.isModifyQuery()) { if (!wasTransactionBegunPrematurely()) { beginEarlyTransaction(); } } Object result = getParent().executeQuery(query, Record); if (objectLevelRead) { result = ((ObjectLevelReadQuery)query).registerResultInUnitOfWork(result, this, Record, false); } if (query.isModifyAllQuery()) { storeModifyAllQuery(query); } return result; } return query.executeInUnitOfWork(this, Record); } protected boolean isOnClient() { return isOnClient; } /** * Return if this session is a unit of work. */ @Override public boolean isRemoteUnitOfWork() { return true; } /** * The returned remote unit of work from the server is prepared to merge with local remote unit of work. */ protected void prepareForMergeIntoRemoteUnitOfWork() { if (this.newObjectsCache == null) { return; } int size = this.newObjectsCache.size(); if (size == 0) { return; } Map originalToClone = new IdentityHashMap(size); Map cloneToOriginal = new IdentityHashMap(size); // For new and unregistered objects the clone from the parent remote unit of work is picked and store as original // in the remote unit of work. This is done so that changes are merged into the clone of the parent. List remoteNewObjects = ((RemoteUnitOfWork)this.parent).getNewObjectsCache(); for (int index = 0; index < size; index++) { Object cloneFromParent = remoteNewObjects.get(index); Object cloneFromSelf = this.newObjectsCache.get(index); if (cloneFromSelf != null) { originalToClone.put(cloneFromParent, cloneFromSelf); cloneToOriginal.put(cloneFromSelf, cloneFromParent); } } List remoteUnregisteredObjects = ((RemoteUnitOfWork)this.parent).getUnregisteredNewObjectsCache(); size = remoteUnregisteredObjects.size(); for (int index = 0; index < size; index++) { Object cloneFromParent = ((RemoteUnitOfWork)getParent()).getUnregisteredNewObjects().get(remoteUnregisteredObjects.get(index)); Object cloneFromSelf = getUnregisteredNewObjects().get(this.unregisteredNewObjectsCache.get(index)); originalToClone.put(cloneFromParent, cloneFromSelf); cloneToOriginal.put(cloneFromSelf, cloneFromParent); } this.newObjectsOriginalToClone = originalToClone; this.newObjectsCloneToOriginal = cloneToOriginal; } /** * INTERNAL: * Re-initialize for the server-side session. * This is done when the uow is passed back to the server for committing. */ public void reinitializeForSession(AbstractSession session, RemoteSessionController parentSessionController) { // If a server, acquire a client to commit into as client store connection for commit. if (session.isServerSession()) { session = ((org.eclipse.persistence.sessions.server.ServerSession)session).acquireClientSession(); } setIsOnClient(false); setParentSessionController(parentSessionController); setParent(session); setProject(session.getProject()); setProfiler(session.getProfiler()); if (session.hasEventManager()) { setEventManager(session.getEventManager().clone(this)); } // setShouldLogMessages(session.shouldLogMessages()); setSessionLog(session.getSessionLog()); setLog(session.getLog()); // These are transient so must be reset. setCommitManager(new CommitManager(this)); setTransactionMutex(new ConcurrencyManager()); getCommitManager().setCommitOrder(session.getCommitManager().getCommitOrder()); if (session.hasExternalTransactionController()) { session.getExternalTransactionController().registerSynchronizationListener(this, session); } if (this.unitOfWorkChangeSet != null) { fixRemoteChangeSet(this.unitOfWorkChangeSet, null, this); } if (this.cumulativeUOWChangeSet != null) { fixRemoteChangeSet(this.cumulativeUOWChangeSet, null, this); } } /** * INTERNAL: * Fix the transient fields in the serialized change set. */ protected void fixRemoteChangeSet(UnitOfWorkChangeSet uowChangeSet, Map cloneMap, AbstractSession session) { if (uowChangeSet == null) { return; } uowChangeSet.setSession(session); for (Map.Entry<Class, Map<ObjectChangeSet, ObjectChangeSet>> entry : uowChangeSet.getObjectChanges().entrySet()) { ClassDescriptor descriptor = getDescriptor(entry.getKey()); for (ObjectChangeSet changeSet : entry.getValue().values()) { changeSet.setDescriptor(descriptor); changeSet.setClassType(entry.getKey()); } } for (Map.Entry<Class, Map<ObjectChangeSet, ObjectChangeSet>> entry : uowChangeSet.getNewObjectChangeSets().entrySet()) { ClassDescriptor descriptor = getDescriptor(entry.getKey()); for (ObjectChangeSet changeSet : entry.getValue().values()) { changeSet.setDescriptor(descriptor); changeSet.setClassType(entry.getKey()); } } if (cloneMap == null) { for (Map.Entry<Object, ObjectChangeSet> entry : uowChangeSet.getCloneToObjectChangeSet().entrySet()) { Object clone = entry.getKey(); ObjectChangeSet changeSet = entry.getValue(); changeSet.postSerialize(clone, uowChangeSet, session); } } else { // Also need to reset the remote objects with their local clones. int size = uowChangeSet.getCloneToObjectChangeSet().size(); Map<Object, ObjectChangeSet> newCloneToObjectChangeSet = new IdentityHashMap<Object, ObjectChangeSet>(size); Map<ObjectChangeSet, Object> newObjectChangeSetToUOWClone = new IdentityHashMap<ObjectChangeSet, Object>(size); for (Map.Entry<Object, ObjectChangeSet> entry : uowChangeSet.getCloneToObjectChangeSet().entrySet()) { Object clone = cloneMap.get(entry.getKey()); // Deleted objects no longer exist, so use remote copy instead. if (clone == null) { clone = entry.getKey(); } ObjectChangeSet changeSet = entry.getValue(); changeSet.postSerialize(clone, uowChangeSet, session); newCloneToObjectChangeSet.put(clone, changeSet); newObjectChangeSetToUOWClone.put(changeSet, clone); } uowChangeSet.setCloneToObjectChangeSet(newCloneToObjectChangeSet); uowChangeSet.setObjectChangeSetToUOWClone(newObjectChangeSetToUOWClone); } } protected void setIsOnClient(boolean isOnClient) { this.isOnClient = isOnClient; } /** * Set a new object cache */ protected void setNewObjectsCache(List newObjectsCache) { this.newObjectsCache = newObjectsCache; } /** * INTERNAL: * Sets the parent RemoteSessionController for this Remote UnitOfWork * Used to retrieve Valueholders that were used on the client */ public void setParentSessionController(RemoteSessionController parentSessionController) { this.parentSessionController = parentSessionController; } /** * Set unregistered new object cache */ protected void setUnregisteredNewObjectsCache(List unregisteredNewObjectsCache) { this.unregisteredNewObjectsCache = unregisteredNewObjectsCache; } /** * Avoid the toString printing the accessor and platform. */ @Override public String toString() { return Helper.getShortClassName(getClass()) + "()"; } /** * TESTING: * This is used by testing code to ensure that a deletion was successful. */ @Override public boolean verifyDelete(Object domainObject) { return getParent().verifyDelete(domainObject); } }