Tuesday, February 17, 2009

Using Client Acknowledge mode to expose a JMS topic as a Web service

A JMS session has three acknowledge modes AUTO, CLIENT and DUPS_OK, messages are removed from the JMS destination (Queue or Topic) only when the session has acknowledged that the client has received the message. In AUTO acknowledge mode the message is acknowledged as soon as the receive method has been completed. In DUPS_OK acknowledge the message is acknowledged lazily which means the message could be sitting in the destination for some time before the broker removes it at its will. In CLIENT acknowledge mode the client after receiving the message acknowledges it by calling its acknowledge method.This mode is key when you want to expose your JMS destination as a web service.

The one problem that needs to be solved when exposing a JMS destination as a web service is reliability. How do we make sure that the web service client has successfully received the message. In other words how do we support distributed transaction. The following example demonstrates this.

Target is to expose a JMS topic called yahoonews as web service.

Step1. Create a durable subscriber with CLIENT acknowledge mode

public Class DurableSubscriber{
Context jndiContext = null;
TopicConnectionFactory topicConnectionFactory = null;
TopicConnection topicConnection = null;
TopicSession topicSession = null;
BasicTopic topic = null;
TopicSubscriber topicSubscriber = null;

public DurableSubscriber(){
try {
topicConnectionFactory = new com.sun.messaging.TopicConnectionFactory();
topicConnectionFactory.setProperty("imqBrokerHostName",brokerHost);
topicConnectionFactory.setProperty("imqBrokerHostPort",brokerPort);

topicConnection = (com.sun.messaging.jms.TopicConnection)
topicConnectionFactory.createTopicConnection();
topicConnection.setClientID("yahoouserid1001");

topicSession =
topicConnection.createTopicSession(false,
Session.CLIENT_ACKNOWLEDGE);
topic = (com.sun.messaging.BasicTopic)topicSession.createTopic("yahoonews");
} catch (Exception e) {
e.printStackTrace();
if (topicConnection != null) {
try {
topicConnection.close();
} catch (javax.jms.JMSException ee) {}
}
}
}// end constructor

public void startSubscriber(){
try {

topicConnection.stop();
topicSubscriber =
topicSession.createDurableSubscriber(topic,
"finanancialnewsSubscriber");

topicConnection.start();
} catch (javax.jms.JMSException e) {
System.err.println("Exception occurred: " +
e.toString());
}

}

public javax.jms.Message getMessage(int timeout) throws Exception{

javax.jms.Message m = topicSubscriber.receive(timeout);
return m;
}
}

Step2. Create a wrapper class that will exposed as web service using REST or SOAP.
The web service should have two functions getMessage and ackMessage.
The class should also store the message that needs to be acknowledged.


public class JMSWebService{

private static DurableSubscriber ds = new DurableSubscriber();
static{
ds.startSubscriber();
}
private javax.jms.Message ackMessage;

public String getMessage(){
if (ackMessage==null)
ackMessage = ds.getMessage(1000); // 1 sec blocking call to get messages
String str = ackMessage.getText();
return str;
}

public void ackMessage(){
if (ackMessage!=null)
ackMessage.acknowledge();
ackMessage = null;
}
}

In this example reliability is achieved by having the ackMessage service along with getMessage, the client has to call the ackMessage function to indicate that all is well at the client side and that the client has commited the transaction at his end. If the ackMessage call is never made and the client keeps calling the getMessage function the transaction will not be commited and so the same message is returned to the client.

No comments: