| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 07/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() |
| */ |
| @Override |
| 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<>(size); |
| Map<ObjectChangeSet, Object> newObjectChangeSetToUOWClone = new IdentityHashMap<>(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); |
| } |
| } |