/** * Copyright (C) 2000-2016 Atomikos <info@atomikos.com> * * LICENSE CONDITIONS * * See http://www.atomikos.com/Main/WhichLicenseApplies for details. */ package com.atomikos.jms; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import javax.jms.JMSException; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TopicSubscriber; import javax.jms.XASession; import com.atomikos.beans.PropertyUtils; import com.atomikos.datasource.pool.Reapable; import com.atomikos.datasource.xa.XATransactionalResource; import com.atomikos.datasource.xa.session.SessionHandleState; import com.atomikos.datasource.xa.session.SessionHandleStateChangeListener; import com.atomikos.icatch.CompositeTransaction; import com.atomikos.logging.Logger; import com.atomikos.logging.LoggerFactory; import com.atomikos.util.ClassLoadingHelper; import com.atomikos.util.DynamicProxy; class AtomikosJmsXaSessionProxy extends AbstractJmsSessionProxy implements SessionHandleStateChangeListener { private static final Logger LOGGER = LoggerFactory.createLogger(AtomikosJmsXaSessionProxy.class); private final static List<String> PRODUCER_CONSUMER_METHODS = Arrays.asList("createConsumer", "createProducer","createDurableSubscriber"); private final static List<String> SESSION_TRANSACTION_METHODS = Arrays.asList("commit", "rollback"); private static Class<?>[] MINIMUM_SET_OF_INTERFACES = {Reapable.class, DynamicProxy.class, javax.jms.Session.class }; private final static String CLOSE_METHOD = "close"; public static Object newInstance ( XASession s , XATransactionalResource jmsTransactionalResource , SessionHandleStateChangeListener pooledConnection , SessionHandleStateChangeListener connectionProxy ) throws JMSException { AtomikosJmsXaSessionProxy proxy = new AtomikosJmsXaSessionProxy ( s , jmsTransactionalResource , pooledConnection , connectionProxy ); Set<Class<?>> interfaces = PropertyUtils.getAllImplementedInterfaces ( s.getClass() ); //see case 24532 interfaces.add ( DynamicProxy.class ); Class<?>[] interfaceClasses = ( Class[] ) interfaces.toArray ( new Class[0] ); List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(); classLoaders.add ( Thread.currentThread().getContextClassLoader() ); classLoaders.add ( s.getClass().getClassLoader() ); classLoaders.add ( AtomikosJmsXaSessionProxy.class.getClassLoader() ); return ( Session ) ClassLoadingHelper.newProxyInstance ( classLoaders , MINIMUM_SET_OF_INTERFACES , interfaceClasses , proxy ); } private XASession delegate; private boolean closed = false; private SessionHandleState state; private XATransactionalResource jmsTransactionalResource; private AtomikosJmsXaSessionProxy ( XASession s , XATransactionalResource jmsTransactionalResource , SessionHandleStateChangeListener pooledConnection , SessionHandleStateChangeListener connectionProxy ) { this.delegate = s; this.jmsTransactionalResource = jmsTransactionalResource; this.state = new SessionHandleState ( jmsTransactionalResource , s.getXAResource() ); state.registerSessionHandleStateChangeListener ( pooledConnection ); state.registerSessionHandleStateChangeListener ( connectionProxy ); state.registerSessionHandleStateChangeListener ( this ); //for JMS, session borrowed corresponds to creation of the session state.notifySessionBorrowed(); } public Object invoke ( Object proxy, Method method, Object[] args ) throws JMSException { String methodName = method.getName(); //see case 24532 if ( methodName.equals ( "getInvocationHandler" ) ) return this; synchronized (this) { if (closed) { if (!methodName.equals(CLOSE_METHOD)) { String msg = "Session was closed already - calling " + methodName + " is no longer allowed."; LOGGER.logWarning ( this + ": " + msg ); throw new javax.jms.IllegalStateException( msg ); } return null; } if ( SESSION_TRANSACTION_METHODS.contains ( methodName ) ) { String msg = "Calling commit/rollback is not allowed on a managed session!"; // When using the Spring PlatformTransactionManager, there is always a call to commit on the Session in a synchronization's afterCompletion. // The PlatformTransactionManager uses that mechanism for non-JTA TX commit which happens because DefaultMessageListenerContainer.sessionTransacted // must be set to true. Spring catches TransactionInProgressException in case of JTA TX management. // This is fine except that we used to log this message at warning level when this happens which is annoying as it repeats once per TX -> lowered it to info. // // See: org.springframework.jms.connection.ConnectionFactoryUtils$JmsResourceSynchronization.afterCommit() // and org.springframework.jms.connection.JmsResourceHolder.commitAll() (as of Spring 2.0.8) if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": " + msg ); throw new javax.jms.TransactionInProgressException ( msg ); } if ( CLOSE_METHOD.equals ( methodName ) ) { state.notifySessionClosed(); if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": closing session " + this + " - is terminated ? " + state.isTerminated() ); if ( state.isTerminated() ) { //only destroy if there is no pending 2PC - otherwise this is done //in the registered synchronization destroy ( true ); } else { //close this handle but keep vendor session open for 2PC //see case 71079 destroy ( false ); } //see case 71079: return here to avoid delegating to vendor session return null; } if (PRODUCER_CONSUMER_METHODS.contains(methodName)) { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": calling " + methodName + " on JMS driver session " + delegate ); Object producerConsumerProxy = null; if ( "createConsumer".equals ( methodName ) ) { MessageConsumer vendorConsumer = null; try { vendorConsumer = ( MessageConsumer ) method.invoke ( delegate , args); } catch ( Exception e ) { String msg = "Failed to create MessageConsumer: " + e.getMessage(); state.notifySessionErrorOccurred(); convertProxyError ( e , msg ); } producerConsumerProxy = new AtomikosJmsMessageConsumerProxy ( vendorConsumer , state ); } else if ( "createProducer".equals( methodName ) ) { MessageProducer vendorProducer = null; try { vendorProducer = ( MessageProducer ) method.invoke ( delegate , args); } catch ( Exception e ) { String msg = "Failed to create MessageProducer: " + e.getMessage(); state.notifySessionErrorOccurred(); convertProxyError ( e , msg ); } producerConsumerProxy = new AtomikosJmsMessageProducerProxy ( vendorProducer , state ); } else if ( "createDurableSubscriber".equals ( methodName ) ) { TopicSubscriber vendorSubscriber = null; try { vendorSubscriber = ( TopicSubscriber ) method.invoke ( delegate , args); } catch ( Exception e ) { String msg = "Failed to create durable TopicSubscriber: " + e.getMessage(); state.notifySessionErrorOccurred(); convertProxyError ( e , msg ); } producerConsumerProxy = new AtomikosJmsTopicSubscriberProxy ( vendorSubscriber , state ); } if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": " + methodName + " returning " + producerConsumerProxy ); return producerConsumerProxy; } try { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": calling " + methodName + " on JMS driver session..." ); Object ret = method.invoke(delegate, args); if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": " + methodName + " returning " + ret ); return ret; } catch (Exception ex) { String msg = "Error delegating call to " + methodName + " on JMS driver"; state.notifySessionErrorOccurred(); convertProxyError ( ex , msg ); } } // synchronized (this) //dummy return to keep compiler happy return null; } protected void destroy ( boolean closeXaSession ) { if ( closeXaSession ) { //see case 71079: don't close vendor session if transaction is not done yet if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": closing underlying vendor session " + this ); try { delegate.close(); } catch ( JMSException e ) { LOGGER.logWarning ( this + ": could not close underlying vendor session" , e ); } } closed = true; } protected boolean isAvailable() { boolean ret = false; if ( state != null ) ret = state.isTerminated(); return ret; } protected boolean isErroneous() { boolean ret = false; if ( state != null ) ret = state.isErroneous(); return ret; } protected boolean isInTransaction ( CompositeTransaction ct ) { boolean ret = false; if ( state != null ) ret = state.isActiveInTransaction ( ct ); return ret; } protected boolean isInactiveTransaction ( CompositeTransaction ct ) { boolean ret = false; if ( state != null ) ret = state.isInactiveInTransaction ( ct ); return ret; } public void onTerminated() { destroy ( true ); } public String toString() { return "atomikos xa session proxy for resource " + jmsTransactionalResource.getName(); } }