S

Table of Contents

Introduction
JMS Architecture

    JMS Messaging Models
JMS Example
    JMS Provider: JBoss configuration
    Core JMS API
    Client: JMS Producer
    Client: JMSConsumer
    JMS Unified Client API
    Execute the example
    Client: JMSConsumer revisited

    JMS Topics instead of Queues
Reliability - Durable Subscriptions

Message Driven Beans
    Client: JMS Consumer revisited
    Transactions
    Distributed Transactions
Summary

References

Introduction

JMS or Java Messaging System is an asynchronous communication API for Java applications. The JMS Specification is defined in JSR 914 [1]. The Java Message Service was developed by Sun Microsystems to provide a means for Java programs to access enterprise messaging systems or message oriented middleware (MOM). They provide a mechanism for integrating applications in a loosely coupled way. They provide asynchronous delivery of data between applications on a store and forward basis; i.e., the applications do not communicate directly with each other, but instead communicate with the MOM, which acts as an intermediary [2], thus handling all network communication details for you. If e.g. the network connection is not available, the MOM will store the message until the connection becomes available, and then forward it to the destination. Thus, the JMS API is characterised as asynhronous and reliable (it can ensure that a message is delivered once and only once).
Even though Messaging follows the same principles as electronic mail (email), messaging differs from electronic mail, which is a method of communication between people or between software applications and people. Messaging is used for communication between software applications or software components.

In the following we 'll show a simple Java "Hello JMS" message exchange application using JMS 1.1.

JMS Architecture

The main players of the JMS architecture are the following [2, 3]:

JMS Messaging Models

JMS has two messaging models [3]: Point-to-Point (P2P) and Publish-Subscribe (Pub-Sub). P2P is a traditional one-to-one queueing mechanism, i.e. although multiple Consumers can listen on a queue, only one Consumer receives a particular message. Producers send messages to a queue, and the JMS Provider (aka JMS Server) delivers each message sequentially to a Consumer listening on the queue. The following figure shows the relationships between Point-to-Point Producers and Consumers.

p2p

Figure 1 JMS Queue

Publish-Subscribe is a one-to-many broadcast model, similar to a newsgroup or a bulletin board or an RSS newsfeed. Producers publish messages to a topic, and the JMS Server delivers messages sequentially to those Consumers subscribed to that topic. The following figure shows the relationships between Pub-Sub Producers and Consumers.

topic

Figure 2 JMS Topic

The biggest difference between P2P and Pub-Sub is that in the Publish-Subscribe Model, all Consumers that subscribe to a Topic can receive all messages published to that topic, while with Point-to-Point, only one Consumer on a queue receives a particular message.

Even though JMS is inherently asynchronous, the JMS specification allows for messages to be consumed in either of two ways [10]:

A JMS message consists of a header, properties and a body [2,10]. The header is a standard set of fields that are used by both clients and providers to identify and route messages; it contains a message id, destination, timestamp, priority etc. The properties provide a facility for adding optional header fields to a message e.g. for categorisation or classification, to provide compatibility with other messaging systems, or to use them to create message selectors. JMS defines a standard set of properties that are optional for providers to supply. The body contains the content to be delivered to a receiving application. The body or content of the JMS message can be many things, e.g. XML, a JavaBean (i.e. an object) etc. JMS API provides five message body formats: text, map, bytes, stream, and object. To send an object as a JMS Message, it must obey the following rules:

JMS Example

Now that we know the basics of JMS, we 'll create the players of our JMS application:

JMS Provider: JBoss configuration

To execute and test the program, you need access to a vendor implementation of JMS. Most Java 2 Enterprise Edition (J2EE) application servers provide an implementation of JMS. JBoss is such an application server. JBoss Messaging provided by default with JBoss 4.2 supports the latest JMS 1.1 specification and it offers vastly improved performance in both single node and clustered environments compared to the older JBossMQ JBoss JMS provider [9]. However, this is something that doesn't bother us as users of the JBoss JMS service.

To setup a JMS message queue or topic in JBoss is very easy. There are actually 3 ways to do this [3,7,8,9]:

  1. Using the JMX console: Start the JBoss application server (%JBOSS_HOME%/bin/run.bat or $JBOSS_HOME/bin.run.sh) and open the JMX console (http://localhost:8080/jmx-console). Click on the service=DestinationManager link (under the jboss.mq heading) to see the JBossMQ DestinationManager MBean page. Look for the createQueue( ) operation and enter jms/MyQueue for the first parameter (the J2EE JNDI name would then be java:comp/env/jms/MyQueue), and queue/MyQueue for the second parameter (the JBoss-specific JNDI name). The JMX-console has the advantage that it is easy to use and you can dynamically specify JMS resources; the disadvantage though is that your changes are lost once you shut down JBoss
  2. Add an <mbean> element for the queue to $JBOSS_HOME/server/default/deploy/jms/jbossmq-destinations-service.xml file. This file is part of the core JBoss deployment and contains JBoss-specific default test Queues and Topics. This has the advantage that the changes survive JBoss startup/shutdowns; the disandvantage is however that you mix-up your destinations with JBoss internal destinations.
  3. <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=MyQueue">
      <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager</depends>
    </mbean>

  4. The best solution is to create your own service descriptor file (post-fixed with -service.xml) that resides in $JBOSS_HOME/server/default/deploy and add an <mbean> element for the queue. This way your changes survice JBoss startup/shutdowns and  you don't mix-up your destinations with JBoss internal destinations. Co-mingling destinations is bad because each time you upgrade to a new version of JBoss, you have to re-add the <mbean> elements to your service descriptor to make things work again.

    <?xml version="1.0" encoding="UTF-8"?>
    <server>
       <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=MyQueue">
         <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager</depends>
       </mbean>
    </server> 

That's it! Please note that destination without a configured SecurityManager or without a SecurityConf will default to role guest with read=true, write=true, create=false. If you want to setup a SecurityManager, you should modify your mbean similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<server>
   <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=MyQueue">
     <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager</depends>
     <depends optional-attribute-name="SecurityManager">jboss.mq:service=SecurityManager</depends>
        <attribute name="SecurityConf">
            <security>
                <role name="guest" create="false" read="true" write="true"/>
                <role name="senders" create="false" read="false" write="true"/>
                <role name="receivers" create="false" read="true" write="false"/>
            </security>
        </attribute>
   </mbean>
</server> 

We shall do one last optional thing to be able to debug our JMS example application. JBoss uses an internal DBMS, an instance of HSQLDB, to store the JMS messages until they are consumed. Of course, it is very easy to setup another DBMS [7,8] but we don't bother with the details in this article. What you can do however, if you have not setup another DBMS, is to enable the HSQL MBean to accept TCP/IP connections [7,8]. Open the hsqldb-ds.xml file which you’ll find in the deploy directory and which sets up the default datasource. Near the top of the file, you’ll find the connection-url element. Make sure the value is set to jdbc:hsqldb:hsql://localhost:1701 and that any other connection-url elements are commented out.

<!-- The jndi name of the DataSource, it is prefixed with java:/ -->
<!-- Datasources are not available outside the virtual machine -->
<jndi-name>DefaultDS</jndi-name>
<!-- for tcp connection, allowing other processes to use the hsqldb
database. This requires the org.jboss.jdbc.HypersonicDatabase mbean. -->
<connection-url>jdbc:hsqldb:hsql://localhost:1701</connection-url>
<!-- for totally in-memory db, not saved when jboss stops.
The org.jboss.jdbc.HypersonicDatabase mbean is unnecessary
<connection-url>jdbc:hsqldb:.</connection-url>
-->
<!-- for in-process db with file store, saved when jboss stops. The
org.jboss.jdbc.HypersonicDatabase is unnecessary
<connection-url>jdbc:hsqldb:${jboss.server.data.dir}/hypersonic/localDB
</connection-url>
-->


Then scroll down to the bottom of the file, and uncomment the MBean declaration for the Hypersonic service.

<mbean code="org.jboss.jdbc.HypersonicDatabase" name="jboss:service=Hypersonic">
  <attribute name="Port">1701</attribute>
  <attribute name="Silent">true</attribute>
  <attribute name="Database">default</attribute>
  <attribute name="Trace">false</attribute>
  <attribute name="No_system_exit">true</attribute>
</mbean>


Open the jmx-console again (you need to restart JBoss), and click on the service=Hypersonic link which you’ll find under the section jboss. Invoke the startDatabaseManager() MBean operation. The HSQLDB Manager is executed.

hsqldb manager
Figure 3 HSQL DB Manager

As you can see, JBoss uses 5 tables for JMS handling, which are shown in the following ER diagram.

JMS ER model
Figure 4 JMS Entity-Relationship model used by JBoss

Each time you execute the JMSProducer you will see a new row in the JMS_MESSAGES table like so:

MESSAGEID DESTINATION TXID TXOP MESSAGEBLOB
1 QUEUE.MyQueueXA A SpyTextMessage {Header ...}

When you run the JMSConsumer, the message is consumed, i.e. removed from the table.

Core JMS API

The JMS API resides in the javax.jms package. These are the most important classes and interfaces for our purposes:
JMS classes
Figure 5 JMS API - class diagram

JMS State Transition Diagram
Figure 6 JMS State Transition Diagram
To be able to compile the classes of our example, you will need the JMS classes in the package javax.jms and the Java Naming and Directory Interface (JNDI) classes in the package javax.naming

Client: JMSProducer

We'll take the following steps to send a message:
  1. Look up a ConnectionFactory using JNDI.
  2. Get a Connection from the ConnectionFactory.
  3. Create a Session associated with the Connection.
  4. Look up a Destination using JNDI.
  5. Create a Message Producer tied to the Destination.
  6. Create a Message.
  7. Send the Message.
  8. Tear down the Message Producer, Session, and Connection.
JMS requires a lot of low-level tedious API calls to send a message, so we encapsulate everything into a JmsProducer class. To compile this and the following classes you need the jbossall-client.jar found inside $JBOSS/client directory.

Listing 1. JmsProducer.java
package jms.producer;

import javax.jms.*;

import jms.ServiceLocator;
import jms.ServiceLocatorException;

/**
* <code>JmsProducer</code> encapsulates sending a JMS Message.
*
*/
public class JmsProducer {
private static final String CONNECTION_FACTORY ="ConnectionFactory";
private static final String MY_QUEUE = "queue/MyQueue";
private static final String HELLO_MSG = "Hello JMS";

public static void main(String args[]) {
JmsProducer.sendMessage(HELLO_MSG, CONNECTION_FACTORY, MY_QUEUE);
}

/**
* Making the default (no arg) constructor private
* ensures that this class cannnot be instantiated.
*/
private JmsProducer() {
}
/**
*
* @param txtMessage The message (xml file) to send via JMS
* @param connectionFactoryJndiName connection factory JNDI name
* @param destinationJndiName destination JNDI name
* @throws JmsProducerException
*/
public static void sendMessage(String txtMessage,
String connectionFactoryJndiName, String destinationJndiName)
throws JmsProducerException {
Connection connection = null;
try {
ConnectionFactory connectionFactory = null;
Session session = null;
Destination destination = null;
MessageProducer messageProducer = null;
TextMessage message = null;

// 1. Look up a ConnectionFactory using JNDI.
connectionFactory = ServiceLocator
.getJmsConnectionFactory(connectionFactoryJndiName);

// 2. Get a Connection from the ConnectionFactory with default user id.
connection = connectionFactory.createConnection();

// 3. Create a Session associated with the Connection.
// 1st parameter: true if Session is transacted
// 2nd parameter: Acknowledgement method
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// 4. Look up a Destination using JNDI.
destination = ServiceLocator.getJmsDestination(destinationJndiName);

// 5. Create a Message Producer tied to the Destination.
messageProducer = session.createProducer(destination);

// 6. Create a Message.
message = session.createTextMessage(txtMessage);

// 7. Send a Message.
messageProducer.send(message);
} catch (JMSException je) {
throw new JmsProducerException(je);
} catch (ServiceLocatorException sle) {
throw new JmsProducerException(sle);
} finally { // 8. Tear down the Message Producer, Session, and Connection.
if (connection != null) {
try { // automatically session and producer are closed
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

The JMS ConnectionFactory and Connection are both JNDI-based resources, so we've encapsulated the JNDI lookups with the ServiceLocator class which is presented later. The ServiceLocator.getJmsConnectionFactory( ) call finds and instantiates the ConnectionFactory to gain access to the JMS provider. The ConnectionFactory creates a Connection. Use the following parameters in the Connection.createSession( ) call to create the Session:
After we've created the Session, the ServiceLocator.getJmsDestination( ) call finds the Destination, a proxy to the actual destination (a queue, in this case, because of how it's deployed) on the JMS server. The Session's createProducer( ) method creates the MessageProducer, which sends messages to a destination asynchronously. The call to the Session's createTextMessage( ) method creates a TextMessage method that contains the String passed into the JmsProducer's sendMessage( ) method. The MessageProducer's send( ) method sends the message to the destination, and we wrap up by closing the MessageProducer, Session, and Connection.
The careful reader may have noticed that we don't define the connection factory "ConnectionFactory" in any configuration file in JBoss. This is true. "ConnectionFactory" is the default name for a connection factory provided by JBoss [8].

Client: JMSConsumer

The code for the JMSConsumer is similar.

Listing 2. JmsConsumer.java
package jms.consumer;

import javax.jms.*;

import jms.ServiceLocator;
import jms.ServiceLocatorException;

/**
* <code>JmsConsumer</code> encapsulates receiving a JMS Message.
*
*/
public class JmsConsumer {
private static final String QUEUE_CONNECTION_FACTORY ="ConnectionFactory";
private static final String MY_QUEUE = "queue/MyQueue";

public static void main(String args[]) throws Exception {
JmsConsumer.receiveMessage(QUEUE_CONNECTION_FACTORY, MY_QUEUE);
}

/**
* Making the default (no arg) constructor private
* ensures that this class cannnot be instantiated.
*/
private JmsConsumer() {
}
/**
* Receives a JMS message.
* @param connectionFactoryJndiName connection factory JNDI name
* @param destinationJndiName destination JNDI name
* @throws JmsProducerConsumerException
*/
public static void receiveMessage(
String connectionFactoryJndiName, String destinationJndiName)
throws JmsConsumerException {
Connection connection = null;
try {
ConnectionFactory connectionFactory = null;
Session session = null;
Destination destination = null;
MessageConsumer messageConsumer = null;
TextMessage textMessage = null;

// 1. Look up a ConnectionFactory using JNDI.
connectionFactory = ServiceLocator
.getJmsConnectionFactory(connectionFactoryJndiName);

// 2. Get a Connection from the ConnectionFactory with default user id.
connection = connectionFactory.createConnection();

// 3. Create a Session associated with the Connection.
// 1st parameter: true if Session is transacted
// 2nd parameter: Acknowledgement method
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// 4. Look up a Destination using JNDI.
destination = ServiceLocator.getJmsDestination(destinationJndiName);

// 5. Create a Message Consumer tied to the Destination.
messageConsumer = session.createConsumer(destination);

// 6. Receive a Message.
connection.start();
Message message = messageConsumer.receive();
if (message instanceof TextMessage) {
textMessage = (TextMessage) message;
System.out.println("Message received: " + textMessage.getText());
}
} catch (JMSException je) {
throw new JmsConsumerException(je);
} catch (ServiceLocatorException sle) {
throw new JmsConsumerException(sle);
} finally { // 7. Tear down the Message Consumer, Session, and Connection.
if (connection != null) {
try {
// messageProducer.close();
// session.close();
connection.close(); // automatically session and producer are closed
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}


Notice the important connection.start(); command in step 6 that causes message delivery to begin. It activates the delivery of messages from the JMS server [2].Without this command, your messages won't be delivered to the consumer. If you want to stop message delivery temporarily without closing the connection, you call the stop() method.

The receive() method can be used in several ways to perform a synchronous receive. If you specify no arguments or an argument of 0, the method blocks indefinitely until a message arrives. For a simple client program, this may not matter. But if you do not want your program to consume system resources unnecessarily, use a timed synchronous receive.

Message message = messageConsumer.receive(1); // wait for 1 ms
Message message = messageConsumer.receiveNoWait(); // receive a message only if one is available

JMS Unified Client API

As of JMS 1.1, the Unified API is now available and works with both the Publish-Subscribe and Point-to-Point models [2,3,7-9]. The JMS 1.1 specification encourages you to write all your new JMS code by using the Unified API, since the Queue and Topic-based APIs could become deprecated in a future version of JMS. However, the model-specific APIs will last for a while because so much production code uses them. Don't get the wrong idea, the physical JMS Queues and Topics are not going away. You'll still use them with the Unified API. The Unified API resembles the Queue and Topic-based APIs because it follows the same calling sequence. The big difference is that your code doesn't really care if you use Queues or Topics. All you need to provide are the JNDI names for Queue/Topic Connection Factory and Queue/Topic.

We recommend using the Unified API because it simplifies development; you don't need the Queue or Topic APIs anymore. You'll still send messages to a Queue or Topic (depending on the JNDI name you use), but this is now just a configuration issue. Your code becomes much more generic because it no longer uses queue or topic-specific API calls. With the Unified API, you always access JMS-based resources in the same way, regardless of your JMS messaging model.

We've wrapped the JMS API calls in the JmsProducer class to make it easy to send a JMS message. The JmsProducer uses the ServiceLocator utility class to find the ConnectionFactory and the JMS Destination using JNDI.

Listing 3. ServiceLocator.java
package jms;

import java.util.Properties;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.naming.*;

/**
* <code>ServiceLocator</code> encapsulates JNDI lookups to make it
* easier to access JNDI-based resources (EJBs, DataSources,
* JMS Destinations, and so on).
*
*/
public class ServiceLocator {
private static Properties environment = null;

static { // can be found in <JBoss>/server/default/conf/jndi.properties
environment = new Properties();
environment.setProperty("java.naming.provider.url", "localhost:1099");
environment.setProperty("java.naming.factory.initial",
"org.jboss.naming.NamingContextFactory");
environment.setProperty("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
}

/**
* Making the default (no arg) constructor private
* ensures that this class cannnot be instantiated.
*/
private ServiceLocator() { }


/**
* Gets a JMS <code>ConnectionFactory</code> using the given JNDI name.
*
* @param jmsConnectionFactoryJndiName The JMS <code>ConnectionFactory</code>'s JNDI name.
* @return ConnectionFactory The JMS <code>ConnectionFactory</code>.
* @throws ServiceLocatorException If there are JNDI lookup problems.
* @see javax.jms.ConnectionFactory
*/
public static ConnectionFactory getJmsConnectionFactory(String jmsConnectionFactoryJndiName)
throws ServiceLocatorException {
ConnectionFactory jmsConnectionFactory = null;

try {
Context ctx = new InitialContext(environment);

jmsConnectionFactory = (ConnectionFactory) ctx.lookup(jmsConnectionFactoryJndiName);
} catch (ClassCastException cce) {
throw new ServiceLocatorException(cce);
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
}

return jmsConnectionFactory;
}

/**
* Gets a JMS <code>Destination</code> using the given JNDI name.
*
* @param jmsDestinationJndiName The JMS <code>Destination</code>'s JNDI name.
* @return Destination The JMS <code>Destination</code>.
* @throws ServiceLocatorException If there are JNDI lookup problems.
* @see javax.jms.Destination
*/
public static Destination getJmsDestination(String jmsDestinationJndiName)
throws ServiceLocatorException {
Destination jmsDestination = null;

try {
Context ctx = new InitialContext(environment);

jmsDestination = (Destination) ctx.lookup(jmsDestinationJndiName);
} catch (ClassCastException cce) {
throw new ServiceLocatorException(cce);
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
}

return jmsDestination;
}
}


The getJmsConnectionFactory( ) and getJmsDestination( ) methods, respectively, encapsulate a JNDI lookup for a JMS ConnectionFactory or JMS Destination. Both methods have the following steps in common:
Notice the environment properties variable that is passed in the InitialContext in order to be able to access JBoss' JNDI lookup.

Execute the example

That's it! This is the simplest JMS application you could write! Compile and run the JmsProducer first; open the HSQL DB Manager and make sure that a new message has been inserted into the JMS_MESSAGES table; run the JmsConsumer to see the "Hello JMS" message to your output; make sure that the new row has disappeared from the JMS_MESSAGES table. Go for a beer!

Client: JMSConsumer revisited

You might have noticed, that JmsConsumer consumes only one message at a time, and that if the JmsProducer sends more messages, they are stored in the JMS_MESSAGES table, until the JmsConsumer runs again and again to consume them. This is synchronous behaviour. We can easily fix this, by creating a Listener to the queue (asynchronous behaviour). The code is shown in Listing 4. The JmsListener class implements the MessageListener interface which provides only one method onMessage(). MessageListener is an interface with a single method -- onMessage(Message) -- that provides asynchronous receipt andd processing of messages. Inside this method you process the received messages.

Listing 4. JmsListener.java
package jms.consumer;
import javax.jms.*;
/**
* <code>JmsListener</code> is the message listener of JMS Messages.
* It implements the specified <code>onMessage</code> method.
*/
public class JmsListener implements MessageListener {

public JmsListener( ) { }

/**
* Receives a JMS message.
* @param message a JMS message
*/
public void onMessage( Message message ) {
// retrieve and process message
try {
if ( message instanceof TextMessage ) {
TextMessage textMessage = (TextMessage) message;
System.out.println(textMessage.getText());
} else {
System.out.println( "Expecting " +
  "TextMessage object, received " + message.getClass().getName() );
}
 } // process JMS exception from message
catch ( JMSException jmsException ) {
  jmsException.printStackTrace();
}
} // end method onMessage
}

You need to modify the JmsConsumer class a bit to delegate the processing of the JMS messages to the JmsListener. The changes are shown in Listing 5. You replace step 6:

// 6. Receive a Message.
Message message = messageConsumer.receive();
if (message instanceof TextMessage) {
    textMessage = (TextMessage) message;
    System.out.println("Message received: " + textMessage.getText());
}


with the steps shown in Listing 5, i.e. by setting the message listener of the messageConsumer to be the JmsListener (step 6). Because it takes some time to consume a JMS message, we simply wait for 3'' before closing the connection.

Listing 5. JmsConsumer.java
// 5. Create a Message Consumer tied to the Destination.
messageConsumer = session.createConsumer(destination);

// 6. Initialize and set message listener
messageConsumer.setMessageListener(new JmsListener());    

// 7. Receive a Message.
connection.start();

// Give it some time to consume the messages
try {
    Thread.sleep(3000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

// 8. Tear down the Message Consumer, Session, and Connection.
//messageConsumer.close();
//session.close();
connection.close();

That's it. Run the JmsProducer again a couple of times, check the JMS_MESSAGES table to see that more than one JMS messages have entered the queue, and then execute the modified JmsConsumer. You will notice that this time all JMS messages are consumed, before the program exits, and that you see the message "Hello JMS" more than once in your console.

JMS Topics instead of Queues

As we have already mentioned, deciding on a topic or a queue is simply a configuration issue.  Here is the service descriptor for the topic.

<?xml version="1.0" encoding="UTF-8"?>
<server>
   <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=MyQueue">
     <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager</depends>
   </mbean>
   <mbean code="org.jboss.mq.server.jmx.Topic" name="jboss.mq.destination:service=Topic,name=MyTopic">
     <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager</depends>
   </mbean>
</server> 

That's it! The java code, in both JMSProducer and JMSConsumer, remains the same, apart, of course, from the reference which has to change to topic/MyTopic instead of queue/MyQueue! This is the power of the unified API.
private static final String MY_TOPIC = "topic/MyTopic";
You 'll see the difference only when you run the programs again. Run (multiple) instances of JmsConsumer (first), then as you send messages by executing JmsProducer, all JmsConsumer instances receive each message sent by the JmsProducer. If you start the JmsProducer first and then the JmsConsumers, you will notice that the consumers receive no messages. This is another dfference among a queue and a topic, i.e. a consumer subscribing to a topic cannot view past messages. Additionally, notice that you won't see a record in the JMS_MESSAGES table. Topic messages are delivered in the same way a television show is delivered; unless you have the TV on and are watching the show, you will miss it.
Unacknowledged messages for a nondurable TopicSubscriber are dropped when the session is closed. This problem is solved by durable subscriptions, discussed next. Durable subscriptions are like having a VCR recording a show you cannot watch at its scheduled time so that you can see what you missed when you turn your TV back on.

Reliability - Durable Subscriptions

As we saw in the previous section, a consumer subscribing to a topic cannot view past messages. This kind of subscription is called non-durable subscription. A subscription can be durable or non-durable. A non-durable subscriber can only receive messages that are published while it is active. A non-durable subscription doesn't guarantee the delivery of the message or may deliver the same message more than once.
A durable subscription, on the other hand, guarantees that the consumer receives the message only once.

The following command, that we already used in Listing 2, creates a non-durable subscription.

// 5. Create a Message Consumer tied to the Destination.
messageConsumer = session.createConsumer(destination);

The most reliable way to produce a message is to send a PERSISTENT message within a transaction. JMS messages are PERSISTENT by default.
The most reliable way to consume a message is to do so within a transaction, either from a queue or from a durable subscription to a topic.

Durable subscriptions allow consumers to receive messages sent to topics while the consumers are not active. Durable subscriptions exist only for Topics; queues are reliable by default. A durable subscriber registers a durable subscription by specifying a unique identity [10] that is retained by the JMS provider. Subsequent subscriber objects that have the same identity resume the subscription in the state in which it was left by the preceding subscriber. If a durable subscription has no active subscriber, the JMS provider retains the subscription's messages until they are received by the subscriber or until they expire.

OK, let's start. First, we need to create our users:

JMS_USERS table
USERID PASSWD CLIENTID
myPublisher passwd
mySubscriber passwd mySubscriberClientID

We created two users, one publisher and one subscriber. Check that the ClientID needs to be setup for the subscriber; this way the publisher has flexibility to send messages to specific subscribers only. Next, we setup the roles:

JMS_ROLES table
ROLEID USERID
publishers myPublisher
subscribers mySubscriber
durablesubscribers mySubscriber

Be careful to declare mySubscriber both in subscribers and in durablesubscribers. Finally, we wire the clientID with the topic.

JMS_SUBSCRIPTIONS table
CLIENTID SUBNAME TOPIC SELECTOR
mySubscriberClientID mySubscriber MyTopic (null)

Then, the my-jbossmq-destinations-service.xml file has to be modified like so:

<?xml version="1.0" encoding="UTF-8"?>
<server>
   <mbean code="org.jboss.mq.server.jmx.Topic" name="jboss.mq.destination:service=Topic,name=MyTopic">
     <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager</depends>
     <depends optional-attribute-name="SecurityManager">jboss.mq:service=SecurityManager</depends>
        <attribute name="SecurityConf">
            <security>
                <role name="guest" create="false" read="true" write="true"/>
                <role name="publishers" create="false" read="false" write="true"/>
                <role name="subscribers" create="true" read="true" write="false"/>
                <role name="durablesubscribers" create="true" read="true" write="true"/>
            </security>
        </attribute>
   </mbean>
</server> 

Note that the role names must match the ROLEIDs in the JMS_ROLES table.

If you are using another database than the default hsqldb, open $jboss/conf/login-config.xml and locate the following:

   <!-- Security domain for JBossMQ -->
    <application-policy name = "jbossmq">
       <authentication>
          <login-module code = "org.jboss.security.auth.spi.DatabaseServerLoginModule"
             flag = "required">
             <module-option name = "unauthenticatedIdentity">guest</module-option>
             <module-option name = "dsJndiName">java:/DefaultDS</module-option>
             <module-option name = "principalsQuery">SELECT PASSWD FROM JMS_USERS WHERE USERID=?</module-option>
             <module-option name = "rolesQuery">SELECT ROLEID, 'Roles' FROM JMS_ROLES WHERE USERID=?</module-option>
          </login-module>
       </authentication>
    </application-policy>

Make sure that the dsJndiName points to the correct datasource and not to the DefaultDS, otherwise you will get a security error.
Then the code for the JMSProducer has to be modified like so:

// 2. Get a Connection from the ConnectionFactory.
connection = connectionFactory.createConnection("myPublisher","passwd"); // username, password found in JMS_USERS

JMSConsumer's connection has to be modified accordignly:
// 2. Get a Connection from the ConnectionFactory.
connection = connectionFactory.createConnection("mySubscriber","passwd"); // username, password found in JMS_USERS
...
// 5. Create a durable subscriber tied to the Destination.
messageConsumer = session.createDurableSubscriber((Topic)destination, "mySubscriber"); // the SUBNAME from JMS_SUBSCRIPTIONS table

If you run the publisher and then the subscriber, you will see the message 'Hello JMS' in the console output.

Message Driven Beans

As you might have expected, JEE has full support for JMS API messaging through Message Driven Beans or MDBs. We saw previously, that we had to create a JMS consumer to consume a JMS message. This isn't necessary with EJB MDBs because the EJB container does that on the MDB's behalf.
A simple MDB is shown in Listing 6. Actually, it is a copy of JmsListener of Listing 4, annotated with EJB 3 annotations, shown in bold! Attention! Your container must support EJB 3.0 or later to execute this code. This is true for JBoss 4.2 or later.

Listing 6. MessageDrivenBean.java
package jms.consumer;
//Java extension packages
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.*;
/**
* <code>MessageDrivenBean</code> is the message listener of JMS Messages.
* It implements the specified <code>onMessage</code> method.
*/
@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/MyQueue")
})
public class MessageDrivenBean implements MessageListener {

/**
* Receives a JMS message.
* @param message a JMS message
*/
public void onMessage( Message message ) {
// retrieve and process message
try {
if ( message instanceof TextMessage ) {
TextMessage textMessage = (TextMessage) message;
System.out.println(textMessage.getText());
} else {
System.out.println( "Expecting " +
  "TextMessage object, received " + message.getClass().getName() );
}
 } // process JMS exception from message
catch ( JMSException jmsException ) {
  jmsException.printStackTrace();
}
} // end method onMessage
}

MDBs are initially described by an annotation—in this case, @MessageDriven (javax.ejb.MessageDriven). This annotation is used by the container to determine certain behavioral traits about the bean (just as with entity beans and session beans), most notably the activation configuration. The activation configuration for a message driven bean can instruct the container for the appropriate messaging method to apply to the bean or what types of messages are allowed to be processed by the bean [12]. As you saw in Listing 6, the MDB has its activation configuration specified by an array of  @ActivationConfigProperty (javax.ejb.ActivationConfigProperty) annotations. This alerts the EJB container as to where and how the messages received by this bean should be treated.
This annotation is an example of an interceptor of Aspect Oriented Programming (AOP) in EJB 3. It is actually executed before the MessageDriveBean class' method onMessage() is executed.  This is a handy tool for quickly adding value to your application without modifying existing code!
So, this is it! Now you only need to package the above class to an EJB. Go to the correct output directory and type the following in a command or shell prompt:
$> jar cf MDB.ejb3 jms/consumer/MessageDrivenBean.class
Copy the MDB.ejb3 to $JBoss/server/default/deploy/ directory. Execute the JMSProducer from Listing 1; notice the message "Hello JMS" that is produced at the JBoss output window (or check $JBoss/server/default/log/server.log)
22:39:28,655 INFO  [STDOUT] Hello JMS

Transactions

A JMS transaction groups a set of produced messages and a set of consumed messages into an atomic unit of work [2]. If an error occurs during a transaction, the production and consumption of messages that occurred before the error can be "undone." Session objects control transactions. A transacted Session always has a begin(); commit() and rollback(). As you might have already guessed, you declare a transaction by setting the first parameter in the createSession() method to true; the second indicates that message acknowledgment is not specified for transacted sessions.

// 3. Create a Session associated with the Connection.
// 1st parameter: true if Session is transacted
// 2nd parameter: Acknowledgement method
session = connection.createSession(true, 0);


MDBs in EJB 3.0 are able to use transactions to allow a higher order of control over message processing [12]. By using transactions, you may “roll back” the processing of a particular message if an action fails. This rollback includes the business method that initially required the transaction and the methods it invoked that support transaction processing. There are two types of transactions: Container-Managed Transactions (CMT) and User-defined Transactions.

Container-Managed Transactions (CMT)

With Container-Managed Transactions (CMT), transactions are handled by the container itself. The EJB specification has made provisions for containers to provide their own transaction functionality. This functionality can be added to an MDB merely by annotating the class name with the @TransactionManagement (javax.ejb.TransactionManagement)  descriptor. Once the class is annotated, the business methods of the class must be annotated to specify how the container’s transaction functionality will interpret their invocation. This behavior can be specified by using the @TransactionAttribute (javax.ejb.TransactionAttribute) annotation on business methods with either of the following two attributes: REQUIRED or NOT_SUPPORTED.

NOT_SUPPORTED as a transaction type merely alerts the container that any previously existing transactions should be suspended as the MDB executes the method in question. Once the method has finished executing, the transaction is resumed and business continues as usual.

REQUIRED transactions, on the other hand, cascade the transaction functionality to the  methods invoked within the original function. Therefore, if a simple SQL INSERT statement fails, all methods invoked up to that point that use transactions are alerted to roll  back, taking on their state before the error.

So, to provide CMT functionality to the MDB in Listing 6, you have to add the following annotations:

/**
* <code>MessageDrivenBean</code> is the message listener of JMS Messages.
* It implements the specified <code>onMessage</code> method.
*/
@TransactionManagement
@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/MyQueue")
})
public class MessageDrivenBean implements MessageListener {

/**
* Receives a JMS message.
* @param message a JMS message
*/
@TransactionAttribute(REQUIRED)
public void onMessage( Message message ) {
// retrieve and process message
Should a database (or other) operation triggered in the onMessage() method cause a rollback in the current transaction, that rollback will cascade up to the MDB level. This makes sure messages don’t get processed until all the dependent logic executes successfully.

User-Defined Transactions

If you wish to use user-defined transactions, you need to modify your code like so:
/**
* <code>MessageDrivenBean</code> is the message listener of JMS Messages.
* It implements the specified <code>onMessage</code> method.
*/
@TransactionManagement(BEAN)
@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/MyQueue")
})
public class MessageDrivenBean implements MessageListener {

/**
* Receives a JMS message.
* @param message a JMS message
*/
@Resource javax.transaction.UserTransaction transaction;
public void onMessage( Message message ) {
// retrieve and process message
transaction.update(); // Start the transaction
...
transaction.commit(); // Assume transaction went well, commit
Simply add the parameter BEAN to the @TransactionManagement class annotation and the container will assume your MDB is handling all the finer details of transaction management. The first step in managing the transactions manually for a particular class is obtaining it via resource injection. The line @Resource javax.transaction.UserTransaction transaction accomplishes this.

Distributed Transactions

Distributed transactions can also be supported by the Java Transaction API (JTA) XAResource API, though this is optional for providers. [TBA]

Summary

In this article we gave a detailed introduction to Java Messaging Service (JMS). JMS allows for anynchronous communication between applications in a loosely coupled manner. We discussed the JMS architecture, the messaging models (queue and topic), and we provided useful examples to help you up and running. We also used JBoss as our JMS provider, but you can use any other JMS Provider you wish. Please notice that we used the latest JMS 1.1 specification which allows for more generic code to be used. With JMS 1.1, the destination (queue or topic) is now simply a configuration setting, and you saw the impact in your code. 

There are some topics we didn’t cover in detail, like security, message selection, queue browsers etc. For that reason the interested reader is invited to check the references for more information.

References

  1. SUN Microsystems (2002), JMS Specification JSR 914, http://java.sun.com/products/jms/docs.html.
  2. Farrell, W. (2004), Introducing the Java Messaging Service, IBM.
  3. Davis, S. & Marrs, T. (2005), JBoss at Work: A Practical Guide, O'Reilly.
  4. Monson-Haefel, R. & Chappell D. A. (2001), Java Messaging Service, O'Reilly.
  5. Haase, K. (2002), Java Messaging Service API Tutorial, SUN Microsystems.
  6. Giotta, P. et. al. (2000), Professional JMS Programming, Apress.
  7. JBoss (2006), Getting Started with JBoss 4.0, Release 5, JBoss Inc..
  8. JBoss (2005), The JBoss 4 Application Server Guide, Release 3, JBoss Inc.
  9. JBoss (2007), JBoss Messaging 1.4 User's Guide, JBoss Inc.
  10. Jendrock E. et al. (2006), The Java™ EE 5 Tutorial, Third Edition, Addison Wesley Professional.
  11. "JMS Transaction with JTA in JBoss 4", http://www.odi.ch/prog/jms-tx.php.
  12. Mukhar K. & Zelenak C. (2006), Beginning Java EE 5 from Novice to Professional, Apress.
Creator: John N. Kostaras - email [email protected]
Last modification: 25 June 2008
URL: http://www.geocities.com/jnkjavaconnection/jmstutorial.html
URL: http://jkost.ergoway.gr/jnkjavaconnection/jmstutorial.html
Hosted by www.Geocities.ws

1