Edit | Attach | New | Raw | Delete | History | Print | Tools

Tomcat 6 Integration with Atomikos 3.5.2+ with lifecycle support

It is possible to fully integrate the Atomikos transaction manager into Tomcat. Doing it this way makes the transaction manager shared across all web applications exactly like with any full-blown J2EE server. Additional efforts have to be made if the resource are configured using JNDI. Tomcat's JNDI does not include a lifecycle management. Therefore it has to be implemented.

A feature like this is needed to be able to start and stop a webapp without running into duplicate resource exceptions.

This implementation is also webapp aware. This is useful to access the same JNDI resource from more than one webapp.

Important note

When the Atomikos transaction manager is installed globally in Tomcat, you now must also install your JDBC driver at the same global location (ie: into the TOMCAT_HOME/lib folder). If you dont do that, you will get a NoClassDefFoundErrors or a ClassNotFoundException or even a ClassCastException during your web application deployment.

This is not a limitation of Atomikos nor of Tomcat but of the J2EE class loading design that both Tomcat and Atomikos must follow.

Installation

Installation is quite simple, it just involves compling source code into JAR files and copying them, create a property file and editing some Tomcat configuration files.

Create server lifecycle listener

The LifecycleListener has to be changed since release 3.5.2. The first class which calls UserTransactionManager.init() is the master for UserTransactionManager. It is not the first class which calls new UserTransactionManager(). Only the master closes UserTransactionManager with its close() method. Therefore UserTransactionManager.init() has to be called after the new operator. The master UserTransactionManager has be created by a lifecycle listener to have the manager available independent of the availability of webapps.

AtomikosLifecycleListener.java

package com.atomikos.tomcat;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;

import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.icatch.system.Configuration;

public class AtomikosLifecycleListener implements LifecycleListener
{
   private UserTransactionManager utm;

   public void lifecycleEvent(LifecycleEvent event)
   {
      try {
         if (Lifecycle.START_EVENT.equals(event.getType())) {
            if (utm == null) {
               utm = new UserTransactionManager();
            }
               utm.init();
         } else if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) {
            if (utm != null) {
               utm.close();
            }
         }
      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }
}

Create context lifecycle listener

Here starts the the lifecycle management. This listener informs the lifecycle manager when a webapp starts or stops.

ContextLifecycleListener.java

package com.atomikos.tomcat;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;

public class ContextLifecycleListener implements LifecycleListener
{
   private String webappName = null;

   public void setWebappName(String name)
   {
      webappName = name;
   }

   public void lifecycleEvent(LifecycleEvent event)
   {
      try {
         if (Lifecycle.START_EVENT.equals(event.getType())) {
            AtomikosLifecycleManager.getInstance().startWebApp(webappName);
         } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
            AtomikosLifecycleManager.getInstance().stopWebApp(webappName);
         }
      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }
}

Create lifecycle manager

This manager maintains a list of Atomikos resources for every webapp. Atomikos resources of a named webapp are closed on request from context lifecycle listener.

AtomikosLifecycleManager.java

package com.atomikos.tomcat;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.atomikos.jms.AtomikosConnectionFactoryBean;

public class AtomikosLifecycleManager
{
   private static AtomikosLifecycleManager manager = null;

   private String webappName = null;
   final private ConcurrentMap<String, List<Object>> atomikosObjectMap = new ConcurrentHashMap<String, List<Object>>();

   private AtomikosLifecycleManager()
   {

   }

   public synchronized static AtomikosLifecycleManager getInstance()
   {
      if (manager == null) {
         manager = new AtomikosLifecycleManager();
      }

      return (manager);
   }

   public void startWebApp(String name)
   {
      webappName = name;

      atomikosObjectMap.put(name, new ArrayList<Object>());
   }

   public String getWebappName()
   {
      return (webappName);
   }

   public void addResource(Object obj)
   {
      if (atomikosObjectMap.containsKey(webappName)) {
         atomikosObjectMap.get(webappName).add(obj);
      }
   }

   public void stopWebApp(String name)
   {
      if (atomikosObjectMap.containsKey(name)) {
         List<Object> list = atomikosObjectMap.get(name);
         for (Object obj : list) {
            if (obj instanceof AtomikosConnectionFactoryBean) {
               ((AtomikosConnectionFactoryBean) obj).close();
            } else if (obj instanceof AtomikosDataSourceBean) {
               ((AtomikosDataSourceBean) obj).close();
            }
         }
         list.clear();
      }
   }
}

Create JNDI factory for Atomikos

JNDI factory to create connection and data source beans. The beans are initialized after creation.

AtomikosTomcatFactoryFactory.java

package com.atomikos.tomcat;

import java.util.Enumeration;
import java.util.Hashtable;

import javax.jms.JMSException;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import org.apache.naming.ResourceRef;

import com.atomikos.beans.PropertyException;
import com.atomikos.beans.PropertyUtils;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.atomikos.jdbc.AtomikosSQLException;
import com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean;
import com.atomikos.jms.AtomikosConnectionFactoryBean;

public class AtomikosTomcatFactoryFactory implements ObjectFactory
{
   public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception
   {
      if (obj instanceof ResourceRef) {
         try {
            Reference ref = (Reference) obj;
            String beanClassName = ref.getClassName();
            Class<?> beanClass = null;
            ClassLoader tcl = Thread.currentThread().getContextClassLoader();

            if (tcl != null) {
               try {
                  beanClass = tcl.loadClass(beanClassName);
               } catch (ClassNotFoundException e) {
                  throw new NamingException("Could not load class " + beanClassName);
               }
            } else {
               try {
                  beanClass = Class.forName(beanClassName);
               } catch (ClassNotFoundException e) {
                  throw new NamingException("Could not load class " + beanClassName);
               }
            }

            if (beanClass == null) {
               throw new NamingException("Class not found: " + beanClassName);
            }

            if (AtomikosDataSourceBean.class.isAssignableFrom(beanClass)) {
               return createDataSourceBean(ref, (AtomikosDataSourceBean) beanClass.newInstance());
            } else if (AtomikosNonXADataSourceBean.class.isAssignableFrom(beanClass)) {
               return createNonXADataSourceBean(ref, (AtomikosNonXADataSourceBean) beanClass.newInstance());
            } else if (AtomikosConnectionFactoryBean.class.isAssignableFrom(beanClass)) {
               return createConnectionFactoryBean(ref, (AtomikosConnectionFactoryBean) beanClass.newInstance());
            } else {
               throw new NamingException(
                     "Class is neither an AtomikosDataSourceBean nor an AtomikosConnectionFactoryBean: "
                           + beanClassName);
            }

         } catch (InstantiationException e) {
            throw (NamingException) new NamingException(
                  "error creating AtomikosDataSourceBean or AtomikosConnectionFactoryBean").initCause(e);
         } catch (IllegalAccessException e) {
            throw (NamingException) new NamingException(
                  "error creating AtomikosDataSourceBean or AtomikosConnectionFactoryBean").initCause(e);
         } catch (JMSException e) {
            throw (NamingException) new NamingException("error creating AtomikosConnectionFactoryBean").initCause(e);
         } catch (AtomikosSQLException e) {
            throw (NamingException) new NamingException("error creating AtomikosDataSourceBean").initCause(e);
         } catch (PropertyException e) {
            throw (NamingException) new NamingException(
                  "error creating AtomikosDataSourceBean or AtomikosConnectionFactoryBean").initCause(e);
         }
      }

      return (null);
   }

   private Object createConnectionFactoryBean(Reference ref, AtomikosConnectionFactoryBean bean) throws JMSException
   {
      Enumeration<RefAddr> en = ref.getAll();

      int i = 0;
      while (en.hasMoreElements()) {
         RefAddr ra = (RefAddr) en.nextElement();
         String propName = ra.getType();

         String value = (String) ra.getContent();

         /**
          * uniqueResourceName is only unique per webapp but has to made
          * unique globally.
          */
         if (propName.equals("uniqueResourceName")) {
            value = AtomikosLifecycleManager.getInstance().getWebappName() + "/" + value;
         }

         try {
            PropertyUtils.setProperty(bean, propName, value);
            i++;
         } catch (PropertyException pe) {
            System.out.println("Property " + propName + "could not be set. " + pe.getMessage());
         }

      }

      bean.init();
      AtomikosLifecycleManager.getInstance().addResource(bean);
      return (bean);
   }

   private Object createNonXADataSourceBean(Reference ref, AtomikosNonXADataSourceBean bean) throws AtomikosSQLException, PropertyException
   {
      if (logger.isDebugEnabled()) {
         logger.debug("instanciating bean of class " + bean.getClass().getName());
      }

      Enumeration<RefAddr> en = ref.getAll();

      int i = 0;
      while (en.hasMoreElements()) {
         RefAddr ra = (RefAddr) en.nextElement();
         String propName = ra.getType();

         String value = (String) ra.getContent();

         /**
          * uniqueResourceName is only unique per webapp but has to made
          * unique globally.
          */
         if (propName.equals("uniqueResourceName")) {
            value = AtomikosLifecycleManager.getInstance().getWebappName() + "/" + value;
         }

         if (logger.isDebugEnabled()) {
            logger.debug("setting property '" + propName + "' to '" + value + "'");
         }

         try {
            PropertyUtils.setProperty(bean, propName, value);
            i++;
         } catch (PropertyException pe) {
            System.out.println("Property " + propName + "could not be set. " + pe.getMessage());
         }
      }

      bean.init();
      AtomikosLifecycleManager.getInstance().addResource(bean);
      return (bean);
   }

   private Object createDataSourceBean(Reference ref, AtomikosDataSourceBean bean) throws AtomikosSQLException
   {
      if (logger.isDebugEnabled()) {
         logger.debug("instanciating bean of class " + bean.getClass().getName());
      }

      Enumeration<RefAddr> en = ref.getAll();

      int i = 0;

      while (en.hasMoreElements()) {
         RefAddr ra = (RefAddr) en.nextElement();
         String propName = ra.getType();

         String value = (String) ra.getContent();

         /**
          * uniqueResourceName is only unique per webapp but has to made
          * unique globally.
          */
         if (propName.equals("uniqueResourceName")) {
            value = AtomikosLifecycleManager.getInstance().getWebappName() + "/" + value;
         }

         if (logger.isDebugEnabled()) {
            logger.debug("setting property '" + propName + "' to '" + value + "'");
         }

         try {
            PropertyUtils.setProperty(bean, propName, value);
            i++;
         } catch (PropertyException pe) {
            System.out.println("Property " + propName + "could not be set. " + pe.getMessage());
         }

      }

      bean.init();
      AtomikosLifecycleManager.getInstance().addResource(bean);
      return (bean);
   }
}

Compile and copying the extensions

  • Compile the source code into a library
  • Copy the library to TOMCAT_HOME/lib

Copying TransactionsEssentials libraries

  • Drop the following JARs from the Atomikos distribution to TOMCAT_HOME/lib folder:
    • transactions.jar
    • transactions-api.jar
    • transactions-jta.jar
    • transactions-jdbc.jar
    • atomikos-util.jar.jar
    • jta.jar

You should also copy the transactions-hibernate3.jar and/or transactions-hibernate2.jar at the same location if you're planning to use Hibernate.

Copying Atomikos configuration file

  • Drop the following properties file into the TOMCAT_HOME/lib folder: jta.properties

Edit server.xml

Then edit the TOMCAT_HOME/conf/server.xml file. At the beginning of the file you should see these four lines:

  <Listener className="org.apache.catalina.core.AprLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener"/>

Right after the last one, add this fifth one:

 <Listener className="com.atomikos.tomcat.AtomikosLifecycleListener" />

Edit global context.xml

Then edit the TOMCAT_HOME/conf/context.xml file. At the beginning of the file you should see this line:

 <WatchedResource>WEB-INF/web.xml</WatchedResource>

Right after it, add that one:

 <Transaction factory="com.atomikos.icatch.jta.UserTransactionFactory" />

Edit webapp context.xml

Then edit META-INF/context.xml from a war file. Add at the beginning of section Context.

<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Listener className="com.atomikos.tomcat.ContextLifecycleListener" webappName="test" />
...
</Context>

Using MySQL

Notice: testQuery is very important for connection management.

<?xml version="1.0" encoding="UTF-8"?>
<Context reloadable="true" crossContext="true">
<Resource
name="jdbc/myDB"
auth="Container"
type="com.atomikos.jdbc.AtomikosDataSourceBean"
factory="com.atomikos.tomcat.AtomikosTomcatFactoryFactory"
uniqueResourceName="jdbc/myDB"
xaDataSourceClassName="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"
xaProperties.databaseName="test"
xaProperties.serverName="localhost"
xaProperties.port="3306"
xaProperties.user="USER"
xaProperties.password="PASSWORD"
xaProperties.url="jdbc:mysql://localhost:3306/test"
testQuery="select 1" />
</Context>

Remember to change the parameter values to your specific environment...

Using WebSphere MQ

MQ client must operate in bind mode to be able use MQ in global transactions.

<?xml version="1.0" encoding="UTF-8"?>
<Context reloadable="true" crossContext="true">

<Resource 
name="jms/myQCF" 
auth="Container" 
type="com.atomikos.jms.AtomikosConnectionFactoryBean" 
factory="com.atomikos.tomcat.AtomikosTomcatFactoryFactory" 
uniqueResourceName="QCF_MQSeries_XA" 
xaConnectionFactoryClassName="com.ibm.mq.jms.MQXAQueueConnectionFactory" 
xaProperties.queueManager="XXX" 
maxPoolSize="3" 
minPoolSize="1" />

<Resource name="jms/myQ" 
auth="Container" 
type="com.ibm.mq.jms.MQQueue" 
factory="com.ibm.mq.jms.MQQueueFactory" 
description="JMS Queue for reading messages" 
QU="MYQ.IN" />
</Context>
spacer
Copyright © 2014 Atomikos BVBA. Transaction Management for Extreme Transaction Processing and SOA Environments serving ISV, Commercial, OEM and Open Source Markets
Site map RSS ATOM