Recently, I moved our Spring based application from WebSphere Application Server (WAS) to Tomcat 7. To start with I am writing about the transaction manager support in the Tomcat. I shall be documenting all the challenges that we faced in migration and its resolutions later in a different post.
As we know Tomcat is a lightweight container and does not support all the JEE features out-of-the-box unlike JEE application servers such as WAS . However, standalone transaction manager like JOTM, Atomikos, etc can be easily integrated with Tomcat in order to support local and global transactions with Datasource and JMS resources.
For our purpose, I have used JOTM (Java Open Transaction Manager) as a JTA APIs implementation and configured our resources (Datasource and JMS ) to participate in the XA distributed transactions (two-phase-commit) using the Spring's JtaTransactionManager. We used JOTM version 2.0.10, Tomcat version 7.0.23 and Spring 3.1 with JDK 7(u2). Though, our database was IBM DB2 and JMS Provider was IBM MQ, it can be configured with other providers in similar fashion. Follow the steps below in order to configure your Datasource and JMS in the Tomcat and XA enabled them:
Add the JOTM, DB2 and MQ JARs in the Tomcat lib.As we know Tomcat is a lightweight container and does not support all the JEE features out-of-the-box unlike JEE application servers such as WAS . However, standalone transaction manager like JOTM, Atomikos, etc can be easily integrated with Tomcat in order to support local and global transactions with Datasource and JMS resources.
For our purpose, I have used JOTM (Java Open Transaction Manager) as a JTA APIs implementation and configured our resources (Datasource and JMS ) to participate in the XA distributed transactions (two-phase-commit) using the Spring's JtaTransactionManager. We used JOTM version 2.0.10, Tomcat version 7.0.23 and Spring 3.1 with JDK 7(u2). Though, our database was IBM DB2 and JMS Provider was IBM MQ, it can be configured with other providers in similar fashion. Follow the steps below in order to configure your Datasource and JMS in the Tomcat and XA enabled them:
Copy the following JARs in the <Tomcat-Install-Dir>/lib directory:
JOTM Jars
- commons-logging-api.jar
- jotm-core.jar
- log4j.jar
- ow2-connector-1.5-spec.jar
- ow2-jta-1.1-spec.jar
- carol.jar
- carol-interceptors.jar
- jotm-datasource.jar
- xapool.jar
DB2 Jars
- db2jcc.jar
IBM MQ Jars
- com.ibm.mq.jar
- com.ibm.mqjms.jar
- connector.jar
- dhbcore.jar
- com.ibm.mq.jmqi.jar
(JARs can be found in the <MQ_installdir>/java/lib directory of the system where MQ Broker is installed.)
Configure your spring configuration to define Transaction Manager, Jms and Datasource.
Spring provides JtaTransactionManager to delegate to a backend JTA provider. We used it as we wanted to handle distributed transactions. Had we only one Datsource and no other resources, DatasourceTransactionManager would have suffice.
You can configure the JTATransactionManager and your JMS & Datasource in your Spring configuration file as follows:
<bean class="org.springframework.transaction.jta.JtaTransactionManager" id="transactionManager">
<property name="transactionManagerName" value="java:comp/UserTransaction" />
<property name="allowCustomIsolationLevels" value="true" />
<property name="transactionSynchronizationRegistryName" ref="transactionSynchronizationRegistryJNDIName" />
</bean>
<bean class="java.lang.String" id="transactionSynchronizationRegistryJNDIName">
<constructor-arg value="java:comp/env/TransactionSynchronizationRegistry" />
</bean>
<!-- To make JMS Connection factory aware of JTA transaction -->
<bean id="connectionFactory" class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
<property name="targetConnectionFactory" ref="jmsConnectionFactory"/>
<property name="synchedLocalTransactionAllowed" value="false"/>
</bean>
<!-- Database Resources -->
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/ds/myDataSource" />
<!-- JMS Resources -->
<jee:jndi-lookup id="jmsConnectionFactory" jndi-name="java:comp/env/jms/myQueueConnectionFactory" />
<jee:jndi-lookup id="cvproxyDestination" jndi-name="java:comp/env/jms/myQueue" />
Spring provides JtaTransactionManager to delegate to a backend JTA provider. We used it as we wanted to handle distributed transactions. Had we only one Datsource and no other resources, DatasourceTransactionManager would have suffice.
You can configure the JTATransactionManager and your JMS & Datasource in your Spring configuration file as follows:
<bean class="org.springframework.transaction.jta.JtaTransactionManager" id="transactionManager">
<property name="transactionManagerName" value="java:comp/UserTransaction" />
<property name="allowCustomIsolationLevels" value="true" />
<property name="transactionSynchronizationRegistryName" ref="transactionSynchronizationRegistryJNDIName" />
</bean>
<bean class="java.lang.String" id="transactionSynchronizationRegistryJNDIName">
<constructor-arg value="java:comp/env/TransactionSynchronizationRegistry" />
</bean>
<!-- To make JMS Connection factory aware of JTA transaction -->
<bean id="connectionFactory" class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
<property name="targetConnectionFactory" ref="jmsConnectionFactory"/>
<property name="synchedLocalTransactionAllowed" value="false"/>
</bean>
<!-- Database Resources -->
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/ds/myDataSource" />
<!-- JMS Resources -->
<jee:jndi-lookup id="jmsConnectionFactory" jndi-name="java:comp/env/jms/myQueueConnectionFactory" />
<jee:jndi-lookup id="cvproxyDestination" jndi-name="java:comp/env/jms/myQueue" />
I used TransactionAwareConnectionFactoryProxy to make the JMS connection factory aware of JTA transaction manager registered with the Spring.
Note that there is no resource provider specific entry in the Spring configuration file. All the resource implementations are defined and read from the JNDI (we are going to define the specifics in the JNDI in the later step). So, you are free to use any transaction manager, datasource and JMS providers without needing any modifications in the spring config file.
We have registered the JTA transaction manager and make datasource & jms connection aware of it. You can declare the transaction control (using AOP or Annotation) and provide the above transactionManager as the transaction manager.
Update web.xml
Define JNDI resources in your application's web.xml.Why? Because, its give you an extra layer of loose coupling between the resource defined in the JNDI and its name being referred in the application. Let say, if you already have datasource defined in the Server's context and you want to re-use it. But the application is referring to a datasource whose name differs than the one defined in the context. You can do that by defining the datasource in your web application's web.xml without needing modifications in the server's context or your application.
<resource-ref id="ResourceRef_1">
<description> </description>
<res-ref-name>ds/myDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
<res-sharing-scope>Unshareable</res-sharing-scope>
</resource-ref>
<resource-env-ref id="ResourceEnvRef_2">
<description> </description>
<resource-env-ref-name>jms/myQueue</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
</resource-env-ref>
<resource-env-ref id="ResourceEnvRef_3">
<description> </description>
<resource-env-ref-name>jms/myQueueConnectionFactory</resource-env-ref-name>
<resource-env-ref-type>javax.jms.QueueConnectionFactory</resource-env-ref-type>
</resource-env-ref>
Configure Tomcat's context
Define the actual resources and its providers' details in the Tomcat's context.Edit the <Tomcat-Install-Dir>/conf/context.xml to add the following enteries:
DataSource
To configure DB2 as your datasource:
<Resource driverClassName="com.ibm.db2.jcc.DB2Driver"
factory="org.objectweb.jotm.datasource.DataSourceFactory"
name="ds/cvProxyDataSource"
type="javax.sql.XADataSource"
url="jdbc:db2://<hostname>:<port>/<database-name>"
username="<username>" password="<password>"/>
Replace, <hostname>, <port>, <database-name>, <username> and <password> with correct values.
Note the type javax.sql.XADataSource. Keep it as javax.sql.DataSource, if you do not need want to XA enabled it.
JMS
To configure IBM MQ as your JMS provider.
<Resource
name="jms/myQueueConnectionFactory"
auth="Container"
type="com.ibm.mq.jms.MQXAQueueConnectionFactory"
factory="com.ibm.mq.jms.MQXAQueueConnectionFactoryFactory"
description="JMS Queue Connection Factory"
QMGR="<queue-manager-name>"/>
<Resource
name="jms/myQueue"
auth="Container"
type="com.ibm.mq.jms.MQQueue"
factory="com.ibm.mq.jms.MQQueueFactory"
description="JMS Queue for receiving messages from Enterprise"
QU="<queue-name>"/>
Replace, <queue-manager-name> and <queue-name> with correct values.
Note that, above definition will set MQ transport as "BIND" by default (i.e. local to the system where the Tomcat is running). If the MQ broker is running on a different system, you will have to set the following extra properties for configuring the QueueConnection factory: HOST (hostname), PORT and CHAN (channel name) and TRAN (transport, should be "CLIENT" for the client mode).
Note the type com.ibm.mq.jms.MQXAQueueConnectionFactory and factory com.ibm.mq.jms.MQXAQueueConnectionFactoryFactory. Keep the type and factory as com.ibm.mq.jms.MQQueueConnectionFactory & com.ibm.mq.jms.MQQueueConnectionFactoryFactory, respectively, if you do not want to XA enabled it.
Transaction
To configure JOTM as transaction manager for the JTA implementation:
<Resource name="TransactionSynchronizationRegistry" auth="Container" type="javax.transaction.TransactionSynchronizationRegistry" factory="org.objectweb.jotm.TransactionSynchronizationRegistryFactory"/>
<Transaction factory="org.objectweb.jotm.UserTransactionFactory" jotm.timeout="60"/>
Update WAR
Add the Jms Jar in your application's lib directory.As far as your tomcat setup for JTA transaction is concerned, you are good to go. However, you will start seeing the the following JMS error on starting the tomcat server:
1)
java.lang.IllegalStateException: Cannot convert value of type
[com.ibm.mq.jms.MQQueueConnectionFactory] to required
type[javax.jms.ConnectionFactory] for property 'connectionFactory': no matching
editors or conversion strategy found.
To resolve the error, copy the com.ibm.mqjms.jar in your WAR's WEB-INF/lib folder before deploying it to the Tomcat.Thats it for the day! Keep me posted, if you run into any problem. I would be happy to help you.