| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2020 IBM Corporation. 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 |
| // 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. |
| */ |
| protected DatasourceAccessor() { |
| this.isInTransaction = false; |
| this.callCount = 0; |
| this.isConnected = false; |
| this.isValid = true; |
| } |
| |
| /** |
| * Clone the accessor. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public void setIsValid(boolean isValid){ |
| this.isValid = isValid; |
| } |
| |
| /** |
| * Return the transaction status of the receiver. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public boolean usesExternalConnectionPooling() { |
| return usesExternalConnectionPooling; |
| } |
| |
| /** |
| * Begin a transaction on the database. If not using managed transaction begin a local transaction. |
| */ |
| @Override |
| 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", 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public int getCallCount() { |
| return callCount; |
| } |
| |
| /** |
| * Commit a transaction on the database. If using non-managed transaction commit the local transaction. |
| */ |
| @Override |
| 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", 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. |
| */ |
| @Override |
| public void connect(Login login, AbstractSession session) throws DatabaseException { |
| session.startOperationProfile(SessionProfiler.ConnectionManagement); |
| session.incrementProfile(SessionProfiler.Connects); |
| |
| try { |
| if (session.shouldLog(SessionLog.FINE, SessionLog.CONNECTION)) {// Avoid printing if no logging required. |
| Object[] args = { login }; |
| session.log(SessionLog.FINE, SessionLog.CONNECTION, "connecting", args, 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. |
| */ |
| @Override |
| public void disconnect(AbstractSession session) throws DatabaseException { |
| session.log(SessionLog.FINE, SessionLog.CONNECTION, "disconnect", 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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), 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. |
| */ |
| @Override |
| public void reestablishConnection(AbstractSession session) throws DatabaseException { |
| if (session.shouldLog(SessionLog.FINE, SessionLog.CONNECTION)) {// Avoid printing if no logging required. |
| Object[] args = { getLogin() }; |
| session.log(SessionLog.FINE, 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", null, this); |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public java.sql.Connection getConnection() { |
| return (java.sql.Connection)this.datasourceConnection; |
| } |
| |
| /** |
| * Return column information for the specified |
| * database objects. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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", 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public void writesCompleted(AbstractSession session) { |
| //this is a no-op in this method as we do not batch on this accessor |
| } |
| |
| /** |
| * Return sequencing callback. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| 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. |
| */ |
| @Override |
| public ConnectionPool getPool() { |
| return pool; |
| } |
| |
| /** |
| * Set the associated connection pool this connection was obtained from. |
| */ |
| @Override |
| public void setPool(ConnectionPool pool) { |
| this.pool = pool; |
| } |
| } |