All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atomikos.jdbc.nonxa.AtomikosThreadLocalConnection Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
/**
 * Copyright (C) 2000-2017 Atomikos 
 *
 * LICENSE CONDITIONS
 *
 * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
 */

package com.atomikos.jdbc.nonxa;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import com.atomikos.beans.PropertyUtils;
import com.atomikos.datasource.pool.Reapable;
import com.atomikos.datasource.pool.XPooledConnectionEventListener;
import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.CompositeTransactionManager;
import com.atomikos.icatch.config.Configuration;
import com.atomikos.icatch.jta.TransactionManagerImp;
import com.atomikos.jdbc.AbstractConnectionProxy;
import com.atomikos.jdbc.AtomikosSQLException;
import com.atomikos.jdbc.JdbcConnectionProxyHelper;
import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;
import com.atomikos.util.ClassLoadingHelper;
import com.atomikos.util.DynamicProxy;

/**
 *
 *
 *
 * A dynamic proxy class that wraps JDBC local connections to enable them for
 * JTA transactions and the J2EE programming model: different modules in the
 * same thread (and transaction) can get a connection from the datasource, and
 * rollback will undo all that work.
 *
 * This proxy also maintains state on behalf of the application/transaction;
 * the underlying connection can be re-pooled if two conditions evaluate to true:
 * 
    *
  • There is no pending SQL work in a pending transaction; i.e. all SQL has been committed or rolled back.
  • *
  • There are no pending close() calls, i.e. no further SQL will arrive on the application's behalf.
  • *
* */ class AtomikosThreadLocalConnection extends AbstractConnectionProxy implements JtaAwareNonXaConnection { private static final Logger LOGGER = LoggerFactory.createLogger(AtomikosThreadLocalConnection.class); private final static List ENLISTMENT_METHODS = Arrays.asList(new String[] {"createStatement", "prepareStatement", "prepareCall"}); private final static List CLOSE_METHODS = Arrays.asList(new String[] {"close"}); private final static List XA_INCOMPATIBLE_METHODS = Arrays.asList(new String[] {"commit", "rollback", "setSavepoint", "releaseSavepoint"}); private final static List NON_TRANSACTIONAL_METHOD_NAMES = Arrays.asList(new String[] { "equals", "hashCode", "notify", "notifyAll", "toString", "wait" }); private static Class[] MINIMUM_SET_OF_INTERFACES = {Reapable.class, DynamicProxy.class, java.sql.Connection.class }; private int useCount; private CompositeTransaction transaction; private boolean stale; private AtomikosNonXAPooledConnection pooledConnection; private Connection wrapped; private boolean originalAutoCommitState; private AtomikosNonXAParticipant participant; // the participant; kept here to add heuristic msgs to // note: this will be null for non-transactional use! private boolean readOnly; private String resourceName; static Object newInstance ( AtomikosNonXAPooledConnection pooledConnection , String resourceName ) { Object ret = null; Object obj = pooledConnection.getConnection(); Set> interfaces = PropertyUtils.getAllImplementedInterfaces ( obj.getClass() ); interfaces.add ( Reapable.class ); //see case 24532 interfaces.add ( DynamicProxy.class ); Class[] interfaceClasses = ( Class[] ) interfaces.toArray ( new Class[0] ); List classLoaders = new ArrayList(); classLoaders.add ( Thread.currentThread().getContextClassLoader() ); classLoaders.add ( obj.getClass().getClassLoader() ); classLoaders.add ( AtomikosThreadLocalConnection.class.getClassLoader() ); ret = ClassLoadingHelper.newProxyInstance ( classLoaders , MINIMUM_SET_OF_INTERFACES , interfaceClasses , new AtomikosThreadLocalConnection ( pooledConnection ) ); DynamicProxy dproxy = (DynamicProxy) ret; AtomikosThreadLocalConnection c = (AtomikosThreadLocalConnection) dproxy.getInvocationHandler(); c.resourceName = resourceName; return ret; } private AtomikosThreadLocalConnection ( AtomikosNonXAPooledConnection pooledConnection ) { this.stale = false; this.useCount = 0; this.transaction = null; this.pooledConnection = pooledConnection; this.wrapped = pooledConnection.getConnection(); this.readOnly = pooledConnection.getReadOnly(); } private void setStale () { this.stale = true; } private void resetForNextTransaction () { try { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": resetting autoCommit to " + originalAutoCommitState ); //see case 24567 wrapped.setAutoCommit ( originalAutoCommitState ); }catch ( Exception ex ){ LOGGER.logError ( "Failed to reset original autoCommit state: "+ex.getMessage(), ex); } setTransaction ( null ); participant = null; } boolean isStale () { return stale; } private void decUseCount () { useCount--; markForReuseIfPossible (); } public void incUseCount () { useCount++; } private void setTransaction ( CompositeTransaction tx ) { this.transaction = tx; } private void updateInTransaction () throws SQLException { CompositeTransactionManager ctm = Configuration.getCompositeTransactionManager (); if ( ctm == null ) return; CompositeTransaction ct = ctm.getCompositeTransaction (); if ( ct != null && ct.getProperty ( TransactionManagerImp.JTA_PROPERTY_NAME ) != null ) { // if we are already in another (parent) tx then reject this, // because nested tx rollback can not be supported!!! if ( isInTransaction () && !isInTransaction ( ct ) ) AtomikosSQLException.throwAtomikosSQLException ( "Connection accessed by transaction " + ct.getTid () + " is already in use in another transaction: " + transaction.getTid () + " Non-XA connections are not compatible with nested transaction use." ); setTransaction ( ct ); if ( participant == null ) { // make sure we add a participant for commit/rollback // notifications participant = new AtomikosNonXAParticipant ( this , resourceName ); participant.setReadOnly ( readOnly ); ct.addParticipant ( participant ); originalAutoCommitState = wrapped.getAutoCommit(); wrapped.setAutoCommit ( false ); } } else { // the current thread has NO tx, which means none was started (OK) // or // the previous one was terminated by the application. In that case, // check that there was no ACTIVE rollback on timeout (meaning that // transactionTerminated was never called!!!) if ( isInTransaction () ) transactionTerminated ( false ); } } public Object invoke ( Object o , Method m , Object[] args ) throws Throwable { final String methodName = m.getName(); //see case 24532 if ( methodName.equals ( "getInvocationHandler" ) ) return this; if (methodName.equals("reap")) { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": reap()..." ); reap(); if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": reap done." ); return null; } else if ( methodName.equals ("isNoLongerInUse") ) { return Boolean.valueOf ( isNoLongerInUse() ); } else if ( methodName.equals ("isInTransaction") ) { return m.invoke( this , args); } else if (methodName.equals("isClosed")) { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": isClosed()..." ); Object ret = Boolean.valueOf ( isStale() ); if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": isClosed() returning " + ret ); return ret; } // detect illegal use after connection was resubmitted to the pool else if ( isStale () && !NON_TRANSACTIONAL_METHOD_NAMES.contains ( methodName ) ) { if (!methodName.equals("close")) AtomikosSQLException.throwAtomikosSQLException ( "Attempt to use connection after it was closed." ); } // disallow local TX methods in global TX context else if ( isInTransaction() ) { if (XA_INCOMPATIBLE_METHODS.contains(methodName)) AtomikosSQLException.throwAtomikosSQLException("Cannot call method '" + methodName + "' while a global transaction is running"); if (methodName.equals("setAutoCommit") && args[0].equals(Boolean.TRUE)) { AtomikosSQLException.throwAtomikosSQLException("Cannot call 'setAutoCommit(true)' while a global transaction is running"); } if (methodName.equals("getAutoCommit")) { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": getAutoCommit()..." ); Object ret = Boolean.FALSE; if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": getAutoCommit() returning false." ); return ret; } CompositeTransactionManager ctm = Configuration.getCompositeTransactionManager (); CompositeTransaction ct = ctm.getCompositeTransaction(); // if we are already in another (parent) tx then reject this, // because nested tx rollback can not be supported!!! if ( ct != null && !isInTransaction ( ct ) ) AtomikosSQLException.throwAtomikosSQLException ( "Connection accessed by transaction " + ct.getTid () + " is already in use in another transaction: " + transaction.getTid () + " Non-XA connections are not compatible with nested transaction use." ); } // check for enlistment else if (ENLISTMENT_METHODS.contains(methodName)) { updateInTransaction(); } Object ret = null; // check for delistment if (CLOSE_METHODS.contains(methodName)) { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": close..." ); decUseCount(); if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": close done." ); return null; } else { try { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": calling " + methodName + " on vendor connection..." ); ret = m.invoke ( wrapped , args); } catch (Exception ex) { pooledConnection.setErroneous(); JdbcConnectionProxyHelper.convertProxyError ( ex , "Error delegating '" + methodName + "' call" ); } } if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": " + methodName + " returning " + ret ); if ( ret instanceof Statement ) { Statement s = ( Statement ) ret; addStatement ( s ); } return ret; } /** * Checks if the connection is being used on behalf the a transaction. * * @return */ boolean isInTransaction() { return transaction != null; } /** * Checks if the connection is being used on behalf of the given transaction. * * @param ct * @return */ boolean isInTransaction ( CompositeTransaction ct ) { boolean ret = false; //See case 29060 and 28683 :COPY attribute to avoid race conditions with NPE results CompositeTransaction tx = transaction; if ( tx != null && ct != null ) { ret = tx.isSameTransaction ( ct ); } return ret; } //can the underlying connection be pooled again? boolean isNoLongerInUse() { return useCount <= 0 && !isInTransaction(); } private void markForReuseIfPossible () { if ( isNoLongerInUse() ) { LOGGER .logTrace ( "ThreadLocalConnection: detected reusability" ); setStale(); pooledConnection.fireOnXPooledConnectionTerminated(); } else { if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( "ThreadLocalConnection: not reusable yet" ); } } private void reap() { LOGGER.logWarning ( this + ": reaping - check if the application closes connections correctly, or increase the reapTimeout value"); setStale(); useCount =0; markForReuseIfPossible(); //added for BugzID 22101 pooledConnection.setErroneous(); forceCloseAllPendingStatements ( true ); } //notification of transaction termination public void transactionTerminated ( boolean commit ) throws SQLException { // delegate commit or rollback to the underlying connection try { if ( commit ) { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": committing on connection..."); wrapped.commit (); } else { // see case 84252 forceCloseAllPendingStatements ( false ); if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": transaction aborting - " + "pessimistically closing all pending statements to avoid autoCommit after timeout" ); if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": rolling back on connection..."); wrapped.rollback (); } } catch ( SQLException e ) { // make sure that reuse in pool is not possible pooledConnection.setErroneous (); String msg = "Error in commit on vendor connection"; if ( ! commit ) msg = "Error in rollback on vendor connection"; AtomikosSQLException.throwAtomikosSQLException ( msg , e ); } finally { // reset attributes for next tx resetForNextTransaction (); // put connection in pool if no longer used // note: if erroneous then the pool will destroy the connection (cf case 30752) // which seems desirable to avoid pool exhaustion markForReuseIfPossible (); //see case 30752: resetting autoCommit should not be done here: //connection may have been reused already! } } public void registerXPooledConnectionEventListener ( XPooledConnectionEventListener l ) { pooledConnection.registerXPooledConnectionEventListener ( l ); } public String toString() { StringBuffer ret = new StringBuffer(); ret.append ( "atomikos non-xa connection proxy for "); ret.append(wrapped); return ret.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy