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

com.atomikos.jdbc.AtomikosConnectionProxy 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;

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.xa.session.InvalidSessionHandleStateException;
import com.atomikos.datasource.xa.session.SessionHandleState;
import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.CompositeTransactionManager;
import com.atomikos.icatch.Synchronization;
import com.atomikos.icatch.config.Configuration;
import com.atomikos.icatch.jta.TransactionManagerImp;
import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;
import com.atomikos.recovery.TxState;
import com.atomikos.util.ClassLoadingHelper;
import com.atomikos.util.DynamicProxy;

class AtomikosConnectionProxy extends AbstractConnectionProxy
{
	private static final Logger LOGGER = LoggerFactory.createLogger(AtomikosConnectionProxy.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 static Class[] MINIMUM_SET_OF_INTERFACES = {Reapable.class, DynamicProxy.class,java.sql.Connection.class };
	
	private final Connection delegate;
	private SessionHandleState sessionHandleState;
	private boolean closed = false;
	private boolean reaped = false;

	private String toString;

	private AtomikosConnectionProxy ( Connection c, SessionHandleState sessionHandleState)
	{
		this.delegate = c;
		this.sessionHandleState = sessionHandleState;
		sessionHandleState.notifySessionBorrowed();
	}

	public String toString()
	{
		if(toString==null){
			StringBuffer ret = new StringBuffer();
			ret.append ( "atomikos connection proxy for ");
			ret.append (delegate);
			toString= ret.toString();
		}
		return toString;
	}

	//no longer synchronized: see case 22101
	public Object invoke ( Object proxy, Method method, Object[] args ) throws SQLException
	{
		final String methodName = method.getName();

		//see case 24532
		if ( methodName.equals ( "getInvocationHandler" ) ) return this;

		if (methodName.equals("reap")) {
			if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": reaping pending connection..." );

			//LOGGER.logDebug("{} : reaping pending connection..", this);
			reap();
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": reap done!" );
			return null;
		}
		if (methodName.equals("isClosed")) {
			if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": isClosed()..." );
			Object ret = Boolean.valueOf(closed);
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": isClosed() returning " + ret );
			return ret;
		}

		if ( closed && !methodAllowedAfterClose(method) ) {
			if ( reaped ) {
				//'reaped' is a system-triggered variant of closed -> throw exception that explains this
				String msg = "Connection has been reaped - calling " + methodName + " is no longer allowed! Increase reapTimeout to avoid this problem.";
				AtomikosSQLException.throwAtomikosSQLException ( msg );
			} else {
				String msg = "Connection was already closed - calling " + methodName + " is no longer allowed!";
				AtomikosSQLException.throwAtomikosSQLException ( msg );
			}
			return null;
		}


		// disallow local TX methods in global TX context
		if (isEnlistedInGlobalTransaction()) {
	        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")) {
	        	return Boolean.FALSE;
	        }
		}

		// check for enlistment
        if (ENLISTMENT_METHODS.contains(methodName)) {
        	try {
        		enlist();
        	} catch ( Exception e ) {
        		//fix for bug 25678
        		sessionHandleState.notifySessionErrorOccurred();
        		JdbcConnectionProxyHelper.convertProxyError ( e , "Error enlisting in transaction - connection might be broken? Please check the logs for more information..." );
        	}
        }

        Object ret = null;

        // check for delistment
        if (CLOSE_METHODS.contains(methodName) && args == null ) {
        	//check for args needed by case 24683
 			close();
 			return null;
 		}
		else {
			try {
				if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": calling " + formatCallDetails(method,args) + "...");
				ret = method.invoke(delegate, args);

			} catch (Exception ex) {
				sessionHandleState.notifySessionErrorOccurred();
				JdbcConnectionProxyHelper.convertProxyError ( ex , "Error delegating '" + methodName + "' call" );
			}
		}
        if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": " + methodName + " returning " + ret );
        if ( ret instanceof Statement ) {
        	//keep statement for closing upon timeout/close
        	//see bug 29708
        	Statement s = ( Statement ) ret;
        	addStatement ( s );
        }
        return ret;
	}


	private boolean methodAllowedAfterClose(Method method) {
		return method.getName().equals("close") || ClassLoadingHelper.existsInJavaObjectClass(method);
	}

	private String formatCallDetails(Method method, Object[] args) {
		StringBuffer ret = new StringBuffer();
		ret.append(method.getName());
		if (args != null && args.length>0) {
			ret.append("(");
			for (int i = 0; i < args.length; i++) {
				ret.append(args[i].toString());
				if (i < args.length-1) ret.append(",");
			}
			ret.append(")");
		}
		return ret.toString();
	}

	private void reap() {
		LOGGER.logWarning ( this + ": reaping - check if the application closes connections correctly, or increase the reapTimeout value");
		close();
		//added for 22101
		sessionHandleState.notifySessionErrorOccurred();
		reaped = true;
	}

	private CompositeTransactionManager getCompositeTransactionManager() {
		CompositeTransactionManager ret = Configuration.getCompositeTransactionManager();
		if ( ret == null ) LOGGER.logWarning ( this + ": WARNING: transaction manager not running?" );
		return ret;
	}
	

	/**
	 * Enlist if necessary
	 * @return True if a JTA transaction was found, false otherwise.
	 *
	 * @throws AtomikosSQLException
	 */
	private boolean enlist() throws AtomikosSQLException {
		boolean ret = false;
		try {
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace( this + ": notifyBeforeUse " + sessionHandleState);
			CompositeTransaction ct = null;
			CompositeTransactionManager ctm = getCompositeTransactionManager();
			if ( ctm != null ) {
				ct = ctm.getCompositeTransaction();
				//first notify the session handle - see case 27857
				sessionHandleState.notifyBeforeUse ( ct );
				if (ct != null && ct.getProperty ( TransactionManagerImp.JTA_PROPERTY_NAME ) != null ) {
					ret = true;
					if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": detected transaction " + ct );
					if ( ct.getState().equals(TxState.ACTIVE) ) ct.registerSynchronization(new JdbcRequeueSynchronization( this , ct ));
					else AtomikosSQLException.throwAtomikosSQLException("The transaction has timed out - try increasing the timeout if needed");
				}
			}

		} catch (InvalidSessionHandleStateException ex) {
			AtomikosSQLException.throwAtomikosSQLException ( ex.getMessage() , ex);
		}
		return ret;
	}

	private void close() {
		if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": close()...");
		forceCloseAllPendingStatements ( false );
		closed = true;
		sessionHandleState.notifySessionClosed();
		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": closed." );
	}

	private boolean isEnlistedInGlobalTransaction()
	{
		CompositeTransactionManager compositeTransactionManager = getCompositeTransactionManager();
		if (compositeTransactionManager == null) {
			return false; // TM is not running, we can only be in local TX mode
		}
		CompositeTransaction ct = compositeTransactionManager.getCompositeTransaction();
		return sessionHandleState.isActiveInTransaction ( ct );
	}
	
	

	public static Reapable newInstance ( Connection c , SessionHandleState sessionHandleState )
	{
		Reapable ret = null;
        AtomikosConnectionProxy proxy = new AtomikosConnectionProxy(c, sessionHandleState );
        Set> interfaces = PropertyUtils.getAllImplementedInterfaces ( c.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 ( c.getClass().getClassLoader() );
		classLoaders.add ( AtomikosConnectionProxy.class.getClassLoader() );

		ret = ( Reapable ) ClassLoadingHelper.newProxyInstance ( classLoaders , MINIMUM_SET_OF_INTERFACES , interfaceClasses , proxy );

        return ret;
    }

	private class JdbcRequeueSynchronization implements Synchronization {
		private static final long serialVersionUID = 1L;

		private CompositeTransaction compositeTransaction;
		private AbstractConnectionProxy proxy;
		private boolean afterCompletionDone;

		public JdbcRequeueSynchronization ( AbstractConnectionProxy proxy , CompositeTransaction compositeTransaction) {
			this.compositeTransaction = compositeTransaction;
			this.proxy = proxy;
			this.afterCompletionDone = false;
		}

		public void afterCompletion(TxState state) {

			if ( afterCompletionDone ) return;


			if ( state.equals ( TxState.ABORTING ) ) {
				//see bug 29708: close all pending statements to avoid reuse outside timed-out tx scope
				forceCloseAllPendingStatements ( true );
			}

			if ( state.equals ( TxState.TERMINATED ) || state.isHeuristic()) {

	            // connection is reusable!
				if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace(  proxy + ": detected termination of transaction " + compositeTransaction );
				sessionHandleState.notifyTransactionTerminated(compositeTransaction);

	            afterCompletionDone = true;

	            // see case 73007 and 84252
	            forceCloseAllPendingStatements ( false );
	        }


		}

		public void beforeCompletion() {
		}

		//override equals: synchronizations for the same tx are equal
		//to avoid receiving double notifications on termination!
		public boolean equals ( Object other )
		{
			boolean ret = false;
			if ( other instanceof JdbcRequeueSynchronization ) {
				JdbcRequeueSynchronization o = ( JdbcRequeueSynchronization ) other;
				ret = this.compositeTransaction.isSameTransaction ( o.compositeTransaction );
			}
		    return ret;
		}

		public int hashCode()
		{
			return compositeTransaction.hashCode();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy