Friday, September 6, 2013

JMS REST tutorial - HORNETQ REST Interface tutorial


Introduction
HORNETQ is the defualt messaging system provided by JBOSS Application Server. In one of my previous tutorial I covered how to configure, produce and consume Messages through JMS API using HORNETQ as provider. You can find the tutorial @ HERE.
NOTE: This tutorial uses queue named 'restInterfaceQueue'. To know how to create this queue please take a look into the above tutorial.
This tutorial will focus on creating and consuming messages from HORNETQ using REST API.
Why would one ever need to produce and consume messages through REST?
Well!!! I would say to support CROSS LANGUAGE CLIENTS.... and also to provide web based API for messaging. With these capabilities one no longer needs producer and consumer to be through JMS API. So anyone who understands HTTP can produce and consume messages. This one big advantage of REST interface. HORNETQ provides a very good REST interface. Some of the features of REST interface provided by HORNETQ are given below
  • Avoid posting of duplicate messages
  • Mix and match JMS and REST producers and consumers
  • Acknowledgement and auto acknowledgement 

Technologies Used
  • JAVA 
  • J2EE
  • RESTEasy
  • HORNETQ 
  • HORNETQ REST Interface
  • Maven  

Coplete list of features and more information about REST interface and HORNETQ can be found @here. Lets get started!!!! 
Boot strapping HORNETQ with REST
To use HORNETQ REST interface in our application we first need to bootstrap it. This can be done by adding the below listener in web.xml

  org.hornetq.rest.integration.RestMessagingBootstrapListener
 

REST Interface Configuration
We should add some configuration information for REST interface. This can be done in an xml file under WEB-INF/classes. The name of the file should be mentioned in web.xml using rest.messaging.config.file context param

hornetq-rest.xml

   0
   false
   true
   
   true
   topic-push-store
   queue-push-store
   10
   1
   300
   0
The entries in above XML file are explained below
Server-in-vm-id : This is to differentiate different HORNETQ servers running in
same VM. We can default it to zero
Use-link-headers: To tell HORNETQ how to publish the links. By default it publishes
using cutoms headers. This tutorial uses the default behaviour so we can mark it as
false
default-durable-send: Used to identify if the posted message needs to persisted in
DB.We will mark it as false. This default behaviour can be overriddden by sending
durable custom header as true
dups-ok Should duplicate messages be allowed?
client-window-size Used to identify buffering capabilities. By specifying it as zero we are saying we don't want any buffering to happen for delivery of messages.
Now lets take a look at our complete web.xml


 
  rest.messaging.config.file
  hornetq-rest.xml
 

 
  resteasy.resources
  itsvenkis.blogspot.in.rest.services.OrderSrvc
 

 
  resteasy.servlet.mapping.prefix
  /resteasy/hornetq
 

 
  org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
 

 
  org.hornetq.rest.integration.RestMessagingBootstrapListener
 

 
  resteasy
  
   org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
  
 

 
  resteasy
  /resteasy/hornetq/*
 

 
 
    BASIC
 
 
 
  
   Rest-Messaging
   /resteasy/hornetq/queues/*
   GET
   POST
   PUT
   HEAD
   DELETE
  
  
   admin
  
 


We will reuse the modified OrderSrvc.java class here. It has single method which will consume XML message and puts it in Queue using JMS API.
package itsvenkis.blogspot.in.rest.services;

import javax.annotation.Resource;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.log4j.Logger;

/**
 * @author itsvenkis
 *
 */
@Path("/postOrder")
public class OrderSrvc {
 
 private final Logger log = Logger.getLogger(OrderSrvc.class);
 /*
  * Map a JNDI connection factory name.As this service is going to be in same
  * JAVA VM as JMS use "java:/ConnectionFactory". You can find this entry in JBOSS server
  * standalone-full.xml configuration file. 
  */
 @Resource(mappedName = "java:/ConnectionFactory")
 private ConnectionFactory connectionFactory;
 
 /*
  * Map the queue. Note how the queue name is defined here. This should match the context
  * of JNDI defined which is "java" namespace followed by the entry name as given in
  * standalone-full.xml configuration file.
  */
 @Resource(mappedName = "java:/queue/restInterfaceQueue")
 private Queue orderQueue;
 
 @POST
 @Consumes(MediaType.APPLICATION_XML)
 public Response postOrder(String xmlStr) throws JMSException{
  log.debug("Started processing the order....");
  Connection con = null;
  try{
   //In real world you may want to do this only once
   con = connectionFactory.createConnection();
   Session session = con.createSession(false,
     Session.AUTO_ACKNOWLEDGE);
   MessageProducer producer = session.createProducer(orderQueue);
   log.debug("starting HornetQ-JMS connection");
   con.start();
   log.debug("started HornetQ-JMS connection");
   //HORNETQ REST accepts only HTTP or OBJECTMESSAGE
   ObjectMessage objMsg = session.createObjectMessage();
   objMsg.setStringProperty("http_content$type",
     "application/xml");
   objMsg.setObject(xmlStr);
   producer.send(objMsg);
   log.debug("Sent message HornetQ-JMS to QUEUE");
  }catch(JMSException e){
   log.error("Failed to push order to the queue ", e);
  }finally{
   if(con != null){
    try{
     con.close();
    }catch(JMSException e){
     log.error("Unexpected error while trying to close the connection", e);
    }
   }
  }
  return Response.status(200).entity("Received XML").build();
 }

}

As we are using JEE 6 i.e. CDI in OrderSrvc.java we are required to turn it on by using a simple beans.xml under WEB-INF directory. It can be a empty XML file
beans.xml





pom.xml

 4.0.0
 in.itsvenkis.blogspot
 JMS-REST-EXAMPLES
 0.0.1-SNAPSHOT
 war
 A simple HORNETQ examples to demonstrate REST interface
 
  
  jms-rest-examples
  
   
    org.apache.maven.plugins
    maven-war-plugin
    
     
      
       org.hornetq
      
     
    
   
  
 
 
  
   jboss
   http://repository.jboss.org/maven2
  
 
 
  
   org.jboss.resteasy
   resteasy-jaxrs
   3.0.0.Final
  
  
   org.jboss.resteasy
   resteasy-jaxb-provider
   3.0.0.Final
  
  
   org.jboss.spec.javax.jms
   jboss-jms-api_1.1_spec
   1.0.1.Final
  
  
   log4j
   log4j
   1.2.17
  
  
   org.hornetq.rest
   hornetq-rest
   2.3.5.Final
  
 


project structure
dependencies

Now build and deploy the application. We are now ready to consume and produce messages using HORNETQ REST interface.
DEMO
Start your server after deploying the war file

Now post our first message to the queue named "restInterfaceQueue" using REST WS call in OrderSrvc.java. It uses regular JMS API to post the message to queue. url to post the xml message using restclient firefox plugin 'http://localhost:8080/jms-rest-examples/resteasy/hornetq/postOrder'

XML posted

Mumbai

300

f238
OR908765


The message counter for the queue should be 1 now as shown in admin console of our JBOSS server.
Now let us consume this message using REST interface. First we will do a GET request to 'http://localhost:8080/jms-rest-examples/resteasy/hornetq/queues/jms.queue.restInterfaceQueue'. To understand how this URL is formed lets break it into pieces
  •  http://localhost:8080/jms-rest-examples- your application context path
  • /resteasy/hornetq- As mapped for RESTEasy path in web.xml
  • /queues- All REST interface URLS should have queues followed by the original queue name itself which is jms.queue.restInterfaceQueue
As you may have already guessed HORNETQ will retrun us some bunch of custom headers with the GET request. Custom headers returned are shown below
Notice the msg-pull-consumer header returned. We will use that URL to create a service so that we can pull the message from the queue. To create a service do a empty post to the URL as mentioned by msg-pull-consumer header

Below are the custom headers returned from above request.

Notice the 'msg-consume-next' header value returned.Do a POST request to this URL to consume/pull the message from the queue.
below is the consumed message

This network roundtrip is just done once and to consume next message we need not issue the GET request again and start the cycle. Instead we can use 'msg-consume-next' header returned to consume next message and do the polling.If by any chance you miss the URL you can issue a get again to get url values as headers.
Now let us look at Produce an message using REST interface
Do a get request to 'http://localhost:8080/jms-rest-examples/resteasy/hornetq/postOrder' and use 'msg-create' header value and do a post

Notice the headers returned from the above POST request.'msg-create-next: http://localhost:8080/jms-rest-examples/resteasy/hornetq/queues/jms.queue.restInterfaceQueue/create'. Do a post to the URL returned as mentioned by 'msg-create-next' to create a message in the queue

This will create a new message in the queue.
This brings us to the end of the tutorial which covers how to post message and consume message using REST interface.
I was really excited to see producers and consumers via REST interface in JMS queue. If your application needs to support cross platform messaging I would strongly suggest to use REST interface.
Hope this tutorial helps you to understand REST interface. If you have any questions please do not hesitate to ask it in comments section. I will be very happy to answer them.
Dear Readers, kindly like us on facebook or follow me on Google+

2 comments:

  1. Could you post the project on github or something?

    Thanks!

    ReplyDelete
  2. Thanks for your interest in the tutorial. I will add the code to github asap. In the mean time the code is included in the tutorial post itself.

    ReplyDelete