Tuesday, July 15, 2008

Creating a JMS Bridge with Spring Integration

In my last post, I discussed replacing Mule with Spring integration. While showing that to my colleague Rod Biresch, he thought it would be interesting to use Spring Integration as a JMS Bridge. He ran into an issue last year (described in his post OpenESB and Glassfish: JMS Messaging) where he needed to replace Glassfish's Messaging Broker with a remote instance of ActiveMQ. From there, he was going to use an MDB to pull the message off the queue, and then write JMS code to place the same message on another broker's (non-ActiveMQ) queue. At the time, that was certainly an acceptable way to accomplish that. Another option would have been to use Mule to bridge the queues. Today several more options exist, including Spring Integration and Camel (among others). The nice thing about these is that they integrate nicely with Spring and are very lightweight. In this post, I will show how to use Spring Integration as a JMS Bridge between ActiveMQ and JBossMQ. I am using Spring Integration 1.0M5, ActiveMQ 5.1 (standalone), and JBoss 4.2.2 GA Server. Of course, this concept can be exteneded to bridge any 2 JMS providers.

In the Spring configuration file, we need to specify a listener (JMS Gateway) to receive the message off the ActiveMQ queue and place it on a channel, a channel adapter to take the messages from the channel and send them to a target endpoint, and a target endpoint that places the messages on the JBoss queue. And, of course a message bus to coordinate everything.

Below is the Spring Integration related configuration as previously described:

<!-- define the message bus -->
<integration:message-bus/>

<!-- necessary channels -->
<integration:channel id="bridgeChannel"/>

<!-- JMS Source that will receive messages -->
<integration:jms-gateway request-channel="bridgeChannel" connection-factory="jmsFactory" destination-name="alert.inbound.notification.queue" expect-reply="false"/>

<!-- define the channel adapter -->
<integration:channel-adapter channel="bridgeChannel" target="jmsTarget"/>

<integration:jms-target id="jmsTarget" connection-factory="jmsJBossConnFactory" destination="jBossQueue"/>

Of course along with this we need the required connection factories defined, as shown here:

<!-- JMS connection factory for ActiveMQ -->
<bean id="jmsFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://localhost:61616</value>
</property>
</bean>

<!-- Connection information for JBoss Queue -->
<jee:jndi-lookup jndi-name="ConnectionFactory" id="jmsJBossConnFactory" environment-ref="jndiProps" expected-type="javax.jms.ConnectionFactory"/>

<jee:jndi-lookup jndi-name="queue/A" id="jBossQueue" environment-ref="jndiProps" expected-type="javax.jms.Queue"/>

<util:properties id="jndiProps" location="classpath:jndi.properties"/>

We also need a way to start up the Spring Context. If you are writing a web application, this would happen through configuration. In my circumstances, I was writing a standalone application for testing purposes, so I wrote a simple java class to load the Spring Context:

public static void main( String[] args ) {
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("spring-integration-config.xml");
}
I used the web console included with the standalone ActiveMQ distribution along with the Administration Console for JBoss to validate everything worked. You could just as easily use Hermes or something like that too.

Important safety tip: The ActiveMQ web console doesn't always work as it appears. When trying to place a message on the queue, do not use the "Send" link within the header area of the page. Use the "Send To" link in the operations column of the queue listing page. I wasted a little time troubleshooting what I thought was a problem :)

If you are using Maven, here is the pom I used for this application:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chariotsolutions.jms.adapter</groupId>
<artifactId>JMSAdapter</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>JMSAdapter</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<id>com.springsource.repository.bundles.milestone</id>
<url>http://repository.springsource.com/maven/bundles/milestone</url>
</repository>
<repository>
<id>com.springsource.repository.bundles.release</id>
<url>http://repository.springsource.com/maven/bundles/release</url>
</repository>
<repository>
<id>com.springsource.repository.bundles.external</id>
<url>http://repository.springsource.com/maven/bundles/external</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>org.springframework.integration</artifactId>
<version>1.0.0.M5</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>org.springframework.integration.adapter</artifactId>
<version>1.0.0.M5</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jbossmq-client</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jnp-client</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jboss-common</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>concurrent</groupId>
<artifactId>concurrent</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>


And it was as simple as that. I spent more time getting the Maven dependencies figured out than anything else.

4 comments:

ilango said...

I am trying to come up with a solution that looks like this:
An MDB Listener that has to pick up the message from a source queue (ActiveMQ), looks at some header value that has destination queue information, and places the message on that destination queue. I may have upto 3 destination queues. Could you direct me to any sample that does something close to that? Thanks

Steve Smith said...

Take a look at the MessageRouter (@Router for annotation based configurations)

pseudonym said...

Using ActiveMQConnectionFactory brings in a lot of connection leaks w.r.t JmsTemplate since internally it creates a new connection with each and every transaction. Hence -it is suggested to use the PooledConnectionFactory , that takes the ctor. with the end-point as well.

Just an optimization in a practical scenario.

Suma said...

Hello, We are having problem of connections getting lost to webmethods broker from a JMS client. Issue is sometimes the connection to Broker becomes stale and shows as disconnected in MWS. We have setup listener using spring framework. Webmethods is the JMS Provider. Is there any property setting to keep this connection active all the time?