blob: aec491801d13ab2490ce94762dad0fb842869757 [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
// 05/28/2008-1.0M8 Andrei Ilitchev
// - 224964: Provide support for Proxy Authentication through JPA.
// Added a new constructor that takes Properties.
// 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
package org.eclipse.persistence.sessions.server;
import java.util.*;
import java.io.*;
import org.eclipse.persistence.platform.server.ServerPlatform;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.SchemaPerMultitenantPolicy;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.sequencing.Sequencing;
import org.eclipse.persistence.internal.sequencing.SequencingFactory;
import org.eclipse.persistence.sessions.coordination.CommandManager;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.internal.sessions.*;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.internal.sessions.AbstractSession;
/**
* <b>Purpose</b>: Acts as a client to the server session.
* <p>
* <b>Description</b>: This session is brokered by the server session for use in three-tiered applications.
* It is used to store the context of the connection, i.e. the login to be used for this client.
* This allows each client connected to the server to contain its own user login.
* <p>
* <b>Responsibilities</b>:
* <ul>
* <li> Allow units of work to be acquired and pass them the client login's exclusive connection.
* <li> Forward all requests and queries to its parent server session.
* </ul>
* <p>
* This class is an implementation of {@link org.eclipse.persistence.sessions.Session}.
* Please refer to that class for a full API. The public interface should be used.
* @see Server
* @see org.eclipse.persistence.sessions.Session
* @see org.eclipse.persistence.sessions.UnitOfWork
*/
public class ClientSession extends AbstractSession {
protected ServerSession parent;
protected ConnectionPolicy connectionPolicy;
protected Map<String, Accessor> writeConnections;
protected boolean isActive;
protected Sequencing sequencing;
/**
* INTERNAL:
* Create and return a new client session.
*/
public ClientSession(ServerSession parent, ConnectionPolicy connectionPolicy) {
this(parent, connectionPolicy, null);
}
public ClientSession(ServerSession parent, ConnectionPolicy connectionPolicy, Map properties) {
super();
// If we have table per tenant descriptors let's clone the project so
// that we can have a separate jpql parse cache for each tenant.
if (parent.hasTablePerTenantDescriptors() || parent.getProject().getMultitenantPolicy() != null) {
this.project = parent.getProject().clone();
this.project.setJPQLParseCacheMaxSize(parent.getProject().getJPQLParseCache().getMaxSize());
} else {
this.project = parent.getProject();
}
if (connectionPolicy.isUserDefinedConnection()) {
// PERF: project only requires clone if login is different
this.setProject(getProject().clone());
this.setLogin(connectionPolicy.getLogin());
}
if (this.project.getMultitenantPolicy() != null && this.project.getMultitenantPolicy().isSchemaPerMultitenantPolicy()) {
SchemaPerMultitenantPolicy mp = (SchemaPerMultitenantPolicy) this.project.getMultitenantPolicy();
if (mp.shouldUseSharedEMF()) {
//force different login instance
this.setLogin(getLogin().clone());
}
}
this.isLoggingOff = parent.isLoggingOff();
this.isActive = true;
this.externalTransactionController = parent.getExternalTransactionController();
this.parent = parent;
this.connectionPolicy = connectionPolicy;
this.name = parent.getName();
this.profiler = parent.getProfiler();
this.serializer = parent.getSerializer();
this.isInProfile = parent.isInProfile();
this.commitManager = parent.getCommitManager();
this.partitioningPolicy = parent.getPartitioningPolicy();
this.sessionLog = parent.getSessionLog();
if (parent.hasEventManager()) {
this.eventManager = parent.getEventManager().clone(this);
}
this.exceptionHandler = parent.getExceptionHandler();
this.pessimisticLockTimeoutDefault = parent.getPessimisticLockTimeoutDefault();
this.pessimisticLockTimeoutUnitDefault = parent.getPessimisticLockTimeoutUnitDefault();
this.queryTimeoutDefault = parent.getQueryTimeoutDefault();
this.queryTimeoutUnitDefault = parent.getQueryTimeoutUnitDefault();
this.isConcurrent = parent.isConcurrent();
this.shouldOptimizeResultSetAccess = parent.shouldOptimizeResultSetAccess();
this.properties = properties;
this.multitenantContextProperties = parent.getMultitenantContextProperties();
if (this.eventManager != null) {
this.eventManager.postAcquireClientSession();
}
// Copy down the table per tenant queries from the parent. These queries
// must be cloned per client session.
if (parent.hasTablePerTenantQueries()) {
for (DatabaseQuery query : parent.getTablePerTenantQueries()) {
addTablePerTenantQuery((DatabaseQuery) query.clone());
}
}
// If we have table per tenant descriptors, they will need to be
// cloned as we will be changing the descriptors per tenant.
if (parent.hasTablePerTenantDescriptors()) {
this.descriptors = new HashMap<>();
this.descriptors.putAll(parent.getDescriptors());
for (ClassDescriptor descriptor : parent.getTablePerTenantDescriptors()) {
ClassDescriptor clonedDescriptor = (ClassDescriptor) descriptor.clone();
addTablePerTenantDescriptor(clonedDescriptor);
this.descriptors.put(clonedDescriptor.getJavaClass(), clonedDescriptor);
}
if (hasProperties()) {
for (Object propertyName : properties.keySet()) {
updateTablePerTenantDescriptors((String) propertyName, properties.get(propertyName));
}
}
} else {
this.descriptors = parent.getDescriptors();
}
incrementProfile(SessionProfiler.ClientSessionCreated);
}
protected ClientSession(org.eclipse.persistence.sessions.Project project) {
super(project);
}
/**
* INTERNAL:
* Called in the end of beforeCompletion of external transaction synchronization listener.
* Close the managed sql connection corresponding to the external transaction
* and releases accessor.
*/
@Override
public void releaseJTSConnection() {
if (hasWriteConnection()) {
for (Accessor accessor : getWriteConnections().values()) {
accessor.closeJTSConnection();
}
releaseWriteConnection();
}
}
/**
* INTERNAL:
* This is internal to the unit of work and should not be called otherwise.
*/
@Override
public void basicCommitTransaction() {
//Only release connection when transaction succeeds.
//If not, connection will be released in rollback.
super.basicCommitTransaction();
// if synchronized then the connection will be released in external transaction callback.
if (hasExternalTransactionController()) {
if(!isSynchronized()) {
releaseJTSConnection();
}
} else {
releaseWriteConnection();
}
}
/**
* INTERNAL:
* This is internal to the unit of work and should not be called otherwise.
*/
@Override
public void basicRollbackTransaction() {
try {
//BUG 2660471: Make sure there is an accessor (moved here from Session)
//BUG 2846785: EXCEPTION THROWN IN PREBEGINTRANSACTION EVENT CAUSES NPE
if (hasWriteConnection()) {
super.basicRollbackTransaction();
}
} finally {
// if synchronized then the connection will be released in external transaction callback.
if (hasExternalTransactionController()) {
if(!isSynchronized()) {
releaseJTSConnection();
}
} else {
releaseWriteConnection();
}
}
}
/**
* INTERNAL:
* Connect the session only (this must be the write connection as the read is shared).
*/
public void connect(Accessor accessor) throws DatabaseException {
accessor.connect(getDatasourceLogin(), this);
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Return true if the pre-defined query is defined on the session.
*/
@Override
public boolean containsQuery(String queryName) {
boolean containsQuery = getQueries().containsKey(queryName);
if (containsQuery == false) {
containsQuery = this.parent.containsQuery(queryName);
}
return containsQuery;
}
/**
* INTERNAL:
* Disconnect the accessor only (this must be the write connection as the read is shared).
*/
public void disconnect(Accessor accessor) throws DatabaseException {
accessor.disconnect(this);
}
/**
* INTERNAL:
* Execute the call on the correct connection accessor.
* Outside of a transaction the server session's read connection pool is used.
* In side a transaction, or for exclusive sessions the write connection is used.
* For partitioning there may be multiple write connections.
*/
@Override
public Object executeCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException {
if ((!isInTransaction() || (query.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)query).isReadOnly())) && !isExclusiveIsolatedClientSession() ) {
return this.parent.executeCall(call, translationRow, query);
}
boolean shouldReleaseConnection = false;
if (query.getAccessors() == null) {
// First check for a partitioning policy.
// An exclusive session will always use a single connection once allocated.
if (!hasWriteConnection() || !isExclusiveIsolatedClientSession()) {
Collection<Accessor> accessors = getAccessors(call, translationRow, query);
if (accessors != null && !accessors.isEmpty()) {
query.setAccessors(accessors);
// the session has been already released and this query is likely instantiates a ValueHolder -
// release exclusive connection immediately after the query is executed, otherwise it may never be released.
shouldReleaseConnection = !this.isActive;
}
}
}
if (query.getAccessors() == null) {
// If the connection has not yet been acquired then do it here.
if (!hasWriteConnection()) {
this.parent.acquireClientConnection(this);
// The session has been already released and this query is likely instantiates a ValueHolder -
// release exclusive connection immediately after the query is executed, otherwise it may never be released.
shouldReleaseConnection = !this.isActive;
query.setAccessors(getAccessors());
} else {
// Must use the default write connection if there are multiple connections.
if (!isExclusiveIsolatedClientSession() && this.connectionPolicy.isPooled()) {
Accessor defaultWriteConnection = this.writeConnections.get(this.connectionPolicy.getPoolName());
if (defaultWriteConnection == null) {
// No default connection yet, must acquire it.
this.parent.acquireClientConnection(this);
}
if (this.writeConnections.size() == 1) {
// Connection is the default, just use it.
query.setAccessors(getAccessors());
} else {
List<Accessor> accessors = new ArrayList(1);
accessors.add(defaultWriteConnection);
query.setAccessors(accessors);
}
} else {
query.setAccessors(getAccessors());
}
}
}
Object result = null;
RuntimeException exception = null;
try {
result = basicExecuteCall(call, translationRow, query);
} catch (RuntimeException caughtException) {
exception = caughtException;
} finally {
if (call.isFinished() || exception != null) {
query.setAccessors(null);
// Note that connection could be release only if it has been acquired by the same query,
// that allows to execute other queries from postAcquireConnection / preReleaseConnection events
// without wiping out connection set by the original query or causing stack overflow, see
// bug 299048 - Triggering indirection on closed ExclusiveIsolatedSession may cause exception
if (shouldReleaseConnection && hasWriteConnection()) {
try {
this.parent.releaseClientSession(this);
} catch (RuntimeException releaseException) {
if (exception == null) {
throw releaseException;
}
//else ignore
}
}
} else {
if (query.isObjectLevelReadQuery()) {
((DatabaseCall)call).setHasAllocatedConnection(shouldReleaseConnection);
}
}
if (exception != null) {
throw exception;
}
}
return result;
}
/**
* INTERNAL:
* Release (if required) connection after call.
*/
@Override
public void releaseConnectionAfterCall(DatabaseQuery query) {
if ((!isInTransaction() || (query.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)query).isReadOnly())) && !isExclusiveIsolatedClientSession() ) {
this.parent.releaseConnectionAfterCall(query);
} else {
if (hasWriteConnection()) {
query.setAccessors(null);
this.parent.releaseClientSession(this);
}
}
}
/**
* INTERNAL:
* Return the write connections if in a transaction.
* These may be empty/null until the first query has been executed inside the transaction.
* This should only be called within a transaction.
* If outside of a transaction it will return null (unless using an exclusive connection).
*/
@Override
public Collection<Accessor> getAccessors() {
if (isInTransaction()) {
if (this.writeConnections == null) {
return null;
}
return this.writeConnections.values();
} else {
return this.accessors;
}
}
/**
* INTERNAL:
* This should normally not be used, getAccessors() should be used to support partitioning.
* To maintain backward compatibility, and to support certain cases that required a default accessor,
* if inside a transaction, then a default connection will be allocated.
* This is required for sequencing, and JPA connection unwrapping, and ordt mappings.
* Outside of a transaction, to maintain backward compatibility the server session's accessor will be returned.
*/
@Override
public Accessor getAccessor() {
Collection<Accessor> accessors = getAccessors();
if ((accessors == null) || accessors.isEmpty()) {
if (isInTransaction()) {
this.parent.acquireClientConnection(this);
accessors = getAccessors();
} else {
return this.parent.getAccessor();
}
}
if (accessors instanceof List) {
return ((List<Accessor>)accessors).get(0);
}
return accessors.iterator().next();
}
/**
* ADVANCED:
* This method will return the connection policy that was used during the
* acquisition of this client session. The properties within the ConnectionPolicy
* may be used when acquiring an exclusive connection for an IsolatedSession.
*/
public ConnectionPolicy getConnectionPolicy() {
return connectionPolicy;
}
/**
* ADVANCED:
* Return all registered descriptors.
*/
@Override
public Map<Class, ClassDescriptor> getDescriptors() {
// descriptors from the project may have been modified (for table per
// tenants so make sure to return the updated ones)
if (hasTablePerTenantDescriptors()) {
return this.descriptors;
} else {
return super.getDescriptors();
}
}
/**
* 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) {
// Note could return self as ClientSession shares the same identity map
// as parent. This reveals a deep problem, as queries will be cached in
// the Server identity map but executed here using the write connection.
return this.parent.getParentIdentityMapSession(descriptor, canReturnSelf, terminalOnly);
}
/**
* Search for and return the user defined property from this client session, if it not found then search for the property
* from parent.
*/
@Override
public Object getProperty(String name){
Object propertyValue = super.getProperty(name);
if (propertyValue == null) {
propertyValue = this.parent.getProperty(name);
}
return propertyValue;
}
/**
* 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) {
// For CR#4334 if in transaction stay on client session.
// That way client's write accessor will be used for all queries.
// This is to preserve transaction isolation levels.
// For bug 3602222 if a query is executed directly on a client session when
// in transaction, then dirty data could be put in the shared cache for the
// client session uses the identity map of its parent.
// However beginTransaction() is not public API on ClientSession.
// if fix this could add: && (query.getSession() != this).
if (isInTransaction()) {
return this;
}
return this.parent.getExecutionSession(query);
}
/**
* INTERNAL:
* Return the parent.
* This is a server session.
*/
@Override
public ServerSession getParent() {
return parent;
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* 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;
}
/**
* INTERNAL:
*/
@Override
public DatabaseQuery getQuery(String name, Vector args) {// CR3716; Predrag;
DatabaseQuery query = super.getQuery(name, args);
if (query == null) {
query = this.parent.getQuery(name, args);
}
return query;
}
/**
* INTERNAL:
* was ADVANCED:
* Creates sequencing object for the session.
* Typically there is no need for the user to call this method -
* it is called from the constructor.
*/
public void initializeSequencing() {
this.sequencing = SequencingFactory.createSequencing(this);
}
/**
* INTERNAL:
* Return the Sequencing object used by the session.
* Lazy init sequencing to defer from client session creation to improve creation performance.
*/
@Override
public Sequencing getSequencing() {
// PERF: lazy init defer from constructor, only created when needed.
if (this.sequencing == null) {
initializeSequencing();
}
return this.sequencing;
}
/**
* INTERNAL:
* Marked internal as this is not customer API but helper methods for
* accessing the server platform from within 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 "ClientSession";
}
/**
* INTERNAL:
* Return the map of write connections.
* Multiple connections can be used for data partitioning and replication.
* The connections are keyed by connection pool name.
*/
public Map<String, Accessor> getWriteConnections() {
if (this.writeConnections == null) {
this.writeConnections = new HashMap(4);
}
return this.writeConnections;
}
/**
* INTERNAL:
* Return the connection to be used for database modification.
*/
public Accessor getWriteConnection() {
if ((this.writeConnections == null) || this.writeConnections.isEmpty()) {
return null;
}
return this.writeConnections.values().iterator().next();
}
/**
* INTERNAL:
* Return if this session has been connected.
*/
public boolean hasWriteConnection() {
if (this.writeConnections == null) {
return false;
}
return !this.writeConnections.isEmpty();
}
/**
* INTERNAL:
* Set up the IdentityMapManager. This method allows subclasses of Session to override
* the default IdentityMapManager functionality.
*/
@Override
public void initializeIdentityMapAccessor() {
this.identityMapAccessor = new ClientSessionIdentityMapAccessor(this);
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Return if the client session is active (has not been released).
*/
public boolean isActive() {
return isActive;
}
/**
* INTERNAL:
* Return if this session is a client session.
*/
@Override
public boolean isClientSession() {
return true;
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Return if this session has been connected to the database.
*/
@Override
public boolean isConnected() {
return this.parent.isConnected();
}
/**
* INTERNAL:
* Was PUBLIC: customer will be redirected to {@link org.eclipse.persistence.sessions.Session}.
* Release the client session.
* This releases the client session back to it server.
* Normally this will logout of the client session's connection,
* and allow the client session to garbage collect.
*/
@Override
public void release() throws DatabaseException {
// Clear referencing classes. If this is not done the object is not garbage collected.
for (Map.Entry<Class, ClassDescriptor> entry : getDescriptors().entrySet()) {
entry.getValue().clearReferencingClasses();
}
if (!this.isActive) {
return;
}
if (this.eventManager != null) {
this.eventManager.preReleaseClientSession();
}
//removed is Lazy check as we should always release the connection once
//the client session has been released. It is also required for the
//behavior of a subclass ExclusiveIsolatedClientSession
if (hasWriteConnection()) {
this.parent.releaseClientSession(this);
}
// we are not inactive until the connection is released
this.isActive = false;
log(SessionLog.FINER, SessionLog.CONNECTION, "client_released");
if (this.eventManager != null) {
this.eventManager.postReleaseClientSession();
}
incrementProfile(SessionProfiler.ClientSessionReleased);
}
/**
* 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) {
// If not in a transaction and has a write connection, must release it if invalid.
getParent().releaseInvalidClientSession(this);
return super.retryQuery(query, row, databaseException, retryCount, executionSession);
}
/**
* INTERNAL:
* This is internal to the unit of work and should not be called otherwise.
*/
protected void releaseWriteConnection() {
if (this.connectionPolicy.isLazy() && hasWriteConnection()) {
this.parent.releaseClientSession(this);
}
}
/**
* INTERNAL:
* Set the connection policy.
*/
public void setConnectionPolicy(ConnectionPolicy connectionPolicy) {
this.connectionPolicy = connectionPolicy;
}
/**
* INTERNAL:
* Set if the client session is active (has not been released).
*/
protected void setIsActive(boolean isActive) {
this.isActive = isActive;
}
/**
* INTERNAL:
* Set the parent.
* This is a server session.
*/
protected void setParent(ServerSession parent) {
this.parent = parent;
}
/**
* INTERNAL:
* Add the connection to the client session.
* Multiple connections are supported to allow data partitioning and replication.
* The accessor is returned, as if detected to be dead it may be replaced.
*/
public Accessor addWriteConnection(String poolName, Accessor writeConnection) {
getWriteConnections().put(poolName, writeConnection);
writeConnection.createCustomizer(this);
//if connection is using external connection pooling then the event will be risen right after it connects.
if (!writeConnection.usesExternalConnectionPooling()) {
postAcquireConnection(writeConnection);
}
// Transactions are lazily started on connections.
if (isInTransaction()) {
basicBeginTransaction(writeConnection);
}
return getWriteConnections().get(poolName);
}
/**
* INTERNAL:
* A begin transaction failed.
* Re-connect and retry the begin transaction.
*/
@Override
public DatabaseException retryTransaction(Accessor writeConnection, DatabaseException databaseException, int retryCount, AbstractSession executionSession) {
if (writeConnection.getPool() == null) {
return super.retryTransaction(writeConnection, databaseException, retryCount, executionSession);
}
String poolName = writeConnection.getPool().getName();
DatabaseLogin login = getLogin();
int count = login.getQueryRetryAttemptCount();
DatabaseException exceptionToThrow = databaseException;
while (retryCount < count) {
getWriteConnections().remove(poolName);
//if connection is using external connection pooling then the event will be risen right after it connects.
if (!writeConnection.usesExternalConnectionPooling()) {
preReleaseConnection(writeConnection);
}
writeConnection.getPool().releaseConnection(writeConnection);
try {
// attempt to reconnect for a certain number of times.
// servers may take some time to recover.
++retryCount;
writeConnection = writeConnection.getPool().acquireConnection();
writeConnection.beginTransaction(this);
//passing the retry count will prevent a runaway retry where
// we can acquire connections but are unable to execute any queries
if (retryCount > 1) {
// We are retrying more than once lets wait to give connection time to restart.
//Give the failover time to recover.
Thread.sleep(login.getDelayBetweenConnectionAttempts());
}
getWriteConnections().put(poolName, writeConnection);
writeConnection.createCustomizer(this);
//if connection is using external connection pooling then the event will be risen right after it connects.
if (!writeConnection.usesExternalConnectionPooling()) {
postAcquireConnection(writeConnection);
}
return null;
} catch (DatabaseException ex){
//replace original exception with last exception thrown
//this exception could be a data based exception as opposed
//to a connection exception that needs to go back to the customer.
exceptionToThrow = ex;
} catch (InterruptedException ex) {
//Ignore interrupted exception.
}
}
return exceptionToThrow;
}
/**
* INTERNAL:
* Set the connection to be used for database modification.
*/
public void setWriteConnections(Map<String, Accessor> writeConnections) {
// Clear customizers.
if ((this.writeConnections != null) && (writeConnections == null)) {
for (Accessor accessor : this.writeConnections.values()) {
accessor.releaseCustomizer(this);
}
}
this.writeConnections = writeConnections;
}
/**
* INTERNAL:
* Set the connection to be used for database modification.
*/
public void setWriteConnection(Accessor writeConnection) {
if (writeConnection == null) {
setWriteConnections(null);
return;
}
String poolName = null;
if (writeConnection.getPool() != null) {
poolName = writeConnection.getPool().getName();
} else {
poolName = ServerSession.NOT_POOLED;
}
addWriteConnection(poolName, writeConnection);
}
/**
* INTERNAL:
* Print the connection status with the session.
*/
@Override
public String toString() {
StringWriter writer = new StringWriter();
writer.write(getSessionTypeString());
writer.write("(");
writer.write(String.valueOf(getWriteConnections()));
writer.write(")");
return writer.toString();
}
/**
* INTERNAL:
* Return the manager that allows this processor to receive or propagate commands from/to TopLink cluster
* @see CommandManager
* @return a remote command manager
*/
@Override
public CommandManager getCommandManager() {
return this.parent.getCommandManager();
}
/**
* INTERNAL:
* Return whether changes should be propagated to TopLink cluster. This is one of the required
* cache synchronization setting
*/
@Override
public boolean shouldPropagateChanges() {
return this.parent.shouldPropagateChanges();
}
/**
* INTERNAL:
* Release the cursor query's connection.
*/
@Override
public void releaseReadConnection(Accessor connection) {
// If the cursor's connection is the write connection, then do not release it.
if ((this.writeConnections != null) && this.writeConnections.containsValue(connection)) {
return;
}
//bug 4668234 -- used to only release connections on server sessions but should always release
this.parent.releaseReadConnection(connection);
}
/**
* INTERNAL:
* This method is called in case externalConnectionPooling is used.
* If returns true, accessor used by the session keeps its
* connection open until released by the session.
*/
@Override
public boolean isExclusiveConnectionRequired() {
return !this.connectionPolicy.isLazy && isActive();
}
}