Sunday, March 10, 2013

What's New in JMS 2 (Part 1)

This post is the first of many parts around the new features of JMS 2.

In this post we talk about some of the new features in the original APIs.  The biggest changes here are around the AutoCloseable interface and RuntimeExceptions.

Let's say we want to read some messages from a Queue, but maybe can't use an MDB or MessageListener to do the work.


public List readMessageContent() {
List messageBodies = new ArrayList<>();
try (Connection conn = connFactory.createConnection();
Session sess = conn.createSession();
MessageConsumer cons = sess.createConsumer(queue)) {
Message m = null;
while ((m = cons.receiveNoWait()) != null) {
if (m instanceof TextMessage) {
TextMessage tm = (TextMessage) m;
messageBodies.add(tm.getText());
m.acknowledge();
}
}
} catch (JMSException | JMSRuntimeException e) {

}
return messageBodies;
}

In our try area we can create a Connection, Session and Message Consumer in an AutoCloseable fashion.  We only want TextMessages so use an if to check the type.  We don't have to deal with any of the closing methods in the code and handle only the exception.  We also have a new JMSRuntimeException that can be caught, or you can let it throw further into the stack.

Digging through the JMS 2.0 javadocs, you'll notice a lot of use in AutoCloseable.  All of the Session interfaces have it now, all of the Connection interfaces as well.  All of the MessageConsumer equivalents (TopicSubscriber, QueueReceiver) and the MessageProducers (TopicPublisher, QueueSender) have it as well.  The new APIs (JMSContext and related) all have it too.  Whenever you create these objects in a try with resources statement they will automatically close for you when done.  This is a pretty powerful feature that matches standard use cases from today.

At this point, all you have to do is finish out your REST resource, and you can easily read all given messages in a single request.  Here's what it looks like:


@Path("/jms")
@Stateless
public class WhatsNewReader {

@Resource(mappedName = "jms/__defaultConnectionFactory")
private ConnectionFactory connFactory;

@Resource(mappedName = "jms/SomeQueue")
private Queue queue;

private Logger logger = Logger.getLogger(WhatsNewReader.class
.getCanonicalName());

public List readMessageContent() {
List messageBodies = new ArrayList<>();
logger.info("Reading.");
try (Connection conn = connFactory.createConnection();
Session sess = conn.createSession();
MessageConsumer cons = sess.createConsumer(queue)) {
logger.info("In the try.");
Message m = null;
while ((m = cons.receiveNoWait()) != null) {
logger.info("In the while.");
if (m instanceof TextMessage) {
TextMessage tm = (TextMessage) m;
messageBodies.add(tm.getText());
m.acknowledge();
}
logger.info("leaving iteration.");
}
} catch (JMSException | JMSRuntimeException e) {

}
return messageBodies;
}

@GET
@Produces("text/plain")
public String getMessages() {
List msgs = readMessageContent();
StringBuilder sb = new StringBuilder("Hello,");
for (String m : msgs) {
sb.append(m).append("\n");
}
return sb.toString();
}
}

Note that I have to use mappedName in my resources.  As far as I can remember, mappedName is supposed to be the product specific JNDI, however I was not able to use name to look these up.  Hopefully this gets corrected in a latest GF4 Build.



You can pull the code from this example, and the next few, from here on my github account: https://github.com/johnament/whats-new-jms2

Edit:

- Fixed JNDI look up issue/at least it's working in b80/b81
- Fixed my typo of receive vs receiveNoWait.