Tomcat Spring ActiveMQ MySQL JMX Integration

Tomcat Spring ActiveMQ MySQL JMX Integration

It is possible to fully integrate the Atomikos transaction manager into Tomcat. Also a JMS Provider is added to Tomcat by starting an embedded ActiveMQ server. This document describes an implementation using Spring. This includes bean lifecycle management using Spring. Therefore all resources have to be configured using Spring and not using JNDI of Tomcat. JMX resources of Atomikos are also activated using Spring. Doing it this way makes the transaction manager, JMS Provider and JMX shared across all web applications. MySQL runs as external database server. Therefore only the MySQL JDBC driver has to be added to Tomcat.

Atomikos 3.6.5

Tomcat 6.0.29

Spring Framework 2.5.6

Spring Integration 1.0.4

ActiveMQ 5.4.2

Installation

Installation is quite simple, it just involves copying some JAR files, a property file and editing some Tomcat configuration files. The Lifecycle Listeners are implemented using Spring. Both listeners have nearly the same source code, but the different Spring configuration files have to be named somewhere. This configuration starts Atomikos and an embedded ActiveMQ server. MySQL has to be installed and started separately.

AtomikosLifecycleListener

This lifecycle manager starts the Atomikos transaction manager before any web application is started and stops the transaction manager after all web applications stopped.
package com.atomikos.tomcat.spring;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AtomikosLifecycleListener implements LifecycleListener
{
   protected ClassPathXmlApplicationContext ctx = null;

   @Override
   public void lifecycleEvent(LifecycleEvent event)
   {
      if (event.getType().equals(Lifecycle.INIT_EVENT)) {
         if (ctx == null) {
            ctx = new ClassPathXmlApplicationContext(
                  "classpath*:com/atomikos/tomcat/spring/atomikos.xml");
         }
      } else if (event.getType().equals(Lifecycle.AFTER_STOP_EVENT)) {
         if (ctx != null) {
            ctx.close();
         }
      }
   }
}

atomikos.xml (Spring)

This configuration includes JMX support for Atomikos.
<?xml version="1.0" encoding="UTF-8"?>
<beans   xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
   <context:mbean-export default-domain="atomikos"/>
   <bean   id="AtomikosTransactionManager"
         class="com.atomikos.icatch.jta.UserTransactionManager" 
          init-method="init"
          destroy-method="close">
      <property name="forceShutdown" value="false"/>
   </bean>
   <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
      <property name="locateExistingServerIfPossible" value="true"/>
   </bean>
   <bean id="jmxTransactionService" class="com.atomikos.icatch.admin.jmx.JmxTransactionService">
      <!-- Optional: show only heuristic problem cases -->
      <property name="heuristicsOnly" value="true"/>
   </bean>
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
      <property name="beans">
         <map>
            <entry key="atomikos:name=tx-service">
               <ref bean="jmxTransactionService"/>
            </entry>
         </map>
      </property>
      <property name="server" ref="mbeanServer"/>
   </bean>
</beans>

ActiveMQLifecycleListener

This lifecycle manager starts the ActiveMQ broker before any web application is started and stops the broker after all web applications stopped.
package com.atomikos.tomcat.spring;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class !ActiveMQLifecycleListener implements LifecycleListener
{
   protected ClassPathXmlApplicationContext ctx = null;

   @Override
   public void lifecycleEvent(LifecycleEvent event)
   {
      if (event.getType().equals(Lifecycle.INIT_EVENT)) {
         if (ctx == null) {
            ctx = new ClassPathXmlApplicationContext(
                  "classpath*:com/atomikos/tomcat/spring/activemq.xml");
         }
      } else if (event.getType().equals(Lifecycle.AFTER_STOP_EVENT)) {
         if (ctx != null) {
            ctx.close();
         }
      }
   }
}

activemq.xml (Spring)

Spring configuration file to start and stop the ActiveMQ broker. The start and stop actions are forwarded behind the scenes to the BrokerFactoryBean using the Spring interfaces InitializingBean and DisposableBean. The configuration of the broker is defined by ${catalina.base}/conf/activemq.xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans   xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
   <bean id="broker" class="org.apache.activemq.xbean.BrokerFactoryBean">
      <property name="config" value="file:${catalina.base}/conf/activemq.xml"/>
      <property name="start" value="true"/>
   </bean>
</beans>

Copying the Lifecycle Listener

Create a jar including the lifecycle listener classes and Spring configuration files. Copy the jar into the TOMCAT_HOME/lib folder.

Copying TransactionsEssentials libraries

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

Copying Atomikos configuration file

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

Copying ActiveMQ

Copy ActiveMQ libraries into the TOMCAT_HOME/lib folder: activemq-core-5.4.2.jar activemq-pool-5.4.2.jar activemq-web-5.4.2.jar (for activemq-admin)

Create and copy an ActiveMQ configuration file (activemq.xml) into the TOMCAT_HOME/conf folder.

Copying MySQL driver

Copy MySQL driver into the TOMCAT_HOME/lib folder: mysql-connector-java-5.1.13-bin.jar

Edit server.xml

Then edit the TOMCAT_HOME/conf/server.xml file. At the beginning of the file you should see these four lines:
  <!-- APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
  <Listener className="org.apache.catalina.core.JasperListener" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
Right after the last one, add these lines:
 <Listener className="com.atomikos.tomcat.spring.AtomikosLifecycleListener"/>
 <Listener className="com.atomikos.tomcat.spring.!ActiveMQLifecycleListener"/>

Edit 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"/>

Create jdbcconfiguration.properties

Then create the TOMCAT_HOME/lib/jdbcconfiguration file. This file is used to define a JDBC resource without setting connection properties in a web application.

user=username
password=password
url=jdbc:mysql://localhost:3306/example?pinGlobalTxToPhysicalConnection=true
(Change properties according to your environment)

Example JDBC application

Here is a sample application snipplet that demonstrates how you can run TransactionsEssentials in a web application after it has been globally installed. The global installation already configured TransactionsEssentials. Therefore the J2ee version of manager and transactions can be used.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
   <display-name>Database Spring Flow</display-name>
   <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/database.xml</param-value>
   </context-param>
   <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
</web-app>

database.xml

Spring flow snipplet from database.xml using MySQL and Spring Integration:
<beans   xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:spi="http://www.springframework.org/schema/integration"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-1.0.xsd">
   <spi:channel id="inputChannel"/>
   <bean id="AtomikosTransactionManager" class="com.atomikos.icatch.jta.J2eeTransactionManager"/>
   <bean id="AtomikosUserTransaction" class="com.atomikos.icatch.jta.J2eeUserTransaction"/>
   <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
      <property name="transactionManager" ref="AtomikosTransactionManager"/>
      <property name="userTransaction" ref="AtomikosUserTransaction"/>
   </bean>
   <bean   id="inboundSource"
      class="com.atomikos.tomcat.spring.InboundSource"
      init-method="init">
      <property name="dataSource" ref="xaDataSource"/>
      <property name="sqlString" value="SELECT 1"/>
   </bean>
   <spi:inbound-channel-adapter   id="inboundAdapter"
                  ref="inboundSource"
                  method="receive"
                  channel="inputChannel">
      <spi:poller>
         <spi:cron-trigger expression="0&#47;5 * * * * 1-5"/>
         <spi:transactional transaction-manager="transactionManager" propagation="REQUIRED"/>
      </spi:poller>
   </spi:inbound-channel-adapter>
   <!-- .......... -->
   <util:properties   id="jdbcConfiguration"
            location="classpath:jdbcconfiguration.properties"/>
   <bean   id="xaDataSource"
      class="com.atomikos.jdbc.AtomikosDataSourceBean"
      init-method="init"
      destroy-method="close">
      <property name="uniqueResourceName" value="DataSourceTransaction"/>
      <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
      <property name="minPoolSize" value="2"/>
      <property name="maxPoolSize" value="5"/>
      <property name="xaProperties" ref="jdbcConfiguration"/>
      <property name="testQuery" value="select 1"/>
   </bean>
</beans>

Class InboundSource:

This class is not a real world one. It shows very briefly how to forward JDBC result sets to a Spring Integration channel. A real world class has to be implemented with a ResultSetExtractor to convert a ResultSet from an real world sql statement into a proper Message.

package com.atomikos.tomcat.spring;

import javax.sql.DataSource;

import org.springframework.integration.core.Message;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;

public class InboundSource implements MessageSource<Integer>
{
   protected JdbcTemplate jdbcTemplate = null;
   protected String statement = null;

   public void setDataSource(DataSource dataSource)
   {
      jdbcTemplate = new JdbcTemplate(dataSource);
   }

   public void setSqlString(String sqlString)
   {
      this.statement = sqlString;
      logger.debug(sqlString);
   }

   @Override
   public Message<Integer> receive()
   {
      String sqlString = new String(statement);

      int result = jdbcTemplate.queryForInt(sqlString);

      if (document != null) {
         return (MessageBuilder.withPayload(new Integer(result)).build());
      }

      return (null);
   }

   public void init() throws Exception
   {
      Assert.notNull(jdbcTemplate, "Data Source object must not be null");
      Assert.notNull(statement, "SQL String must not be null");
   }
}

Example Middleware application

Here is a sample application snipplet that demonstrates how you can run TransactionsEssentials in a web application after it has been globally installed. The global installation already configured TransactionsEssentials. Therefore the J2ee version of manager and transactions can be used. It is a simple blueprint application that shows accessing ActiveMQ.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
   <display-name>Database Spring Flow</display-name>
   <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/middleware.xml</param-value>
   </context-param>
   <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
</web-app>

middleware.xml

Spring flow snipplet using ActiveMQ and Spring Integration to read from a queue. The property sessionTransacted has to be set to "true". This differs to a "real" J2EE server because a J2EE server sets session transacted to true by default. Spring flow snipplet from middleware.xml using ActiveMQ and Spring Integration:
<beans   xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:spi="http://www.springframework.org/schema/integration"
      xmlns:amq="http://activemq.apache.org/schema/core"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-1.0.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.3.0.xsd">
   <spi:channel id="inputChannel"/>
   <bean id="AtomikosTransactionManager" class="com.atomikos.icatch.jta.J2eeTransactionManager"/>
   <bean id="AtomikosUserTransaction" class="com.atomikos.icatch.jta.J2eeUserTransaction"/>
   <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
      <property name="transactionManager" ref="AtomikosTransactionManager"/>
      <property name="userTransaction" ref="AtomikosUserTransaction"/>
   </bean>
   <bean   id="ConnectionFactory"
      class="com.atomikos.jms.AtomikosConnectionFactoryBean"
      init-method="init"
      destroy-method="close">
      <property name="uniqueResourceName" value="ExampleTransaction"/>
      <property name="xaConnectionFactory" ref="XAQCF"/>
   </bean>
   <bean id="ListenerContainer"
      class="org.springframework.jms.listener.DefaultMessageListenerContainer">
      <property name="receiveTimeout" value="10000"/>
      <property name="connectionFactory" ref="ConnectionFactory"/>
      <property name="destination" ref="QueueDestination"/>
      <property name="transactionManager" ref="transactionManager"/>
      <property name="sessionTransacted" value="true"/>
      <property name="sessionAcknowledgeMode" value="0"/>
   </bean>
   <bean id="JmsInput"
      class="org.springframework.integration.jms.JmsMessageDrivenEndpoint">
      <constructor-arg ref="ListenerContainer"/>
      <constructor-arg ref="MessageListener"/>
   </bean>
   <bean id="MessageListener"
      class="com.westlb.traderouter.jms.listener.ChannelPublishingJmsMessageListener">
      <property name="requestChannel" ref="inputChannel"/>
      <property name="expectReply" value="false"/>
   </bean>
   <!-- .......... -->
   <amq:queue id="QueueDestination" physicalName="EXAMPLE.QUEUE"/>
   <amq:xaConnectionFactory id="XAQCF" brokerURL="vm://localhost?create=false"/>
<beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值