Version 1

    We had a scenario where we are not using an application server like JBoss (WildFly), WebSphere, Welogic etc. We create web application archive and deploy them in a basic servlet container like Tomcat, Jetty etc. In our case we use Tomcat 7.x. We also needed both XA as well non-XA datasource requirements since we have business cases that supports single or multiple resources. As I was browsing through the internet I did not find a decent article that showed the configurations clearly and we ran into a lot of problems until we got it finally working. In this article, I will try to articulate the configuration settings with Spring based annotations.

     

    Stack:

    Tomcat 7.x

    Spring 3.2.1

    Standalone Narayna/JBoss TS (5.0.1.Final)

    Hibernate 4.1.7 (ORM layer)

    Apache DBCP 2 (2.0.0)

    JMS Pool from ActiveMq project (5.9.0)

    HornetQ 2.2.18

    PostgresSql 9

     

    List of Maven Dependencies:

     

    I am listing only those dependencies that are transactions related. You will still need other Spring, logging Hibernate dependencies etc.

     

    Common Dependencies for both Database as well JMS transactions. This is typically created in its own platform-commons.jar that could be used in you DAL and MOM layer for transaction support.

     

    <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-tx</artifactId>
           <version>3.2.1.RELEASE</version>
    </dependency>
    <dependency>
           <groupId>org.jboss.narayana.jta</groupId>
           <artifactId>narayana-jta</artifactId>
           <version>5.0.1.Final</version>
    </dependency>
    <!--jboss-transaction-spi is needed cos of a missing transitive dependency in narayana-full.jar. This will be fixed in release 5.0.3. You will not need to include this dependency then -->
    
    <dependency>
             <groupId>org.jboss</groupId>
             <artifactId>jboss-transaction-spi</artifactId>
             <version>7.1.0.Final</version>
             <exclusions>
                     <exclusion>
                          <groupId>*</groupId>
                          <artifactId>*</artifactId>
                     </exclusion>
             </exclusions>
    </dependency>
    <dependency>
           <groupId>javax.transaction</groupId>
           <artifactId>jta</artifactId>
           <version>1.1</version>
    </dependency>
    <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-dbcp2</artifactId>
           <version>2.0.0</version>
    </dependency>
    
    
    
    
    
    
    
    

     

    Configuration in Spring to bootstrap PlatformTransactionManager and JBoss TransactionManager in platform-commons.jar

     

    TransactionConfig.java

     

    import javax.transaction.SystemException;
    import javax.transaction.TransactionManager;
    import javax.transaction.UserTransaction;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    import org.springframework.transaction.jta.JtaTransactionManager;
    
    import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
    import com.arjuna.ats.internal.jta.transaction.arjunacore.UserTransactionImple;
    
    
    @Configuration
    @EnableTransactionManagement
    public class TransactionConfig {
    
     @Value("${transaction.timeout:60}")
     private Integer transactionTimeOut;
    
     @Bean
      public PlatformTransactionManager jtaTransactionManager() {
    
      return new JtaTransactionManager(usrTransactionManager(), userTransaction());
      }
    
     @Bean
      public TransactionManager userTransaction() {
    
      TransactionManagerImple transactionManager = new TransactionManagerImple();
      try {
      transactionManager.setTransactionTimeout(transactionTimeOut);
      } catch (SystemException e) {
      throw new RunTimeException("Transaction timed out");
      }
    
      return transactionManager;
      }
    
     @Bean
      public UserTransaction usrTransactionManager() {
    
      UserTransactionImple utm = new UserTransactionImple();
    
      try {
      utm.setTransactionTimeout(transactionTimeOut);
      } catch (SystemException e) {
      throw new RunTimeException("Transaction timed out");
      }
    
      return utm;
    
      }
    }
    
    
    
    
    
    
    
    

     

    Once you have wired Spring and JBoss TS, now we will set up DAL layer with XA aware and non-xa Datasources and wire up the db connections. This is typically done in platform jar like platform-dal.jar where platform-commons from above is a dependency. In the following class, we are explicitly setting up PostgresSQL datasources that are XA and non-XA aware. All the configuration values (@Value properties) can be injected from a property file which is typical in a Spring application. Also, note that we are using Apache DBCP2 pooling for pooling our connections. Although there are some negative remarks about DBCP on the internet, we found it to work optimally especially the newer version. Other options was BoneCP but you cannot do XA pooling.

     

    PgConfig.java

     

    import javax.inject.Inject;
    import javax.sql.DataSource;
    import javax.sql.XADataSource;
    import javax.transaction.TransactionManager;
    
    import org.apache.commons.dbcp2.managed.BasicManagedDataSource;
    import org.apache.commons.dbcp2.managed.DataSourceXAConnectionFactory;
    import org.postgresql.xa.PGXADataSource;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    
    @Configuration
    public class PgConfig {
    
     // This value not to be provided by applications using this module and will always default to using the provided PostgreSQL JDBC driver.
     private String driverClass = "org.postgresql.Driver";
    
     @Value("${pg.jdbc.url}")
      private String jdbcUrl;
    
     @Value("${pg.username}")
      private String userName;
    
     @Value("${pg.password}")
      private String password;
    
     @Value("${pg.min.pool.size:1}")
      private Integer minPoolSize;
    
     @Value("${pg.max.pool.size:5}")
      private Integer maxPoolSize;
    
     @Value("${pg.login.time.out:60}")
     private Integer loginTimeOut;
    
     @Value("${pg.database.name}")
     private String databaseName;
    
     @Value("${pg.server.name}")
      private String serverName;
    
     @Value("${pg.server.port}")
      private Integer portNumber;
    
     @Value("${application.name}")
     private String applicationName;
    
     @Inject
      private TransactionManager transactionManager;
    
      @Bean(destroyMethod = "close")
      public DataSource pgNonXaDataSource() {
    
      BasicManagedDataSource dataSource = new BasicManagedDataSource();
      dataSource.setDriverClassName(driverClass);
      dataSource.setUrl(jdbcUrl);
      dataSource.setUsername(userName);
      dataSource.setPassword(password);
      dataSource.setInitialSize(minPoolSize);
      dataSource.setMaxTotal(maxPoolSize);
      dataSource.setTransactionManager(transactionManager);
    
      return dataSource;
      }
    
     @Bean(name = "pgXAConnectionFactory")
      public DataSourceXAConnectionFactory xaConnectionFactory() {
    
      return new DataSourceXAConnectionFactory(transactionManager, pgXaDataSourceProperties());
      }
    
     @Bean
      public DataSource pgXaDataSource() {
    
      BasicManagedDataSource dataSource = new BasicManagedDataSource();
      dataSource.setDriverClassName(driverClass);
      dataSource.setUrl(jdbcUrl);
      dataSource.setUsername(userName);
      dataSource.setPassword(password);
      dataSource.setInitialSize(minPoolSize);
      dataSource.setMaxTotal(maxPoolSize);
      dataSource.setTransactionManager(transactionManager);
      dataSource.setXaDataSourceInstance(pgXaDataSourceProperties());
    
      return dataSource;
      }
    
      private XADataSource pgXaDataSourceProperties() {
    
      PGXADataSource pgxaDataSource = new PGXADataSource();
      pgxaDataSource.setServerName(serverName);
      pgxaDataSource.setPortNumber(portNumber);
      pgxaDataSource.setDatabaseName(databaseName);
      pgxaDataSource.setUser(userName);
      pgxaDataSource.setPassword(password);
      pgxaDataSource.setApplicationName(applicationName);
      return pgxaDataSource;
      }
    }
    
    
    
    
    
    
    

     

    Now, we need to wire up Spring's EntityManagerFactoryBean with the xa and non-xa DataSources we defined earlier:

     

    PgJTAConfig.java

     

    import java.util.Properties;
    
    import javax.inject.Inject;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    @Configuration
    @EnableTransactionManagement
    public class PgJtaConfig {
    
     @Value("${hibernate.show.sql:false}")
     private Boolean hibernate_show_sql;
    
     @Value("${hibernate.format.sql:false}")
     private Boolean hibernate_format_sql;
    
     @Inject
      private PgConfig pgConfig;
    
     @Inject
      private SpringDalConfig springDalConfig;
    
     @Bean
      public LocalContainerEntityManagerFactoryBean pgJtaXaEntityManagerFactory() {
    
      LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
      localContainerEntityManagerFactoryBean.setJtaDataSource(pgConfig.pgXaDataSource());
      localContainerEntityManagerFactoryBean.setJpaDialect(hibernateJpaDialect());
      localContainerEntityManagerFactoryBean.setJpaProperties(setJpaProperties());
      localContainerEntityManagerFactoryBean.setPersistenceUnitName("pg-xa");
      return localContainerEntityManagerFactoryBean;
      }
    
     @Bean
      public LocalContainerEntityManagerFactoryBean pgJtaNonXaEntityManagerFactory() {
    
      LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
      localContainerEntityManagerFactoryBean.setJtaDataSource(pgConfig.pgNonXaDataSource());
      localContainerEntityManagerFactoryBean.setJpaDialect(hibernateJpaDialect());
      localContainerEntityManagerFactoryBean.setJpaProperties(setJpaProperties());
      localContainerEntityManagerFactoryBean.setPersistenceUnitName("pg-non-xa");
      return localContainerEntityManagerFactoryBean;
      }
    
    private HibernateJpaDialect hibernateJpaDialect() {
    
      return new HibernateJpaDialect();
      }
    
      private Properties setJpaProperties() {
    
      Properties props = new Properties();
      props.setProperty("hibernate.transaction.manager_lookup_class", JBossTransactionManagerLookup.class.getName());
      props.put("hibernate.show_sql", hibernate_show_sql);
      props.put("hibernate.format_sql", hibernate_format_sql);
      return props;
      }
    }
    
    
    
    
    
    
    

     

    We will need one another class that will hookup JBoss TS and Hibernate which is referred in the above setJpaProperties() method, namely JBossTransactionManagerLookup

     

    JBossTransactionManagerLookup.java

     

    import java.util.Properties;
    
    import javax.transaction.Transaction;
    import javax.transaction.TransactionManager;
    
    import org.hibernate.HibernateException;
    import org.hibernate.transaction.TransactionManagerLookup;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class JBossTransactionManagerLookup implements TransactionManagerLookup {
    
     private static final TransactionManager TRANSACTION_MANAGER_INSTANCE;
    
     static {
      
      TRANSACTION_MANAGER_INSTANCE = com.arjuna.ats.jta.TransactionManager.transactionManager();
      if (null == TRANSACTION_MANAGER_INSTANCE) {
      throw new HibernateException("Could not obtain arjuna Transaction Manager instance.");
      }
      }
    
      public TransactionManager getTransactionManager(Properties props) {
    
      return TRANSACTION_MANAGER_INSTANCE;
      }
    
      public String getUserTransactionName() {
    
      return null;
      }
    
      public Object getTransactionIdentifier(Transaction transaction) {
    
      return transaction;
      }
    }
    
    
    
    
    
    
    

     

    Now, your persistence.xml would look something like this:

     

    <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
    
     <persistence-unit name="pg-xa" transaction-type="JTA">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
    
      <exclude-unlisted-classes>false</exclude-unlisted-classes>
      <properties>
                <property name="hibernate.archive.autodetection" value="class,hbm"/>
                <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
      </properties>
     </persistence-unit>
     <persistence-unit name="pg-non-xa" transaction-type="JTA">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <exclude-unlisted-classes>false</exclude-unlisted-classes>
      <properties>
           <property name="hibernate.archive.autodetection" value="class,hbm"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
      </properties>
     </persistence-unit>
    </persistence>
    
    
    
    
    
    
    

     

    Thats all to the DAL layer. Now to the MOM layer, where you want to hook up JBoss Transaction manager with your JMS provider. In our case it is Hornet Q. We used ActiveMq's JMS pooling for our XA pooling of JMS connections. The classes enlisted are typically in a platform-mom.jar that has platform-commons.jar as a dependency from above.

     

    HornetQBase.java

     

    import java.util.List;
    import java.util.Map;
    
    import javax.annotation.PostConstruct;
    
    import org.hornetq.api.core.TransportConfiguration;
    import org.hornetq.core.remoting.impl.netty.NettyConnectorFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import com.google.common.base.Splitter;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    
    @Component
    public class HornetQBase {
    
    
     @Value("${hornetq.server.url}")
     private String hornetQServerUrls;
    
    
     @Value("${hornetq.port}")
     private String hornetQPort;
    
      private List<String> transportServers = Lists.newArrayList();
    
     @PostConstruct
      void initializeTransportServers() {
    
      Splitter splitter = Splitter.on(DELEMITER_COMMA).omitEmptyStrings().trimResults();
    
      for (String url : splitter.split(hornetQServerUrls)) {
      transportServers.add(url);
      }
      }
    
      TransportConfiguration[] transportConfiguration() {
    
      List<TransportConfiguration> transportConfigurations = Lists.newArrayList();
    
      for (String url : transportServers) {
    
      Map<String, Object> map = Maps.newHashMap();
      map.put(MESSAGING_HOST, url);
      map.put(MESSAGING_PORT, hornetQPort);
    
      TransportConfiguration transportConfiguration = new TransportConfiguration(NettyConnectorFactory.class.getName(), map);
      transportConfigurations.add(transportConfiguration);
      }
    
      return transportConfigurations.toArray(new TransportConfiguration[transportConfigurations.size()]);
      }
    }
    
    
    
    
    

     

     

    JmsHornetQXAConfig.java

     

    import javax.inject.Inject;
    import javax.jms.ConnectionFactory;
    import javax.transaction.TransactionManager;
    
    import org.apache.activemq.jms.pool.XaPooledConnectionFactory;
    import org.apache.camel.component.jms.JmsComponent;
    import org.apache.camel.component.jms.JmsConfiguration;
    import org.hornetq.api.core.TransportConfiguration;
    import org.hornetq.jms.client.HornetQXAConnectionFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    import org.springframework.jms.core.JmsTemplate;
    import org.springframework.transaction.PlatformTransactionManager;
    
    @Configuration
    public class JmsHornetQXaConfig {
    
     @Inject
      private HornetQBase hornetQBase;
    
     private static final String JMS_XA_RESOURCE_NAME = "jmsXaHornetQ";
    
     @Value("${hornetq.xa.minpoolsize}")
      private Integer minPoolSize;
    
     @Value("${hornetq.xa.maxpoolsize}")
      private Integer maxPoolSize;
    
     @Value("${jms.hornetq.xa.session.transacted:true}")
     private Boolean isTransacted;
    
     @Value("${jms.hornetq.xa.cache.level.name:CACHE_NONE}")
     private String cacheLevelName;
    
     @Value("${jms.hornetq.xa.acknowledgment.mode:transacted}")
     private String consumerAcknowledgementMode;
    
     @Value("${jms.hornetq.session.cache.size:10}")
     private Integer sessionCacheSize;
    
     @Value("${jms.hornetq.cache.producers:true}")
     private Boolean isCacheProducers;
    
     // This value can be overridden by the application connecting to HornetQ. If not provided, it will default to Spring's default of true.
     @Value("${jms.hornetq.cache.consumers:true}")
     private Boolean isCacheConsumers;
    
     @Value("${jms.hornetq.idletaskexecution.limit:1}")
     private Integer idleTaskExecutionLimit;
    
     @Value("${jms.hornetq.receivetimeout:1000}")
     private Integer receiveTimeout;
    
     @Value("${jms.hornetq.maxmessagepertask:1}")
     private Integer maxMessagesPerTask;
    
     @Value("${jms.hornetq.idleconsumer.limit:1}")
     private Integer idleConsumerLimit;
    
     @Value("${jms.hornetq.reconnect.attempts:-1}")
     private Integer reconnectAttempts;
    
     @Value("${jms.hornetq.retry.interval:10000}")
     private Long retryInterval;
    
     @Value("${jms.hornetq.retry.interval.multiplier:1.5}")
     private Double retryIntervalMultiplier;
    
     @Value("${jms.hornetq.max.retry.interval:60000}")
     private Long maxRetryInterval;
    
     @Value("${hornetq.high.available:false}")
      private Boolean ha;
    
     @Inject
      private PlatformTransactionManager transactionManager;
    
     @Inject
      private TransactionManager jtaTransactionManager;
    
     @Bean
      public JmsComponent camelJmsHornetQXa() {
    
      JmsComponent jmsComponent = new JmsComponent();
      jmsComponent.setConfiguration(camelJmsHornetQConfigXa());
      return jmsComponent;
      }
    
      private JmsConfiguration camelJmsHornetQConfigXa() {
    
      JmsConfiguration jmsConfiguration = new JmsConfiguration();
      jmsConfiguration.setConnectionFactory(pooledHornetQConnectionFactory());
      jmsConfiguration.setTransacted(isTransacted);
      jmsConfiguration.setCacheLevelName(cacheLevelName);
      jmsConfiguration.setTransactionManager(transactionManager);
      jmsConfiguration.setAcknowledgementModeName(consumerAcknowledgementMode);
      jmsConfiguration.setIdleTaskExecutionLimit(idleTaskExecutionLimit);
      jmsConfiguration.setReceiveTimeout(receiveTimeout);
      jmsConfiguration.setIdleConsumerLimit(idleConsumerLimit);
      jmsConfiguration.setMaxMessagesPerTask(maxMessagesPerTask);
    
      return jmsConfiguration;
      }
    
     @Bean
      public JmsTemplate hornetQXaJmsTemplate() {
    
      JmsTemplate jmsTemplate = new JmsTemplate(pooledHornetQConnectionFactory());
      return jmsTemplate;
      }
    
      @Bean(destroyMethod = "stop")
      public ConnectionFactory pooledHornetQConnectionFactory() {
    
      XaPooledConnectionFactory pooledConnectionFactory = new XaPooledConnectionFactory();
      pooledConnectionFactory.setMaxConnections(maxPoolSize);
      pooledConnectionFactory.setConnectionFactory(hornetQXaConnectionFactory());
      pooledConnectionFactory.setTransactionManager(jtaTransactionManager);
      return pooledConnectionFactory;
    
      }
    
     @Bean
      public ConnectionFactory hornetQXaConnectionFactory() {
    
      TransportConfiguration[] transportConfigurations = hornetQBase.transportConfiguration();
      HornetQXAConnectionFactory xaConnectionFactory = new HornetQXAConnectionFactory(ha, transportConfigurations);
      // Specify number of times the Hornetq client should try to re connect to the server before it destroy the connection
      xaConnectionFactory.setReconnectAttempts(reconnectAttempts);
    
      // Specify long time the next retry will begin
      xaConnectionFactory.setRetryInterval(retryInterval);
    
      // Specify a multiplier to apply to the retryInterval since the last retry to compute the time to the next retry
      xaConnectionFactory.setRetryIntervalMultiplier(retryIntervalMultiplier);
    
      // Specify the max retry interval that can be
      xaConnectionFactory.setMaxRetryInterval(maxRetryInterval);
    
      return xaConnectionFactory;
      }
    }
    
    
    
    
    

     

    Few things to note above is that we are using PlatformTransactionManager for message consumption using Apache Camel. We using JmsTemplate to send message which is using the pooled connection provided by JMS pool as described above. You will have to use the TransactionManager in Pooled XA connection as it needs to coordinate the 2 phase transaction.

     

    I hope someone will find this article useful while using Standalone JBoss TS in a Spring environment.