| /* |
| * 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.internal.helper.*; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.DescriptorQueryManager; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.internal.sessions.remote.*; |
| import org.eclipse.persistence.queries.*; |
| import org.eclipse.persistence.sessions.Login; |
| import org.eclipse.persistence.sessions.SessionProfiler; |
| import org.eclipse.persistence.internal.queries.*; |
| import org.eclipse.persistence.internal.identitymaps.*; |
| import org.eclipse.persistence.internal.sessions.*; |
| |
| /** |
| * <b>Purpose</b>: Super class to all remote client session's. |
| */ |
| public abstract class DistributedSession extends DatabaseSessionImpl { |
| /** Connection to remote persistence service. */ |
| protected transient RemoteConnection remoteConnection; |
| /** Cache if default classes have been read from server. */ |
| protected boolean hasDefaultReadOnlyClasses; |
| /** Define if meta-data is initialized locally, or serialized from the server. */ |
| protected boolean isMetadataRemote = true; |
| |
| /** |
| * INTERNAL: |
| * Create a blank session, used for proxy session. |
| */ |
| protected DistributedSession(int nothing) { |
| } |
| |
| /** |
| * PUBLIC: |
| * Creates a DistributedSession. |
| * @param remoteConnection remote session requires a remote connection. This must be accessed remotely from the client through RMI or CORBA. |
| */ |
| protected DistributedSession(RemoteConnection remoteConnection) { |
| this.remoteConnection = remoteConnection; |
| this.remoteConnection.initialize(this); |
| this.project = new org.eclipse.persistence.sessions.Project(); |
| } |
| |
| /** |
| * 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 abstract UnitOfWorkImpl acquireUnitOfWork(); |
| |
| /** |
| * PUBLIC: |
| * Start a transaction on the server. |
| * A unit of work should normally be used instead of transactions for the remote session. |
| */ |
| @Override |
| public void beginTransaction() { |
| // Acquire the mutex so session knows it is in a transaction. |
| getTransactionMutex().acquire(); |
| startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); |
| getRemoteConnection().beginTransaction(); |
| endOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); |
| } |
| |
| /** |
| * PUBLIC: |
| * Commit a transaction on the server. |
| * A unit of work should normally be used instead of transactions for the remote session. |
| */ |
| @Override |
| public void commitTransaction() { |
| startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); |
| getRemoteConnection().commitTransaction(); |
| endOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); |
| getTransactionMutex().release(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return remote cursor stream. |
| */ |
| public RemoteCursoredStream cursorSelectObjects(CursoredStreamPolicy policy) { |
| return getRemoteConnection().cursorSelectObjects(policy, this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return remote scrollable cursor |
| */ |
| public RemoteScrollableCursor cursorSelectObjects(ScrollableCursorPolicy policy) { |
| return getRemoteConnection().cursorSelectObjects(policy, this); |
| } |
| |
| /** |
| * 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 { |
| startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); |
| Transporter transporter = 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); |
| } |
| |
| /** |
| * Execute the database query. |
| */ |
| @Override |
| public abstract Object executeQuery(DatabaseQuery query); |
| |
| /** |
| * INTERNAL: |
| * Execute the database query. |
| */ |
| @Override |
| public Object executeQuery(DatabaseQuery query, AbstractRecord row) { |
| query.setTranslationRow(row); |
| return executeQuery(query); |
| } |
| |
| /** |
| * INTERNAL: |
| * CR#2751 |
| * Returns the set of read-only classes for the receiver. These class come from the |
| * Remote connection |
| * @return A Vector containing the Java Classes that are currently read-only. |
| */ |
| @Override |
| public Vector getDefaultReadOnlyClasses() { |
| if (this.isMetadataRemote && !this.hasDefaultReadOnlyClasses) { |
| getProject().setDefaultReadOnlyClasses(getRemoteConnection().getDefaultReadOnlyClasses()); |
| this.hasDefaultReadOnlyClasses = true; |
| } |
| return super.getDefaultReadOnlyClasses(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the table descriptor specified for the class. |
| */ |
| @Override |
| public ClassDescriptor getDescriptor(Class domainClass) { |
| ClassDescriptor descriptor = getDescriptors().get(domainClass); |
| |
| // If the descriptor is null then this means that descriptor must now be read from the server. |
| if (descriptor == null) { |
| if (!this.isMetadataRemote) { |
| return super.getDescriptor(domainClass); |
| } |
| startOperationProfile(SessionProfiler.RemoteMetadata, null, SessionProfiler.ALL); |
| descriptor = getRemoteConnection().getDescriptor(domainClass); |
| endOperationProfile(SessionProfiler.RemoteMetadata, null, SessionProfiler.ALL); |
| if (descriptor == null) { |
| return super.getDescriptor(domainClass); |
| } |
| getDescriptors().put(domainClass, descriptor); |
| String alias = descriptor.getAlias(); |
| if (alias != null) { |
| getProject().addAlias(alias, descriptor); |
| } |
| descriptor.remoteInitialization(this); |
| } |
| |
| return descriptor; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the table descriptor specified for the class. |
| */ |
| @Override |
| public ClassDescriptor getDescriptorForAlias(String alias) { |
| ClassDescriptor descriptor = super.getDescriptorForAlias(alias); |
| |
| // If the descriptor is null then this means that descriptor must now be read from the server. |
| if (descriptor == null) { |
| if (!this.isMetadataRemote) { |
| return null; |
| } |
| startOperationProfile(SessionProfiler.RemoteMetadata, null, SessionProfiler.ALL); |
| descriptor = getRemoteConnection().getDescriptorForAlias(alias); |
| endOperationProfile(SessionProfiler.RemoteMetadata, null, SessionProfiler.ALL); |
| if (descriptor == null) { |
| return null; |
| } |
| getDescriptors().put(descriptor.getJavaClass(), descriptor); |
| getProject().addAlias(alias, descriptor); |
| descriptor.remoteInitialization(this); |
| } |
| |
| return descriptor; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the descriptor. |
| */ |
| public ClassDescriptor getDescriptorCorrespondingTo(ClassDescriptor descriptor) { |
| return getDescriptors().get(descriptor.getJavaClass()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the corresponding objects from the remote session for the objects read from the server. |
| */ |
| public abstract Object getObjectCorrespondingTo(Object serverSideDomainObject, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query); |
| |
| /** |
| * INTERNAL: |
| * Return the corresponding objects from the remote session for the objects read from the server. |
| */ |
| public abstract Object getObjectsCorrespondingToAll(Object serverSideDomainObjects, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, ContainerPolicy containerPolicy); |
| |
| /** |
| * INTERNAL: |
| * Return the remote connection. |
| */ |
| public RemoteConnection getRemoteConnection() { |
| return remoteConnection; |
| } |
| |
| /** |
| * INTERNAL: |
| * Checks if the descriptor exists or not. |
| */ |
| public boolean hasCorrespondingDescriptor(ClassDescriptor descriptor) { |
| return getDescriptors().containsKey(descriptor.getJavaClass()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set up the IdentityMapManager. Overrides the default IdentityMapManager |
| */ |
| @Override |
| public void initializeIdentityMapAccessor() { |
| this.identityMapAccessor = new DistributedSessionIdentityMapAccessor(this, new IdentityMapManager(this)); |
| } |
| |
| /** |
| * INTERNAL: |
| * This will instantiate value holder on the server. |
| */ |
| public abstract Object instantiateRemoteValueHolderOnServer(RemoteValueHolder remoteValueHolder); |
| |
| /** |
| * PUBLIC: |
| * Return if this session is connected to the server. |
| */ |
| @Override |
| public boolean isConnected() { |
| if (getRemoteConnection() == null) { |
| return false; |
| } |
| |
| return getRemoteConnection().isConnected(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this session is a distributed session. |
| */ |
| @Override |
| public boolean isDistributedSession() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this session is a remote session. |
| */ |
| @Override |
| public boolean isRemoteSession() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * You cannot add descriptors to a remote session. This is a internal method used by TopLink |
| */ |
| public void privilegedAddDescriptor(ClassDescriptor descriptor) { |
| getDescriptors().put(descriptor.getJavaClass(), descriptor); |
| } |
| |
| /** |
| * PUBLIC: |
| * Rollback a transaction on the server. |
| * A unit of work should normally be used instead of transactions for the remote session. |
| */ |
| @Override |
| public void rollbackTransaction() { |
| startOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); |
| getRemoteConnection().rollbackTransaction(); |
| endOperationProfile(SessionProfiler.Remote, null, SessionProfiler.ALL); |
| getTransactionMutex().release(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the remote connection. |
| */ |
| public void setRemoteConnection(RemoteConnection remoteConnection) { |
| this.remoteConnection = remoteConnection; |
| } |
| |
| /** |
| * PUBLIC: |
| * Avoid printing the accessor and platform. |
| */ |
| @Override |
| public String toString() { |
| return Helper.getShortClassName(getClass()) + "()"; |
| } |
| |
| /** |
| * PUBLIC: |
| * Logout the session, close the remote connection and release the hold resources |
| */ |
| @Override |
| public void logout() { |
| //CR3854: logging out of DistributedSession is not releasing remote resources. |
| //The remote connection remove() call should release the resource, like the stateful |
| //session been, which the connection holds on. |
| this.remoteConnection.release(); |
| } |
| |
| /** |
| * ADVANCED: |
| * Return if the descriptors and meta-data should be serialized from the server, |
| * or if they will be provided locally. |
| */ |
| public boolean isMetadataRemote() { |
| return isMetadataRemote; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set if the descriptors and meta-data should be serialized from the server, |
| * or if they will be provided locally. |
| */ |
| public void setIsMetadataRemote(boolean isMetadataRemote) { |
| this.isMetadataRemote = isMetadataRemote; |
| } |
| |
| /** |
| * INTERNAL: |
| * Connect not required. |
| */ |
| @Override |
| public void connect() throws DatabaseException { |
| this.remoteConnection.initialize(this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Disconnect not required. |
| */ |
| @Override |
| public void disconnect() throws DatabaseException { |
| getSequencingHome().onDisconnect(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Connect to the database using the predefined login. |
| * During connection, attempt to auto detect the required database platform. |
| * This method can be used in systems where for ease of use developers have |
| * EclipseLink autodetect the platform. |
| * To be safe, however, the platform should be configured directly. |
| * The login must have been assigned when or after creating the session. |
| */ |
| @Override |
| public void loginAndDetectDatasource() throws DatabaseException { |
| preConnectDatasource(); |
| connect(); |
| Login login = this.remoteConnection.getLogin(); |
| setLogin(login); |
| postConnectDatasource(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Connect to the database using the predefined login. |
| * Obtain the login from the server, as it may have configuration initialized from the database meta-data. |
| */ |
| @Override |
| public void login() throws DatabaseException { |
| preConnectDatasource(); |
| connect(); |
| Login login = this.remoteConnection.getLogin(); |
| setLogin(login); |
| postConnectDatasource(); |
| } |
| } |