/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 | |
* GYorke - non-bug update to set accessor in case of connection failure. Thi | |
* will allow the retry code to function. | |
******************************************************************************/ | |
package org.eclipse.persistence.internal.databaseaccess; | |
import java.util.*; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.sessions.Login; | |
import org.eclipse.persistence.queries.Call; | |
import org.eclipse.persistence.internal.sequencing.SequencingCallback; | |
import org.eclipse.persistence.internal.sequencing.SequencingCallbackFactory; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.logging.SessionLog; | |
import org.eclipse.persistence.sessions.SessionProfiler; | |
import org.eclipse.persistence.sessions.server.ConnectionPool; | |
/** | |
* INTERNAL: | |
* <code>DatasourceAccessor</code> is an abstract implementation | |
* of the <code>Accessor</code> interface providing common functionality to the concrete database and EIS accessors. | |
* It is responsible for | |
* connecting, | |
* transactions, | |
* call execution | |
* | |
* @see Call | |
* @see Login | |
* | |
* @author James | |
* @since OracleAS TopLink 10<i>g</i> (10.0.3) | |
* | |
* 05/28/2008-1.0M8 Andrei Ilitchev. | |
* - 224964: Provide support for Proxy Authentication through JPA. | |
* Added ConnectionCustomizer, also fixed postConnect/preDisconnect ExternalConnection calls so that they called in case of reads, too. | |
*/ | |
public abstract class DatasourceAccessor implements Accessor { | |
/** Store the reference to the driver level connection. */ | |
protected Object datasourceConnection; | |
/** Store the login information that connected this accessor. */ | |
protected Login login; | |
/** | |
* Keep track of the number of concurrent active calls. | |
* This is used for connection pooling for loadbalancing and for external connection pooling. | |
*/ | |
protected int callCount; | |
/** | |
* Keep track of the number of the storedprocedure statement that being executed. | |
*/ | |
public int storedProcedureStatementsCount; | |
/** | |
* Keep track of the number of the read statement that being executed. | |
*/ | |
public int readStatementsCount; | |
/** | |
* Keep track of the number of the write statement that being executed. | |
*/ | |
public int writeStatementsCount; | |
//Stores the number of executed read SQL statements | |
public static final String READ_STATEMENTS_COUNT_PROPERTY = "Read_Statements_Count_Property"; | |
//Stores the number of executed write SQL statements | |
public static final String WRITE_STATEMENTS_COUNT_PROPERTY = "Write_Statements_Count_Property"; | |
//Stores the number of executed store procedure statements | |
public static final String STOREDPROCEDURE_STATEMENTS_COUNT_PROPERTY = "StoredProcedure_Statements_Count_Property"; | |
/** Keep track if the accessor is within a transaction context */ | |
protected boolean isInTransaction; | |
/** Keep track of whether the accessor is "connected". */ | |
protected boolean isConnected; | |
/** PERF: Cache platform to avoid gets (small but can add up). */ | |
/** This is also required to ensure all accessors for a session are using the same platform. */ | |
protected DatasourcePlatform platform; | |
/** | |
* This attribute is used to determine if the connection should be returned to the pool or | |
* removed from the pool and closed. It will be set to false if an exception occurs during | |
* Call execution. | |
*/ | |
protected boolean isValid; | |
/** | |
* During (not external) transaction, SequencingManager may set SequencingCallback on the accessor, | |
* The callback's only method is called when transaction commits, | |
* after transaction is completed the callback is discarded. | |
*/ | |
protected transient SequencingCallback sequencingCallback; | |
/** | |
* This attribute is used to track failures on an accessor that may be communication based. | |
* If a failure is detected executing a call with a query timeout this flag is set. If an error happens | |
* twice in a row on the same accessor then that accessor will be checked for a comm error. If there is no | |
* query timeout then the flag is not set and the accessor will be checked immediately. | |
*/ | |
protected boolean possibleFailure; | |
/** | |
* Used only in externalConnectionPooling case. Indicates which session is currently using connection. | |
* Allows to rise appropriate session's event when connection is already/still alive. | |
* Events will be risen when ClientSession or DatabaseSession acquires connection in the beginning | |
* and releases connection in the end of transaction (in afterCompletion in jta case). | |
* In case the session requires exclusive connection (ExclusiveIsolatedClientSession) the events | |
* will be risen every time the session acquires or releases connection - | |
* which is a rare event - the connection is kept for the duration of jta transaction, | |
* after jta transaction is completed the new connection is acquired on a first query execution | |
* and kept until the next beginTransaction call. | |
* In non-jta case the connection is acquired for session's life and release only by session's release. | |
* Note that the attribute is nullified only in one place - by closeConnection method. | |
*/ | |
protected transient AbstractSession currentSession; | |
/** | |
* PERF: Cache connection pooling flag. | |
*/ | |
protected boolean usesExternalConnectionPooling; | |
/** | |
* Back-door to allow isConnect checks. | |
* Since we now support fail-over and retry, removing old isConnected usage which can | |
* cause major performance issues (on Sybase), and minor ones in general. | |
*/ | |
public static boolean shouldCheckConnection = false; | |
/** | |
* Allows session-specific connection customization. | |
*/ | |
protected ConnectionCustomizer customizer; | |
protected ConnectionPool pool; | |
/** | |
* Default Constructor. | |
*/ | |
public DatasourceAccessor() { | |
this.isInTransaction = false; | |
this.callCount = 0; | |
this.isConnected = false; | |
this.isValid = true; | |
} | |
/** | |
* Clone the accessor. | |
*/ | |
public Object clone() { | |
try { | |
DatasourceAccessor accessor = (DatasourceAccessor)super.clone(); | |
if(accessor.customizer != null) { | |
accessor.customizer.setAccessor(accessor); | |
} | |
return accessor; | |
} catch (CloneNotSupportedException exception) { | |
throw new InternalError("clone not supported"); | |
} | |
} | |
/** | |
* Called from beforeCompletion external transaction synchronization listener callback | |
* to close the external connection corresponding to the completing external transaction. | |
* Final sql calls could be sent through the connection by this method | |
* before it closes the connection. | |
*/ | |
public void closeJTSConnection() { | |
if (usesExternalTransactionController()) { | |
this.isInTransaction = false; | |
if (this.usesExternalConnectionPooling) { | |
closeConnection(); | |
} | |
} | |
} | |
/** | |
* Set the transaction transaction status of the receiver. | |
*/ | |
protected void setIsInTransaction(boolean value) { | |
isInTransaction = value; | |
} | |
/** | |
* This should be set to false if a communication failure occurred during a call execution. | |
* In the case of an invalid accessor the Accessor will not be returned to the pool. | |
*/ | |
public void setIsValid(boolean isValid){ | |
this.isValid = isValid; | |
} | |
/** | |
* Return the transaction status of the receiver. | |
*/ | |
public boolean isInTransaction() { | |
return isInTransaction; | |
} | |
/** | |
* Returns true if this Accessor can continue to be used. This will be false if a communication | |
* failure occurred during a call execution. In the case of an invalid accessor the Accessor | |
* will not be returned to the pool. | |
*/ | |
public boolean isValid(){ | |
return this.isValid; | |
} | |
public boolean isPossibleFailure() { | |
return possibleFailure; | |
} | |
public void setPossibleFailure(boolean possibleFailure) { | |
this.possibleFailure = possibleFailure; | |
} | |
/** | |
* Return true if some external connection pool is in use. | |
*/ | |
public boolean usesExternalConnectionPooling() { | |
return usesExternalConnectionPooling; | |
} | |
/** | |
* Begin a transaction on the database. If not using managed transaction begin a local transaction. | |
*/ | |
public void beginTransaction(AbstractSession session) throws DatabaseException { | |
if (usesExternalTransactionController()) { | |
if (session.isExclusiveConnectionRequired() && !this.isInTransaction && this.usesExternalConnectionPooling) { | |
closeConnection(); | |
} | |
this.isInTransaction = true; | |
return; | |
} | |
session.log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_transaction", (Object[])null, this); | |
try { | |
session.startOperationProfile(SessionProfiler.Transaction); | |
incrementCallCount(session); | |
basicBeginTransaction(session); | |
this.isInTransaction = true; | |
} finally { | |
decrementCallCount(); | |
session.endOperationProfile(SessionProfiler.Transaction); | |
} | |
} | |
/** | |
* Begin the driver level transaction. | |
*/ | |
protected abstract void basicBeginTransaction(AbstractSession session); | |
/** | |
* Commit the driver level transaction. | |
*/ | |
protected abstract void basicCommitTransaction(AbstractSession session); | |
/** | |
* Rollback the driver level transaction. | |
*/ | |
protected abstract void basicRollbackTransaction(AbstractSession session); | |
/** | |
* Used for load balancing and external pooling. | |
*/ | |
public synchronized void decrementCallCount() { | |
int count = this.callCount; | |
// Avoid decrementing count if already zero, (failure before increment). | |
if (count <= 0) { | |
return; | |
} | |
this.callCount--; | |
if (this.usesExternalConnectionPooling && (!this.isInTransaction) && (currentSession == null || !currentSession.isExclusiveConnectionRequired()) && (count == 1)) { | |
try { | |
closeConnection(); | |
} catch (DatabaseException ignore) { | |
// Don't allow for errors to be masked by disconnect. | |
} | |
} | |
} | |
/** | |
* Used for load balancing and external pooling. | |
*/ | |
public synchronized void incrementCallCount(AbstractSession session) { | |
this.callCount++; | |
if (this.callCount == 1) { | |
// If the login is null, then this accessor has never been connected. | |
if (this.login == null) { | |
throw DatabaseException.databaseAccessorNotConnected(); | |
} | |
// If the connection is no longer connected, it may have timed out. | |
if (this.datasourceConnection != null) { | |
if (shouldCheckConnection && !isConnected()) { | |
if (this.isInTransaction) { | |
throw DatabaseException.databaseAccessorNotConnected(); | |
} else { | |
reconnect(session); | |
} | |
} | |
} else { | |
// If ExternalConnectionPooling is used, the connection can be re-established. | |
if (this.usesExternalConnectionPooling) { | |
reconnect(session); | |
session.postAcquireConnection(this); | |
currentSession = session; | |
} else { | |
throw DatabaseException.databaseAccessorNotConnected(); | |
} | |
} | |
} | |
} | |
/** | |
* Reset statement count. | |
*/ | |
public void reset() { | |
this.readStatementsCount = 0; | |
this.writeStatementsCount = 0; | |
this.storedProcedureStatementsCount = 0; | |
} | |
/** | |
* Connect to the database. | |
* Exceptions are caught and re-thrown as EclipseLink exceptions. | |
*/ | |
protected void connectInternal(Login login, AbstractSession session) throws DatabaseException { | |
try{ | |
this.datasourceConnection = login.connectToDatasource(this, session); | |
this.isConnected = true; | |
if(this.customizer != null) { | |
customizer.customize(); | |
} | |
}catch (DatabaseException ex){ | |
//Set the accessor to ensure the retry code has an opportunity to retry. | |
ex.setAccessor(this); | |
throw ex; | |
} | |
} | |
/** | |
* Set whether the accessor has a connection to the "data store". | |
*/ | |
protected void setIsConnected(boolean isConnected) { | |
this.isConnected = isConnected; | |
} | |
/** | |
* Used for load balancing and external pooling. | |
*/ | |
protected void setCallCount(int callCount) { | |
this.callCount = callCount; | |
} | |
/** | |
* Used for load balancing and external pooling. | |
*/ | |
public int getCallCount() { | |
return callCount; | |
} | |
/** | |
* Commit a transaction on the database. If using non-managed transaction commit the local transaction. | |
*/ | |
public void commitTransaction(AbstractSession session) throws DatabaseException { | |
if (usesExternalTransactionController()) { | |
// if there is no external TX controller, then that means we are currently not synchronized | |
// with a global JTS transaction. In this case, there won't be any 'afterCompletion' | |
// callbacks so we have to release the connection here. It is possible (WLS 5.1) to choose | |
// 'usesExternalTransactionController' on the login, but still acquire a uow that WON'T be | |
// synchronized with a global TX. | |
if (!session.isSynchronized()) { | |
this.isInTransaction = false; | |
if (this.usesExternalConnectionPooling) { | |
// closeConnection method uses currentSession and then sets it to null. | |
currentSession = session; | |
closeConnection(); | |
} | |
} | |
return; | |
} | |
session.log(SessionLog.FINER, SessionLog.TRANSACTION, "commit_transaction", (Object[])null, this); | |
try { | |
session.startOperationProfile(SessionProfiler.Transaction); | |
incrementCallCount(session); | |
basicCommitTransaction(session); | |
if(sequencingCallback != null) { | |
sequencingCallback.afterCommit(this); | |
} | |
this.isInTransaction = false; | |
} finally { | |
sequencingCallback = null; | |
decrementCallCount(); | |
session.endOperationProfile(SessionProfiler.Transaction); | |
} | |
} | |
/** | |
* Connect to the datasource. Through using a CCI ConnectionFactory. | |
* Catch exceptions and re-throw as EclipseLink exceptions. | |
*/ | |
public void connect(Login login, AbstractSession session) throws DatabaseException { | |
session.startOperationProfile(SessionProfiler.ConnectionManagement); | |
session.incrementProfile(SessionProfiler.Connects); | |
try { | |
if (session.shouldLog(SessionLog.CONFIG, SessionLog.CONNECTION)) {// Avoid printing if no logging required. | |
session.log(SessionLog.CONFIG, SessionLog.CONNECTION, "connecting", new Object[] { login }, this); | |
} | |
setLogin(login); | |
this.setDatasourcePlatform((DatasourcePlatform)session.getDatasourceLogin().getDatasourcePlatform()); | |
createCustomizer(session); | |
try { | |
connectInternal(login, session); | |
this.isInTransaction = false; | |
} catch (RuntimeException exception) { | |
session.handleSevere(exception); | |
} | |
if (session.hasEventManager()) { | |
session.getEventManager().postConnect(this); | |
} | |
incrementCallCount(session); | |
try { | |
buildConnectLog(session); | |
} finally { | |
decrementCallCount(); | |
} | |
} finally { | |
session.endOperationProfile(SessionProfiler.ConnectionManagement); | |
} | |
} | |
/** | |
* Close the connection to the driver level datasource. | |
*/ | |
protected abstract void closeDatasourceConnection(); | |
/** | |
* Execute the call to driver level datasource. | |
*/ | |
protected abstract Object basicExecuteCall(Call call, AbstractRecord row, AbstractSession session); | |
/** | |
* Build a log string of any driver metadata that can be obtained. | |
*/ | |
protected abstract void buildConnectLog(AbstractSession session); | |
/** | |
* Return the login | |
*/ | |
public Login getLogin() { | |
return login; | |
} | |
/** | |
* SECURE: | |
* set the login | |
*/ | |
protected void setLogin(Login login) { | |
this.login = login; | |
this.usesExternalConnectionPooling = login.shouldUseExternalConnectionPooling(); | |
} | |
/** | |
* Disconnect from the datasource. | |
*/ | |
public void disconnect(AbstractSession session) throws DatabaseException { | |
session.log(SessionLog.CONFIG, SessionLog.CONNECTION, "disconnect", (Object[])null, this); | |
if (this.datasourceConnection == null) { | |
return; | |
} | |
session.incrementProfile(SessionProfiler.Disconnects); | |
session.startOperationProfile(SessionProfiler.ConnectionManagement); | |
try { | |
releaseCustomizer(); | |
closeDatasourceConnection(); | |
this.datasourceConnection = null; | |
this.isInTransaction = true; | |
} finally { | |
session.endOperationProfile(SessionProfiler.ConnectionManagement); | |
} | |
} | |
/** | |
* Close the accessor's connection. | |
* This is used only for external connection pooling | |
* when it is intended for the connection to be reconnected in the future. | |
*/ | |
public void closeConnection() { | |
try { | |
if (this.datasourceConnection != null) { | |
if (isDatasourceConnected()) { | |
if(currentSession != null) { | |
currentSession.preReleaseConnection(this); | |
} | |
if(customizer != null && customizer.isActive()) { | |
customizer.clear(); | |
} | |
closeDatasourceConnection(); | |
} | |
this.datasourceConnection = null; | |
} | |
} catch (DatabaseException exception) { | |
// Ignore | |
this.datasourceConnection = null; | |
} finally { | |
currentSession = null; | |
} | |
} | |
/** | |
* Execute the call. | |
* @return depending of the type either the row count, row or vector of rows. | |
*/ | |
public Object executeCall(Call call, AbstractRecord translationRow, AbstractSession session) throws DatabaseException { | |
// If the login is null, then this accessor has never been connected. | |
if (this.login == null) { | |
throw DatabaseException.databaseAccessorNotConnected(); | |
} | |
if (session.shouldLog(SessionLog.FINE, SessionLog.SQL)) {// pre-check to improve performance | |
session.log(SessionLog.FINE, SessionLog.SQL, call.getLogString(this), (Object[])null, this, false); | |
} | |
Object result = basicExecuteCall(call, translationRow, session); | |
return result; | |
} | |
/** | |
* PUBLIC: | |
* Reconnect to the database. This can be used if the connection was disconnected or timedout. | |
* This ensures that the security is checked as it is public. | |
* Because the messages can take a long time to build, | |
* pre-check whether messages should be logged. | |
*/ | |
public void reestablishConnection(AbstractSession session) throws DatabaseException { | |
if (session.shouldLog(SessionLog.CONFIG, SessionLog.CONNECTION)) {// Avoid printing if no logging required. | |
Object[] args = { getLogin() }; | |
session.log(SessionLog.CONFIG, SessionLog.CONNECTION, "reconnecting", args, this); | |
} | |
reestablishCustomizer(); | |
reconnect(session); | |
this.isInTransaction = false; | |
this.isValid = true; | |
if (session.hasEventManager()) { | |
session.getEventManager().postConnect(this); | |
} | |
} | |
/** | |
* Attempt to save some of the cost associated with getting a fresh connection. | |
* Assume the DatabaseDriver has been cached, if appropriate. | |
* Note: Connections that are participating in transactions will not be refreshed.^M | |
*/ | |
protected void reconnect(AbstractSession session) throws DatabaseException { | |
session.log(SessionLog.FINEST, SessionLog.CONNECTION, "reconnecting_to_external_connection_pool"); | |
session.startOperationProfile(SessionProfiler.ConnectionManagement); | |
try { | |
connectInternal(this.login, session); | |
} finally { | |
session.endOperationProfile(SessionProfiler.ConnectionManagement); | |
} | |
} | |
/** | |
* Return the platform. | |
*/ | |
public DatasourcePlatform getDatasourcePlatform() { | |
return platform; | |
} | |
/** | |
* Set the platform. | |
* This should be set to the session's platform, not the connections | |
* which may not be configured correctly. | |
*/ | |
public void setDatasourcePlatform(DatasourcePlatform platform) { | |
this.platform = platform; | |
} | |
/** | |
* Return the driver level connection. | |
*/ | |
public Object getDatasourceConnection() { | |
return datasourceConnection; | |
} | |
/** | |
* Helper method to return the JDBC connection for DatabaseAccessor. | |
* Was going to deprecate this, but since most clients are JDBC this is useful. | |
*/ | |
public java.sql.Connection getConnection() { | |
return (java.sql.Connection)this.datasourceConnection; | |
} | |
/** | |
* Return column information for the specified | |
* database objects. | |
*/ | |
public Vector getColumnInfo(String catalog, String schema, String tableName, String columnName, AbstractSession session) throws DatabaseException { | |
return new Vector(); | |
} | |
/** | |
* Return the number of read statements. | |
*/ | |
public int getReadStatementsCount() { | |
return readStatementsCount; | |
} | |
/** | |
* Return the number of write statements. | |
*/ | |
public int getWriteStatementsCount() { | |
return writeStatementsCount; | |
} | |
/** | |
* Return the number of stored procedure call. | |
*/ | |
public int getStoredProcedureStatementsCount() { | |
return storedProcedureStatementsCount; | |
} | |
/** | |
* Return table information for the specified | |
* database objects. | |
*/ | |
public Vector getTableInfo(String catalog, String schema, String tableName, String[] types, AbstractSession session) throws DatabaseException { | |
return new Vector(); | |
} | |
/** | |
* If client requires to manually set connection they can use the connection manager. | |
*/ | |
protected void setDatasourceConnection(Object connection) { | |
this.datasourceConnection = connection; | |
} | |
/** | |
* Rollback the transaction on the datasource. If not using managed transaction rollback the local transaction. | |
*/ | |
public void rollbackTransaction(AbstractSession session) throws DatabaseException { | |
if (usesExternalTransactionController()) { | |
// if there is no external TX controller, then that means we are currently not synchronized | |
// with a global JTS transaction. In this case, there won't be any 'afterCompletion' | |
// callbacks so we have to release the connection here. It is possible (WLS 5.1) to choose | |
// 'usesExternalTransactionController' on the login, but still acquire a uow that WON'T be | |
// synchronized with a global TX. | |
if (!session.isSynchronized()) { | |
this.isInTransaction = false; | |
if (this.usesExternalConnectionPooling) { | |
// closeConnection method uses currentSession and then sets it to null. | |
currentSession = session; | |
closeConnection(); | |
} | |
} | |
return; | |
} | |
session.log(SessionLog.FINER, SessionLog.TRANSACTION, "rollback_transaction", (Object[])null, this); | |
try { | |
session.startOperationProfile(SessionProfiler.Transaction); | |
incrementCallCount(session); | |
basicRollbackTransaction(session); | |
} finally { | |
this.isInTransaction = false; | |
sequencingCallback = null; | |
decrementCallCount(); | |
session.endOperationProfile(SessionProfiler.Transaction); | |
} | |
} | |
/** | |
* Return true if some external transaction service is controlling transactions. | |
*/ | |
public boolean usesExternalTransactionController() { | |
if (this.login == null) { | |
throw DatabaseException.databaseAccessorNotConnected(); | |
} | |
return this.login.shouldUseExternalTransactionController(); | |
} | |
/** | |
* Return true if the accessor is currently connected to a data source. | |
* Return false otherwise. | |
*/ | |
public boolean isConnected() { | |
if ((this.datasourceConnection == null) && (this.login == null)) { | |
return false; | |
} | |
if (this.usesExternalConnectionPooling) { | |
return true;// As can always reconnect. | |
} | |
if (this.datasourceConnection == null) { | |
return false; | |
} | |
return isDatasourceConnected(); | |
} | |
/** | |
* Return if the driver level connection is connected. | |
*/ | |
protected abstract boolean isDatasourceConnected(); | |
/** | |
* Added as a result of Bug 2804663 - satisfy the Accessor interface | |
* implementation. | |
*/ | |
public void flushSelectCalls(AbstractSession session) { | |
// By default do nothing. | |
} | |
/** | |
* This method will be called after a series of writes have been issued to | |
* mark where a particular set of writes has completed. It will be called | |
* from commitTransaction and may be called from writeChanges. Its main | |
* purpose is to ensure that the batched statements have been executed | |
*/ | |
public void writesCompleted(AbstractSession session) { | |
//this is a no-op in this method as we do not batch on this accessor | |
} | |
/** | |
* Return sequencing callback. | |
*/ | |
public SequencingCallback getSequencingCallback(SequencingCallbackFactory sequencingCallbackFactory) { | |
if(sequencingCallback == null) { | |
sequencingCallback = sequencingCallbackFactory.createSequencingCallback(); | |
} | |
return sequencingCallback; | |
} | |
/** | |
* Attempts to create ConnectionCustomizer. If created the customizer is cached by the accessor. | |
* Called by the owner of accessor (DatabaseSession, ServerSession through ConnectionPool) just once, | |
* typically right after the accessor is created. | |
* Also called by ClientSession when it acquires write accessor. | |
* If accessor already has a customizer set by ConnectionPool then ClientSession's customizer | |
* compared with the existing one and if they are not equal (don't produce identical customization) | |
* then the new customizer set onto accessor, caching the old customizer so that it could be restored later. | |
*/ | |
public void createCustomizer(AbstractSession session) { | |
ConnectionCustomizer newCustomizer; | |
if(customizer == null) { | |
// Create a new customizer. The platform may be null if the accessor hasn't yet been connected. | |
if(platform != null) { | |
newCustomizer = platform.createConnectionCustomizer(this, session); | |
} else { | |
newCustomizer = ((DatasourcePlatform)session.getDatasourcePlatform()).createConnectionCustomizer(this, session); | |
} | |
if(newCustomizer == null) { | |
// Neither old nor new exists - nothing to do. | |
} else { | |
// Old customizer doesn't exist - just set the new one. | |
setCustomizer(newCustomizer); | |
} | |
} else { | |
// the passed session has built the old customizer - no need to build the new one. | |
if(customizer.getSession() == session) { | |
return; | |
} | |
// Create a new customizer. The platform may be null if the accessor hasn't yet been connected. | |
if(platform != null) { | |
newCustomizer = platform.createConnectionCustomizer(this, session); | |
} else { | |
newCustomizer = ((DatasourcePlatform)session.getDatasourcePlatform()).createConnectionCustomizer(this, session); | |
} | |
if(newCustomizer == null) { | |
// New customizer doesn't exist - but the old one does. | |
if(customizer.isActive()) { | |
customizer.clear(); | |
} | |
// The only reason for setting empty customizer is to preserve the previous customizer | |
// until releaseCustomizer(session) is called - where session is the one set in empty customizer. | |
// Happens when ServerSession defines customization but ClientSession explicitly demands no customization. | |
newCustomizer = ConnectionCustomizer.createEmptyCustomizer(session); | |
newCustomizer.setPrevCustomizer(customizer); | |
// No need to call customize on Empty customizer - it does nothing. | |
customizer = newCustomizer; | |
} else { | |
// Both old and new customizers exist. | |
if(newCustomizer.equals(customizer)) { | |
// The equality of customizers means they customize connection in exactly the same way. | |
// Therefore clearing the old customization followed by application of the new one could be skipped: | |
// just keep the old customizer. | |
// Happens when ServerSession and ClientSession define equivalent customizers. | |
} else { | |
// The old customizer substituted for the new one. | |
if(customizer.isActive()) { | |
customizer.clear(); | |
} | |
// Note that the old one is cached in the new one and will be restored | |
// when releaseCustomizer(session( is called - where session is the one set in the new customizer. | |
// Happens when ClientSession customizer overrides ServerSession's customizer. | |
newCustomizer.setPrevCustomizer(customizer); | |
setCustomizer(newCustomizer); | |
} | |
} | |
} | |
} | |
/** | |
* Set customizer, customize the connection if it's available. | |
*/ | |
protected void setCustomizer(ConnectionCustomizer newCustomizer) { | |
this.customizer = newCustomizer; | |
if(getDatasourceConnection() != null) { | |
customizer.customize(); | |
} | |
} | |
/** | |
* Clear customizer if it's active and set it to null. | |
* Called by the same object that has created customizer (DatabaseSession, ConnectionPool) when | |
* the latter is no longer required, typically before releasing the accessor. | |
* Ignored if there's no customizer. | |
*/ | |
public void releaseCustomizer() { | |
if(customizer != null) { | |
if(customizer.isActive()) { | |
customizer.clear(); | |
} | |
customizer = null; | |
} | |
} | |
/** | |
* Clear and remove customizer if its session is the same as the passed one; | |
* in case prevCustomizer exists set it as a new customizer. | |
* Called when ClientSession releases write accessor: | |
* if the customizer was created by the ClientSession it's removed, and | |
* the previous customizer (that ConnectionPool had set) is brought back; | |
* otherwise the customizer (created by ConnectionPool) is kept. | |
* Ignored if there's no customizer. | |
*/ | |
public void releaseCustomizer(AbstractSession session) { | |
if(customizer != null) { | |
if(customizer.getSession() == session) { | |
if(customizer.isActive()) { | |
customizer.clear(); | |
} | |
if(customizer.getPrevCustomizer() == null) { | |
customizer = null; | |
} else { | |
setCustomizer(customizer.getPrevCustomizer()); | |
} | |
} | |
} | |
} | |
/** | |
* This method is called by reestablishConnection. | |
* Nothing needs to be done in case customize is not active (customization hasn't been applied yet). | |
* to repair existing customizer after connection became invalid. | |
* However if connection has been customized then | |
* if connection is still there and deemed to be valid - clear customization. | |
* Otherwise (or if clear fails) remove customizer and set its prevCustomizer as a new customizer, | |
* then re-create customizer using the same session as the original one. | |
*/ | |
protected void reestablishCustomizer() { | |
if(customizer != null && customizer.isActive()) { | |
if(isValid()) { | |
// the method eats SQLException in case of a failure. | |
customizer.clear(); | |
} else { | |
// It's an invalid connection - don't bother trying to clear customization. | |
AbstractSession customizerSession = (AbstractSession)customizer.getSession(); | |
// need this so that the new customizer has the same prevCustomizer as the old one. | |
customizer = customizer.getPrevCustomizer(); | |
// customizer recreated - it's the same as the original one, but not active. | |
createCustomizer(customizerSession); | |
} | |
} | |
} | |
/** | |
* Return the associated connection pool this connection was obtained from. | |
*/ | |
public ConnectionPool getPool() { | |
return pool; | |
} | |
/** | |
* Set the associated connection pool this connection was obtained from. | |
*/ | |
public void setPool(ConnectionPool pool) { | |
this.pool = pool; | |
} | |
} |