(This week's module spotlight is written by John Ament, the Seam JMS module lead. - Shane)
by John Ament
For the third entry in Seam Module spotlight, I present to you Seam JMS.
Seam JMS is a set of components meant to simplify use cases around JMS. Seam JMS aims to fill the gap between the JMS APIs and a cleaner feel to working with message oriented middleware. We do this by:
- Providing basic injection capabilities for JMS Resources.
- Providing a clean API for sending & receiving messages
- Providing an easy-to-use mechanism to map the sending and receiving of JMS messages to the firing and observing of CDI events.
The main goal of this article is to go in depth with how to work with JMS Messages as CDI Events. At the end, I’ll discuss a little future stuff. For details about how to get started with the project, please start with the reference guide.
Requirements
In order to use Seam JMS, you need to have a JMS ConnectionFactory bound at the JNDI location /ConnectionFactory. In addition, you’ll need to include the seam-jms.jar libary in your project; as well as a required dependency on seam-solder.jar. Optionally, if you are working in a servlet container, it is highly recommended to include the seam-servlet.jar library in your application, this will add in support container start-up events to the application.
Mapping
Mapping events is at the core of what Seam JMS does. This allows you to fire a CDI event and have a JMS message sent to a destination, and vice versa.. Seam JMS allows you to define both one-way and two-way events; supporting both ingress (in-bound to the application) as well as egress (out-bound from the application). When a two-way route is used, your application will both create and process the event, handled as an asynchronous event.
Routing
CDI Event -> JMS message mapping happens via POJIs (Plain Old Java Interfaces). There are three supported ways to create the routes via interfaces. Here is a quick example mapping interface:
public interface ApplicationObserverMappings { @Routing(RouteType.BOTH) public void mapStrings(@Observes String s, @MyTopic Topic topic); @Routing(RouteType.INGRESS) public void mapInboundLongs(@Observes Long l, @MyQueue Queue queue); @Routing(RouteType.EGRESS) public void mapOutboundShorts(@Observes Short s, @MyOtherTopic Topic topic); }
This creates three routes: one egress, one ingress and one bidirectional. For each ingress and bi-drectional route you will need to define an observer method to listen for events fired on the appropriate destination. When dealing with ingress routes, a JMS Message Listener is created on application start up. Note: if you are not using Seam Servlet in your application, you will need to inject a reference to org.jboss.seam.jms.bridge.RouteBuilder in your startup component in order to trigger the initialization of all listeners.
At ths point, all Topics & Queues are resolved against CDI bean lookup. Each message coming in via the message listener that has the correct type will be fired to all observer methods of the correct type. In the above, you would need appropriate producer methods for Queues/Topics matching the paired qualifiers, for example:
@Resource(“jms/MyTopic”) Topic topic; @Produces @MyTopic public Topic produceMyTopic() { return topic; }
For each of the egress events, you would define a CDI Event (injection point). Firing an object through this event results in creating a JMS message.
Firing Events
One you have defined your routes, you need to have an Event or a BeanManager to fire events against. For example, inject the following Event in your code
@Inject @Routing(RouteType.EGRESS) Event<String> xmlEvent;
And in your business logic, fire the following:
public void sendData(String data) { xmlEvent.fire(“<simple>”+data+”</simple”); }
This event will be forwarded to the JMS Topic identified by the CDI component of type Topic that is qualified @MyTopic. A TextMessage will be published to the Topic, with the text contained in the event. If the event was another object, an ObjectMessage would be published. Likewise, if we were using a Queue the appropriate Messages would be sent to the Queue.
Handling Events
For all ingress and bi-directional routes, Seam JMS will create events for all Messages received on the JMS Destinations defined within the route. The object type will be the payload type of the message; TextMessages become strings. The event will include all qualifiers from the route, as well a necessary @Routing annotation, with ingress type.
These Routes, as noted above, will generate messages that will be handled by these observer methods.
@Routing(RouteType.BOTH) public void mapStrings(@Observes String s, @MyTopic Topic topic); public void handleInBoundText(@Observes @Routing(RouteType.INGRESS) String xmlString) { … } @Routing(RouteType.INGRESS) public void mapInboundLongs(@Observes Long l, @MyQueue Queue queue); public void handleInBoundLongs(@Observes @Routing(RouteType.INGRESS) Long someId) { … }
Support and Capabilities
By default, all events are fired as ObjectMessages. The underlying object is the Serialized event object that was fired. If the fired object is a String, then we use a TextMessage instead of ObjectMessage. This should help save on bandwidth, selectively (e.g. simple XML instead of a Serialized object). Using the provided MessageManager API, you can easily send and receive messages as well, allowing for easy programmatic integration with the defined routes, without needing to fire events or handling events.
To the Future
The biggest near term add on coming is support in Seam Forge. We will add tooling support for Routing interfaces, HornetQ descriptors, generating MDBs and creating observer methods. In the next release of Seam JMS, there will be more testing in cross-container capabilities, as well as explicit distribution to remote systems.