Tuesday, August 5, 2008

Remotely send and consume messages with JMS in JBoss AS 5.0

This tutorial will show you how to create a queue in JBoss AS 5 (which uses JBoss Messaging 1.4.1), send a message to a remote queue and listen to the queue using a Message Driven Bean.

I have been playing with JMS queues and MDBs in JBoss AS 5 today to complete the clustering chapter of Hibernate Search in Action and went through more bumps than I should have. Let me share what I've learnt. Disclaimer, I am a JMS noob: this tutorial will go only over the basic concepts. In particular, I will not cover subjects like security, message persistence and so on. This tutorial can be partly reused by Hibernate Search users using clustering (just ignore the part when we send and consume messages as Hibernate Search does that under the hood).

First of all, get a fresh version of JBoss AS 5.0 (currently in Release Candidate 1) here. We will launch two instances of JBoss AS in parallel. If you are lucky enough, run them in two different machines (virtual image or not). If you are not, you will have to remap a few ports to avoid any conflict and this is what I will just describe now.

Go to JBOSS_HOME/server and copy the default directory as master.

cp -r default master

We now have two versions of the JBoss configuration. default will contain our slave instance configuration and master will contain our master instance configuration. Let's now change the default ports on the master configuration. In the master directory, open each file described and change the following ports

  • conf/jboss-service.xml from port 1099 to 1199 (JNDI)
  • conf/jboss-service.xml from port 8083 to 8084 (WebServices)
  • conf/jboss-service.xml from port 1098 to 1097 (RMI)
  • conf/jboss-service.xml from port 4446 to 4447 (Remoting)
  • deploy/ejb3-connectors-service.xml from port 3873 to 3874
  • deploy/jbossweb.sar/server.xml from 8080 to 8081 (HTTP)
  • deploy/jbossweb.sar/server.xml from 8009 to 8010 (Apache connector)
  • deploy/http-invoker.sar/META-INF/jboss-service.xml from port 8080 to 8081
  • deploy/jmx-remoting.sar/META-INF/jboss-service.xml from port 1090 to 1091
  • deploy/messaging/remoting-bisocket-service.xml from port 4457 to 4458
  • deploy/remoting-service.xml from port 4444 to 4443
  • deploy/remoting-service.xml from port 4445 to 4442

This step is only required if you use the same server to run both instances. There is an alternative and more elegant approach described here (thanks Julien for tweeting me the answer after I did all the hard work :) )

You need to make sure JBoss Messaging has a different ServerPeerID between different instances. Update deploy/messaging/messaging-service.xml, and set ServerPeerID to 1 (the default configuration uses 0)

We will now create the queue in the master instance. Open deploy/messaging/destinations-service.xml, and add the following fragment

<mbean code="org.jboss.jms.server.destination.QueueService"

      name="jboss.messaging.destination:service=Queue,name=hibernatesearch"

      xmbean-dd="xmdesc/Queue-xmbean.xml">

      <depends optional-attribute-name="ServerPeer">jboss.messaging:service=ServerPeer</depends>

      <depends>jboss.messaging:service=PostOffice</depends&gt;

</mbean>

A queue is an MBean object, you can refine it's configuration as explained in the JBoss Messaging documentation. As a start, you can simply copy the fragment and replace hibernatesearch by the name of your queue. The queue will be available in JNDI under queue/hibernatesearch (this can be overridden if needed). If you start the master instance of JBoss AS (go to JBOSS_HOME/bin and launch ./run.sh -c master), you should see the following lines in the console

19:27:46,403 INFO [QueueService] Queue[/queue/hibernatesearch] started, fullSize=200000, pageSize=2000, downCacheSize=2000

The next step is to publish a message from the default JBoss AS instance into the queue. Here is a simple servlet doing so:

@Override

public void doGet(HttpServletRequest request, HttpServletResponse response)

    throws ServletException, IOException {

    QueueConnectionFactory factory;

    Queue queue;

    try {

        Properties jndiProps = new Properties();

        jndiProps.setProperty("java.naming.provider.url", "jnp://localhost:1199")

        InitialContext initialContext = new InitialContext( jndiProps );

        factory = (QueueConnectionFactory) initialContext.lookup( "/ConnectionFactory" );

        queue = (Queue) initialContext.lookup( "queue/hibernatesearch" );

    }

    catch (NamingException e) {

        throw new Exception( "Unable to lookup queue", e );

    }


    QueueConnection cnn;

    QueueSender sender;

    QueueSession session;

    try {

        cnn = factory.createQueueConnection();

        session = cnn.createQueueSession( false, QueueSession.AUTO_ACKNOWLEDGE );


        TextMessage message = session.createTextMessage();

        message.setText("Pass it along");

        sender = session.createSender( queue );

        sender.send( message );

        session.close();

    }

    catch (JMSException e) {

        throw new Exception( "Unable to send message to JMS queue", e );

    }

    finally {

        try {

            if (cnn != null) cnn.close();

        }

        catch ( JMSException e ) {

            log.warn( "Unable to close JMS connection", e );

        }

    }

}

A few things are noticeable here:

  • we override the JNDI URL to point to the master JNDI host and port: if you run the master instance on a different machine (without remapping ports), the URL will look like jnp://master.host:1099.
  • to look up the factory we use /ConnectionFactory. Do not use java:/ConnectionFactory as this value points to your local instance (I lost a few hours here, thanks Clebert for the hand!). If you want to change this name, open deploy/messaging/connection-factories-service.xml and add a new binding under the JNDIBindings attribute.
  • always close your connection in a finally block to avoid connection leaks

On the master side, you can deploy a MDB (a trivial task with EJB 3 as no deployment descriptor is needed).

@MessageDriven(activationConfig = {

    @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),

    @ActivationConfigProperty(propertyName="destination", propertyValue="queue/hibernatesearch"),

    @ActivationConfigProperty(propertyName="DLQMaxResent", propertyValue="1")

} )

public class MDBPassOnController implements MessageListener {

    private final Logger log = LoggerFactory.getLogger( MDBPassOnController.class );


    public void onMessage(Message message) {

        if ( !( message instanceof TextMessage ) ) {

            log.error( "Incorrect message type: {}", message.getClass() );

            return;

        }

        TextMessage textMessage = (TextMessage) message;

        System.out.println( textMessage.getText() );

    }

}

The new embedded console (to come with JBoss AS 5 final) based on a stripped down version of JBoss ON will make some of these steps much easier but now that you have gone the roots way, don't you feel stronger? :)

8 comments:

AnjanBacchuDev said...

hi there,

nice post.

"We know have two versions" : you mean "We NOW have two versions"

BR,
~A

Unknown said...

Yes, this was a typo. Fixed now. Thanks.

Anonymous said...

Do you know any way to send remotely messages from a C/C++ program with JMS in JBOSS AS?

Unknown said...

"Do you know any way to send remotely messages from a C/C++ program with JMS in JBOSS AS?"


Nope, but ask in the JBoss messaging forum. If there is a way, they know it.

Anonymous said...

Hey Emmanuel , thanks for this post. I had just recently gathered all this info the hard way except for the remote setpup part. Nice to bump into your post - especially the parts where you mentioned the names of the files that needed the changes and the remote connection part.

monZta said...

hi bro..that's a nice tutorial.
Can u explain how we can use JMS over the Spring framework and achieve this?

Unknown said...

Sorry I don't use Spring.

Anonymous said...

Hi Emmanual,
is it possible to write a mdb to read/consume messages in remote jboss queue server?