com.atomikos.jdbc.AtomikosConnectionProxy Maven / Gradle / Ivy
/**
* 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();
}
}
}