blob: b1d1ab4714789aa15b5d18dc3c9c890f65fb8f55 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.sessions.remote;
import java.util.*;
import org.eclipse.persistence.config.ReferenceMode;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.history.AsOfClause;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.sessions.*;
import org.eclipse.persistence.internal.sessions.remote.*;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.internal.queries.*;
import org.eclipse.persistence.sessions.Login;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.internal.sequencing.Sequencing;
import org.eclipse.persistence.internal.sequencing.SequencingFactory;
import org.eclipse.persistence.logging.SessionLog;
/**
* <b>Purpose</b>: Provide transparent remote three-tiered replacation support.
* The remote session allows for complex three-tiered applications to be easily built.
* It gives the remote client the fully functionality of the TopLink api including,
* <ul>
* <li>Client side caching and object-identity maintainence.
* <li>Complex query support
* <li>Unit of work support
* <li>Indirection support through remote value holders.
* </ul>
*
* This session is a primary interface which resides on the client side. Users would interact
* with session just the same way as if it was a normal session.
*/
public class RemoteSession extends DistributedSession {
protected Sequencing sequencing;
protected boolean shouldEnableDistributedIndirectionGarbageCollection = false;
public RemoteSession() {
super(0);
}
/**
* PUBLIC:
* Creates a RemoteSession.
* @param remoteConnection remote session requires a remote connection. This must be accessed remotely from the client through RMI or CORBA.
*/
public RemoteSession(RemoteConnection remoteConnection) {
super(remoteConnection);
initializeSequencing();
}
/**
* ADVANCED:
* Allow the server-side value holders to be cleaned-up when the client-side value holder finalize.
*/
public void setShouldEnableDistributedIndirectionGarbageCollection(boolean shouldEnableDistributedIndirectionGarbageCollection) {
this.shouldEnableDistributedIndirectionGarbageCollection = shouldEnableDistributedIndirectionGarbageCollection;
}
/**
* ADVANCED:
* Allow the server-side value holders to be cleaned-up when the client-side value holder finalize.
*/
public boolean shouldEnableDistributedIndirectionGarbageCollection() {
return shouldEnableDistributedIndirectionGarbageCollection;
}
/**
* INTERNAL:
* Acquires a special historical session for reading objects as of a past time.
*/
@Override
public Session acquireHistoricalSession(AsOfClause clause) throws ValidationException {
throw ValidationException.cannotAcquireHistoricalSession();
}
/**
* PUBLIC:
* Return a unit of work for this session.
* The unit of work is an object level transaction that allows
* a group of changes to be applied as a unit.
*
* @see UnitOfWorkImpl
*/
@Override
public UnitOfWorkImpl acquireUnitOfWork() {
return acquireUnitOfWork(null);
}
/**
* PUBLIC:
* Return a unit of work for this session.
* The unit of work is an object level transaction that allows
* a group of changes to be applied as a unit.
*
* @see UnitOfWorkImpl
* @param referenceMode The reference type the UOW should use internally when
* referencing Working clones. Setting this to WEAK means the UOW will use
* weak references to reference clones and if the application no longer
* references the clone the clone may be garbage collected. If the clone
* has uncommitted changes then those changes will be lost.
*/
@Override
public UnitOfWorkImpl acquireUnitOfWork(ReferenceMode referenceMode) {
log(SessionLog.FINER, SessionLog.TRANSACTION, "acquire_unit_of_work");
setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() + 1);
return new RemoteUnitOfWork(this, referenceMode);
}
/**
* PUBLIC:
* Return a repeatable write unit of work for this session.
* A repeatable write unit of work allows multiple writeChanges (flushes).
*
* @see RepeatableWriteUnitOfWork
*/
@Override
public RepeatableWriteUnitOfWork acquireRepeatableWriteUnitOfWork(ReferenceMode referenceMode) {
return new RemoteUnitOfWork(this, referenceMode);
}
/**
* PUBLIC:
* Execute the database query.
*/
@Override
public Object executeQuery(DatabaseQuery query) {
return query.remoteExecute(this);
}
/**
* PUBLIC:
* Return the login.
* This must retrieve the login information from the server this first time called.
* This is useful to be able to do things differently depending on the database platform.
*/
@Override
public Login getDatasourceLogin() {
Login login = super.getDatasourceLogin();
if ((login == null) && (getRemoteConnection() != null)) {
startOperationProfile(SessionProfiler.RemoteMetadata, null, SessionProfiler.ALL);
login = getRemoteConnection().getLogin();
endOperationProfile(SessionProfiler.RemoteMetadata, null, SessionProfiler.ALL);
setDatasourceLogin(login);
}
return login;
}
/**
* INTERNAL:
* Return the corresponding objects from the remote session for the objects read from the server.
*/
@Override
public Object getObjectCorrespondingTo(Object serverSideDomainObject, Map<Object, ObjectDescriptor> objectDescriptors, Map<Object, Object> processedObjects, ObjectLevelReadQuery query) {
if (serverSideDomainObject == null) {
return null;
}
ClassDescriptor descriptor = getDescriptor(serverSideDomainObject);
// CR... fix to descriptor iterator exposed the bug that we were putting aggregate-collections in the cache.
if (descriptor.isAggregateCollectionDescriptor() || ((query != null) && (!query.shouldMaintainCache()))) {
if ((query != null) && (!query.hasPartialAttributeExpressions())) {
descriptor.getObjectBuilder().fixObjectReferences(serverSideDomainObject, objectDescriptors, processedObjects, query, this);
}
return serverSideDomainObject;
}
// Extract the object primary key and check if it exist on the remote session or not. If we find an object
// with this primary key then that's the corresponding object. Other wise its a new object for the remote
// session which needs to be registered in the remote sessions identity map and this is also a corresponding
// object.
ObjectDescriptor objectDescriptor = objectDescriptors.get(serverSideDomainObject);
if (objectDescriptor == null){
//the object must have been added concurrently before serialize generate a new ObjectDescriptor on this side
objectDescriptor = new ObjectDescriptor();
objectDescriptor.setKey(descriptor.getObjectBuilder().extractPrimaryKeyFromObject(serverSideDomainObject, this));
objectDescriptor.setObject(serverSideDomainObject);
OptimisticLockingPolicy policy = descriptor.getOptimisticLockingPolicy();
if (policy == null){
objectDescriptor.setWriteLockValue(null);
}else{
objectDescriptor.setWriteLockValue(policy.getBaseValue());
}
objectDescriptors.put(serverSideDomainObject, objectDescriptor);
}
Object primaryKey = objectDescriptor.getKey();
Object clientSideDomainObject = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, serverSideDomainObject.getClass(), descriptor);
// If object is already processed the return back, this check must be done after the cliet-side object is found.
if (processedObjects.containsKey(serverSideDomainObject)) {
if (clientSideDomainObject == null) {
return serverSideDomainObject;
} else {
return clientSideDomainObject;
}
}
processedObjects.put(serverSideDomainObject, serverSideDomainObject);
if (clientSideDomainObject == null) {
getIdentityMapAccessorInstance().putInIdentityMap(serverSideDomainObject, primaryKey, objectDescriptor.getWriteLockValue(), objectDescriptor.getReadTime(), descriptor);
descriptor.getObjectBuilder().fixObjectReferences(serverSideDomainObject, objectDescriptors, processedObjects, query, this);
clientSideDomainObject = serverSideDomainObject;
} else {
// if the query is null, that means we refreshed a newly-created client object at some point
// and we should refresh the identity map and cascade private parts
if ((query == null) || (query.shouldRefreshRemoteIdentityMapResult()) || getDescriptor(clientSideDomainObject).shouldAlwaysRefreshCacheOnRemote()) {
MergeManager mergeManager = new MergeManager(this);
mergeManager.refreshRemoteObject();
mergeManager.setObjectDescriptors(objectDescriptors);
if (query == null) {
mergeManager.cascadePrivateParts();
} else {
mergeManager.setCascadePolicy(query.getCascadePolicy());
}
clientSideDomainObject = mergeManager.mergeChanges(serverSideDomainObject, null, this);
}
}
return clientSideDomainObject;
}
/**
* INTERNAL:
* Return the corresponding objects from the remote session for the objects read from the server.
*/
@Override
public Object getObjectsCorrespondingToAll(Object serverSideDomainObjects, Map<Object, ObjectDescriptor> objectDescriptors, Map<Object, Object> processedObjects, ObjectLevelReadQuery query, ContainerPolicy containerPolicy) {
Object clientSideDomainObjects = containerPolicy.containerInstance(containerPolicy.sizeFor(serverSideDomainObjects));
for (Object iter = containerPolicy.iteratorFor(serverSideDomainObjects);
containerPolicy.hasNext(iter);) {
Object serverSideDomainObject = containerPolicy.next(iter, this);
containerPolicy.addInto(getObjectCorrespondingTo(serverSideDomainObject, objectDescriptors, processedObjects, query), clientSideDomainObjects, this);
}
return clientSideDomainObjects;
}
/**
* INTERNAL:
* This will instantiate value holder on the server.
*/
@Override
public Object instantiateRemoteValueHolderOnServer(RemoteValueHolder remoteValueHolder) {
startOperationProfile(SessionProfiler.RemoteLazy, null, SessionProfiler.ALL);
Transporter transporter = getRemoteConnection().instantiateRemoteValueHolderOnServer(remoteValueHolder);
endOperationProfile(SessionProfiler.RemoteLazy, null, SessionProfiler.ALL);
return remoteValueHolder.getMapping().getObjectCorrespondingTo(transporter.getObject(), this, transporter.getObjectDescriptors(), new IdentityHashMap<>(), remoteValueHolder.getQuery());
}
/**
* INTERNAL:
* Return if this session is remote.
*/
@Override
public boolean isRemoteSession() {
return true;
}
/**
* INTERNAL:
* Return the Sequencing object used by the session.
* Sequences may be provided locally, or remotely.
*/
@Override
public Sequencing getSequencing() {
if (this.isMetadataRemote) {
return this.sequencing;
} else {
return super.getSequencing();
}
}
/**
* ADVANCED:
* Creates sequencing object for the session.
* Sequences may be provided locally, or remotely.
*/
@Override
public void initializeSequencing() {
if (this.isMetadataRemote) {
this.sequencing = SequencingFactory.createSequencing(this);
} else {
super.initializeSequencing();
}
}
}