Tomcat 6 Integration with Atomikos 3.5.2+ with lifecycle support

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 callsUserTransactionManager.init() is the master for UserTransactionManager. It is not the first class which calls newUserTransactionManager(). Only the master closes UserTransactionManager with its close() method. ThereforeUserTransactionManager.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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农丁丁

你的认可是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值