Spring Integration

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&#8243; xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221;
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd”&gt;
<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&gt;
<repositories>
<repository>
<id>com.springsource.repository.bundles.milestone</id>
<url>http://repository.springsource.com/maven/bundles/milestone</url&gt;
</repository>
<repository>
<id>com.springsource.repository.bundles.release</id>
<url>http://repository.springsource.com/maven/bundles/release</url&gt;
</repository>
<repository>
<id>com.springsource.repository.bundles.external</id>
<url>http://repository.springsource.com/maven/bundles/external</url&gt;
</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.

Advertisements

Trying out Spring Integration

When building our Virtual Enterprise (1-1.5 years ago), we ended up using Mule ESB (version 1.3.3 at the time) to handle protocol mediation. The intent at the time was to focus on getting our hands into various SOA product suites in more of a real world situation. Therefore, we really didn’t want to spend the time to write the code required to write to or read from a JMS queue, or call a web service, or write code to communicate over whatever protocol our enterprise requires. We also wanted the ability to change the protocol over which any endpoint within the Virtual Enterprise communicated. This would allow us to mimic numerous scenarios without having to make coding changes. Mule ESB gave us these capabilities mainly through the configuration of connectors and routers on POJOs.

With the introduction of Spring Integration (currently milestone 4), I have decided to take a look back at one of the endpoints in our Virtual Enterprise and convert it from Mule to Spring Integration. The main motivation for this is to get my hands into Spring Integration and start to get a feel for the capabilities it offers. The endpoint I chose was the Emergency Alert System (EAS). This is a spring web application that runs an embedded instance of Mule for the protocol mediation mentioned above. This application communicates via message queues. When an alert is created, it sent to a JMS queue that kicks off a business process which subsequently notifies other external systems. During various points in the business process, information flows back into the EAS via other message queues.

In order to accomplish this with Mule, the following mule configuration file was required:


<?xml version=”1.0″ encoding=”UTF-8″?>

<!DOCTYPE mule-configuration PUBLIC “-//SymphonySoft //DTD mule-configuration XML V1.0//EN”
http://www.symphonysoft.com/dtds/mule/mule-spring-configuration.dtd”&gt;

<mule-configuration id=”SOA_LAB_EAS” version=”1.0″>

<mule-environment-properties serverUrl=”tcp://localhost:60505″/>

<!– Use this connector if using ActiveMQ as the JMS provider –>
<connector name=”jmsConnector” className=”org.mule.providers.jms.JmsConnector”>
<properties>
<property name=”specification” value=”1.1″/>
<property name=”connectionFactoryJndiName” value=”ConnectionFactory”/>
<property name=”jndiInitialFactory” value=”org.apache.activemq.jndi.ActiveMQInitialContextFactory”/>
<map name=”connectionFactoryProperties”>
<property name=”brokerURL” value=”tcp://localhost:61616″/>
<property name=”brokerXmlConfig” value=”classpath:/activemq-config.xml”/>
</map>
</properties>
</connector>

<transformers>
<transformer name=”JmsMessageToString” className=”org.mule.providers.jms.transformers.JMSMessageToObject” returnClass=”java.lang.String”/>
<transformer name=”StringToJmsMessage” className=”org.mule.providers.jms.transformers.ObjectToJMSMessage” returnClass=”javax.jms.TextMessage”/>
</transformers>

<model name=”EAS”>
<mule-descriptor name=”EASCallReceiver” implementation=”conferenceCallReceiver”>
<inbound-router>
<endpoint address=”jms://alert.inbound.call.queue” transformers=”JmsMessageToString”/>
</inbound-router>
</mule-descriptor>
<mule-descriptor name=”EASNotificationReceiver” implementation=”notificationInfoReceiver”>
<inbound-router>
<endpoint address=”jms://alert.inbound.notification.queue” transformers=”JmsMessageToString”/>
</inbound-router>
</mule-descriptor>
</model>

</mule-configuration>

As you can see, this configuration defines the appropriate JMS Connection information for connecting to ActiveMQ, the transformers necessary to convert to and from JMS Text Messages, and the Mule endpoints (the implementations refer to Spring beans defined in my Spring config file) with the appropriate routers defined for the inbound and outbound JMS Queues. This configuration isn’t too bad. In order to embed Mule within my Spring application, I had to add 16 JARS to my classpath. Then, when the web application would start up, the embedded Mule server would start up as well, sharing the spring context, and everything connected nicely. In order to send messages to the outbound queue, I used the MuleClient class to get access to the endpoints, and let the Mule server handle the rest. The code was as simple as this:

MuleClient muleClient = new MuleClient();
muleClient.sendNoReceive(outboundEndpoint, messageBody, null);

where outboundEndpoint is a String value injected into my class which references the URI for the JMS queue (i.e. jms://alert.outbound.queue). As for the inbound messages, they were handled by Mule based on the inbound routers configured on the Mule Endpoints. Once a message was delivered to a queue, Mule transformed it to a string (based on the defined transformers) and routed it to my POJO. Overall, we were quite pleased with this scenario.

With the introduction of Spring Integration, we were interested in seeing if we could use this framework in the same way we were using Mule. So I went about the task of converting the embedded Mule configuration to Spring Integration. In the end, it really wasn’t too difficult.

The first step in my conversion process was to send the outbound message to the alert.outbound.queue (an ActiveMQ queue). I only had to add the following JARS to my classpath: org.springframework.integration-1.0.0.M4.jar and org.springframework.integration-adapter-1.0.0.M4.jar.

In order to use spring integration to route my messages, I first defined a message bus. This is accomplished by declaring the following component in my spring configuration file:

<integration:message-bus/>

Next, I needed to define a channel, and connect that channel to a target endpoint. This is accomplished with the following configuration:

<integration:channel id=”outboundAlertChannel”/>
<integration:target-endpoint input-channel=”outboundAlertChannel” target=”jmsTarget”/>

Since we are targeting a JMS endpoint, the target attribute refers to a JMS target bean configured like this:

<integration:jms-target id=”jmsTarget” connection-factory=”jmsFactory” destination-name=”alert.outbound.queue”/>

This uses an ActiveMQ connection factory and defines the queue destination on which to place the outbound message.

Then in my POJO, I added setters to inject the MessageChannel (represented by the channel element in my configuration) so I could send the message to the channel.

outboundAlertChannel.send(new StringMessage(messageBody));

Then the Spring Integration framework would take over from there to route the message to the alert.outbound.queue.

Next, I had to configure the inbound messages and route them to a particular method on another POJO. This was pretty straightforward as well, and was accomplished entirely through configuration. I needed to define a channel, a JMS Source, and a handler endpoint. In this case, I wanted to follow a message driven approach rather than a polling approach, so I used a JMS Gateway. Unfortunately, there is a bug when using namespace support for JMS Gateway configuration (that is already fixed, but not in milestone 4), so I had to configure it using a bean declaration. The configuration for receiving the message from the queue and routing to my POJO can be seen below:

<integration:channel id=”inboundAlertNotificationChannel”/>

<bean id=”jmsGateWay” class=”org.springframework.integration.adapter.jms.JmsGateway” init-method=”start”
destroy-method=”destroy”>
<property name=”connectionFactory” ref=”jmsFactory”/>
<property name=”requestChannel” ref=”inboundAlertNotificationChannel”/>
<property name=”destinationName” value=”alert.inbound.notification.queue”/>
<property name=”expectReply” value=”false”/>
</bean>

<integration:handler-endpoint input-channel=”inboundAlertNotificationChannel” handler=”notificationInfoReceiver” method=”receiveNotificationInfo”/>

The jmsGateway connects the queue to the channel and the handler-endpoint connects the channel to my POJO, specifying the method to call on the POJO. The message is converted by the framework from a JMS Text message to a String. That was it, no coding changes required for this modification.

I found using Spring Integration (in this simple example) very logical, straightforward, and lightweight. I guess these seem to be typical attributes that come to mind when using Spring.