Friday, June 20, 2008

JMS Send/Response implementation

In this article I want to talk about how you will create a send/response service using JMS.

There're several reasons to turn an asynchronous thing into a synchronous process, one of them is the following problem: What if you need to call an external service and you will not know how much time that will takes, but you don't want your user seat in front of the page forever? That's one of the problems I faced up some days ago, and because of that I'm writing this.

Not all the JEE implementations provide a timeout for a method transaction, some of them does and you can put a timeout for a method in the vendor specific deployment descriptor, but it's not required by the EJB spec and, if you want a JEE compliant application, you should work with the specification. There's an annotation "@TimeOut" you could use, and it's a standard supported annotation, but this will not help you at all to solve this problem, but explaining the uses of timers is not the focus of this article (if you need clarification on this, just post a comment...)

JMS Background

The sample below uses some JMS basic concepts you need to know about, so If you surf through Internet you will find a lot of tutorials about how to write a client/server JMS stuff, and I will not copy them. In order to fully understand this article I suggest you to take a bird view of Java Sun tutorial for JEE http://java.sun.com/javaee/5/docs/tutorial/doc/.

In the "normal" samples found in Internet you will find a typical solution, a simple java client and a simple java listener, but how everything works if the sender is an EJB Session bean and the JMS receiver is a MessageListener bean?

ok, lets start with the normal sample, a client sends a message to a MessageDrivenBean and the client continues with its life.


@MessageDriven(mappedName = "jms/MyQueue", activationConfig =  {
@ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
})
public class MyMessageDrivenBeanBean implements MessageListener {

public MyMessageDrivenBeanBean() {
}

public void onMessage(Message message) {
System.out.println("Received");
}
}


Here the server just gets the message and does nothing with it. The Client code is inside a Session bean method:

@Resource(name = "jms/MyQueue")
private Queue myQueue;
@Resource(name = "jms/MyQueueFactory")
private ConnectionFactory myQueueFactory;

public void sendReceiveMessage() {
try {
Connection conn = myQueueFactory.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
TextMessage textMessage = session.createTextMessage("Test");
MessageProducer producer = session.createProducer(myQueue);
producer.send(textMessage);
producer.close();
session.close();
conn.close();
} catch (JMSException ex) {
Logger.getLogger(ClientBean.class.getName()).log(Level.SEVERE, null, ex);
}
}


The client code does not have anything new; it just creates a connection, a jms session with this connection, a Text Message and sends it to the JMS pool.

What's next? We will send a callback in the client message and it'll try to listen for a response on it.

JMS Callback

        // Creates a callback queue
TemporaryQueue tempQueue = session.createTemporaryQueue();
textMessage.setJMSReplyTo(tempQueue);
producer.send(textMessage);

With this simple code we created a new Queue that will exists only for the current Connection, and will let us receive the response message, and here is the code to receive that message:

        conn.start();
MessageConsumer consumer = session.createConsumer(tempQueue);
Message response = consumer.receive(10000);
if (response != null) {
System.out.println("Response received");
} else {
System.out.println("Response not received due to timeout");
}
consumer.close();

We created a message consumer using the temporal queue and listen for a response on it for 10 seconds (10000 milliseconds), if the message response is null we will know the time out was reached.

JMS Callback sender

In the code above we created a Temporal Queue and assigned it to our message, so we will be able to get it from the server side and use it to send the response.

public void onMessage(Message message) {
try {
System.out.println("Received");

if (message.getJMSReplyTo() != null) {
Connection conn = myQueueFactory.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(message.getJMSReplyTo());
producer.send(session.createTextMessage("Response message"));
producer.close();
session.close();
conn.close();
}
} catch (JMSException ex) {
Logger.getLogger(MyMessageDrivenBeanBean.class.getName()).log(Level.SEVERE, null, ex);
}
}

Note: the connection factory was injected using the @Resource annotation.

Ok, you will get the JMSReplyTo and you'll send a message to that queue, this destination will be the temporally queue you created in the client side.

And that's it.... but... is not!, you must take care of two things to work this out, the first and most important... if your session bean (the sender) is in a transaction the message will not be send until the end of the transaction is reached, so the consumer will wait for the 10 secs and will always write the message "Timeout....", how can we fix this? just put the annotation "@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)" in the caller method. And second... if you don't put the line "conn.start()" just before the consumer receive method call the consumer will not start listening... and you will get a "Timeout" message again. So beware of this two lines, they drove me crazy until I figured out what was going on the first time.

Full code of the application is downloadable from Full project download it's a netbeans project so you will be able to open it with that IDE, or just browse for the code in the subprojects JMSSendResponse-ejb and JMSSendResponse-war (actually it does not nothing but calling the sessionbean).

8 comments:

Ilango said...

I tried to download your Implementation, but I could not do so. A broken link, maybe?
Could you place the project for download again, please?
thanks

Cross said...

Sorry about that, changed, please try it again.

or just click in the following link:
download code

Ilango said...

Thank you very much. I was able to download it.

Ilango said...

Since you used annotations, I assume that you used EJB3. Being naive about EJB 2.0 and having the knowledge that EJB 2.0 is associated with the concepts of local and remote intefaces, is your application EJB3 or EJB 2 compliant?
Thanks again

Ilango said...

Instead of setting temporary queue:
TemporaryQueue tempQueue = session.createTemporaryQueue();

can I create another queue so that I can forward the message to that queue by having
textMessage.setJMSReplyTo(newForwardedQueue);
And this Forwarded queue is Listened to by say, another servlet?

Cross said...

Ilango, sorry for the late answer, about the first question: "Yes, it's EJB 3.0 compliant", it's based on the specification.

About the second one, I think you could create the synchronization using another queue, but you will need to put additional information on the Object message to synchronize which answer is for which request, remember, the message queue could be used by other threads at the same time and you will need to deal with that fact. The temporally queue will solve all that problems easily.

Unknown said...

Thanks for taking the time to help, I really apprciate it.

http://dealzfirst.com/Just+My+Size+JMS-3.htm

Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!