Sunday 10 November 2013

Sending and receiving a message using JMS 2 API

Sending a message with JMS 2.0
The JMS 2.0 aka simplified API has reduced the number of JMS objects required to send a message to a destination. The simplified API provides two objects for the said task, JMSProducer and JMSConsumer interfaces respectively. The good thing about them is they both allow method calls to be chained together. 

Let's take a look at the sender code. 

public void sendMessage(String messageText, String destinationAddress){
   ResourceFactory resourceFactory = new ResourceFactoryImpl();
   try(JMSContext jmsContext = resourceFactory.getJMSContext(JMSContext.AUTO_ACKNOWLEDGE);){
Queue queue = jmsContext.createQueue(destinationAddress);
jmsContext.createProducer().send(queue, messageText);
   }
}


A word on JMSContext.createQueue(String queueAddress)
The JMSContext.createQueue(String queueAddress) would not create a physical destination but Queue object for an existing destination. It is encouraged to look up for the administrative Queue object on the JNDI namespace since this approach reduces the chances of portability of the code as we have to provide it with a vendor specific physical Queue name. 

However, in this example, it's required to provide this method with the physical queue name. In HornetQ, it's the queue address name.

On a side note, the JMS API does not provide a mechanism to create permanent destinations. It's up to the JMS implementation to provide a mechanism to create permanent destination(s). This is not mandatory since JMS specification doesn't impose JMS providers to implement such a feature.

However the JMS API allows the creation of temporary destinations. The temporary destination(s) would be removed and garbage collected when the JMS Session/JMSContext associated with the temporary destination gets closed. 


Receiving a message with simplified API 
The JMS receiver code is pretty simple too. One thing you would notice is the ease of obtaining the message payload using JMSConsumer.receiveBody(), without performing any typecasting as it was with the JMS 1.1 specification. Here's the receiver code :

public String receiveTextMessage(String destinationAddress){
   ResourceFactory resourceFactory = new ResourceFactoryImpl();
   try(JMSContext jmsContext = resourceFactory.getJMSContext(JMSContext.AUTO_ACKNOWLEDGE);){
      Queue queue = jmsContext.createQueue(destinationAddress);
      JMSConsumer jmsConsumer = jmsContext.createConsumer(queue);
      return jmsConsumer.receiveBody(String.class);
   } 
}



The ResourceFactory helper interface and its implementation excerpt :

public interface ResourceFactory {
   public ConnectionFactory getConnectionFactory();
   public JMSContext getJMSContext(int sessionTx);
}


public class ResourceFactoryImpl implements ResourceFactory {
....
....
    @Override
    public JMSContext getJMSContext(int sessionTx) {
       ConnectionFactory connectionFactory = getConnectionFactory();
       JMSContext jmsContext = connectionFactory.createContext(JMSHelper.getProperty("jms.user.name"), JMSHelper.getProperty("jms.user.password"),sessionTx);
       return jmsContext;
    }
....
....
}

Friday 8 November 2013

JMSContext in action

One of the key changes in JMS 2.0 (simplified) API is the introduction of JMSContext Interface. JMSContext encapsulates the functionality of both Connection and a Session. This eliminates the need to create a JMS session explicitly, thus it reduces the number of JMS objects needed to send a message. A JMSContext can be used to create JMSConsumer(s), JMSProducer(s) or more JMSContext(s) as preferred.


Starting a JMSContext
With the JMS 1.1 specification, you need to start a connection to receive messages explicitly by invoking connection.start(). The connection objects are in "stopped" mode so you need to start the connection to consume messages. Please mind, you *only* required to start a connection if you're planning to consume messages via the created connection object, else it is *not* required to start a connection. 

The JMS specification is a bit ambiguous on the "state" of the JMSContext. The specification doesn't expect JMS providers to offer a JMSContext at "started" state by default. Instead, it expects JMS providers to auto start the JMSContext at the creation of the first consumer. However, HornetQ offers a JMSContext at started state irrespective to that. You could invoke JMSContext.getAutoStart() to obtain the status of the context.


Connection sharing with JMSContext (How to cache a JMSContext)
On my previous blog post, I explained it's an anti-pattern to create a connection at each message send() when sending messages from a plain JMS client. How could that be possible with JMSContext; since it represents both, session and a connection? 

Yes, this is still doable with JMSContext on an application client. A JMSContext is not recommended to be accessed concurrently to ensure thread safety. However you could still maintain a single physical connection and share it among multiple JMS client threads by issuing each JMS client with a JMSContext. You need to invoke JMSContext.createContext(int sessionMode) for each JMS client; each JMSContext.createContext() invocation would not create a new physical connection but a new JMSContext. When you invoke JMSContext.close() on a JMSContext, it would close just the JMSContext not the physical connection if it's being used by another (active) JMSContext(s). Here's an illustration :

JMSContext jmsContext = resourceFactory.getJMSContext(JMSContext.AUTO_ACKNOWLEDGE);
JMSContext jmsContext2 = jmsContext.createContext(0);
JMSContext jmsContext3 = jmsContext.createContext(0);
jmsContext.close();   
jmsContext3.close();
//The physical connection still active although the initial JMSContext was closed.
Queue queue = jmsContext2.createQueue("A"); 
JMSConsumer jmsConsumer = jmsContext2.createConsumer(queue);


Closing a JMSContext
Invoking JMSContext.close() is suffice to close a connection. The client code does *not* need to close its associated resources, such as JMS consumers and JMS producers. The JMS provider closes all the associated resources when you invoke JMSContext.close().


The invocation of JMSContext.createContext(int sessionMode) is prohibited inside a JEE container as it violates JEE 7 specification. The WEB, EJB applications inside a JEE container are not allowed to have more than an active JMSContext at any given moment. The container would throw JMSRuntimeException if attempted to create more than a single active JMS context. (Please refer JEE 7 Spec, section 6:7)