JMS User's Guide

  1. JMS installation and configuration aspects
  2. Writing JMS operations within an application component
  3. Some programming rules and restrictions
  4. JMS administration
  5. Running an EJB performing JMS operations
  6. A JMS EJB example

As required by the J2EE v1.3 specification, application components (servlets, JSP pages and enterprise beans) can use JMS for Java messaging. Moreover, applications can use Message-driven Beans for asynchronous EJB method invocation, as specified by the EJB 2.0 specification.

Application component providers may use JMS Connection Factory resources by the way of resource references, and JMS Destination resources (JMS Queues and JMS Topics), by the way of resource environment references. Thus, they are able to provide JMS code inside of an EJB method or web component method, in order to send or synchronously receive messages toward a JMS Queue or Topic.

The EJB container and the Web container are able to take into account JMS operations within a global transaction which may include other resources such as databases.

Details about JMS should be found in the Java Message Service Specification 1.0.2.

In the JOnAS JMS integration, JOnAS makes use of a third party JMS implementation: currently the Joram opensource is integrated and delivered with JOnAS, the SwiftMQ product may also be used, other JMS provider implementations may easily be integrated. The JMS administered objects used by the application components, such as the connection factories and the destinations, may be created previously to execution, by the way of the proprietary JMS implementation administration facilities. However, JOnAS provides "wrappers" upon such JMS administration APIs, allowing simple administration operations to be achieved automatically by the JOnAS server itself. This avoids the deployer to cope with proprietary administration APIs or tools. See JMS Administration.

JMS installation and configuration aspects

To use JMS with the JOnAS you do not need to perform any installation or configuration operation. JOnAS contains:

You may of course decide to use another JMS implementation. Currently the SwiftMQ product has been tested.

Writing JMS operations within an application component

In order to send (or synchronously receive) JMS messages, the component needs access to JMS administered objects, i.e. Connection Factories to create connections to JMS resources, and Destination objects (Queue or Topic) which are the JMS entities used as destination within JMS sending operations. Both are made available through JNDI by the JMS provider administration facility.

Accessing the Connection Factory

The EJB 1.1 specification introduces the concept of Resource Manager Connection Factory References. This concept also appears in the J2EE v1.3 specification. It is used to create connections to a resource manager. Up to now, three kinds of Resource Manager Connection Factories are considered: We are interested here by the second ones, which should be used to get JMS Connection Factories.

The standard deployment descriptor should contain the following resource-ref element :

      <resource-ref>
      <res-ref-name>jms/conFact</res-ref-name>
      <res-type>javax.jms.QueueConnectionFactory</res-type>
      <res-auth>Container</res-auth>
      </resource-ref>
This means that the programmer will have access to a QueueConnectionFactory object using the JNDI name java:comp/env/jms/conFact. The source code to obtain the factory object should be :
      QueueConnectionFactory qcf = (QueueConnectionFactory)
                 ctx.lookup("java:comp/env/jms/conFact");
The mapping to the actual JNDI name of the connection factory (as assigned by the JMS provider administration tool), QCF in the example, will be done in the JOnAS specific deployment descriptor with the following element:
      <jonas-resource>
      <res-ref-name>jms/conFact</res-ref-name>
      <jndi-name>QCF</jndi-name>
      </jonas-resource>

Accessing the Destination Object

Accessing a JMS destination within the code of an application component will require to use a Resource Environment Reference which is represented in the standard deployment descriptor as follows:
      <resource-env-ref>
      <resource-env-ref-name>jms/stockQueue</resource-env-ref-name>
      <resource-env-ref-type>javax.jms.Queue<resource-env-ref-type>
      </resource-env-ref>
The application component's source code should contain:
      Queue q = (Queue) ctx.lookup("java:comp/env/jms/stockQueue");
the mapping to the actual JNDI name (e.g. "myQueue") being done in the JOnAS specific deployment descriptor in the following way:
      <jonas-resource-env>
      <resource-env-ref-name>jms/stockQueue</resource-env-ref-name>
      <jndi-name>myQueue<jndi-name>
      </jonas-resource-env>

Writing JMS Operations

A typical method performing a message sending JMS operations will look like the following:
     void sendMyMessage() {

       QueueConnectionFactory qcf = (QueueConnectionFactory)
               ctx.lookup("java:comp/env/jms/conFact");
       Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue");
       QueueConnection qc = qcf.createQueueConnection();
       QueueSession qs = qc.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
       QueueSender sender = qs.createSender(queue);
       ObjectMessage msg = qs.createObjectMessage();
       msg.setObject("Hello");
       sender.send(msg);
       qs.close();
       qc.close();
     }
It is also possible for an application component to synchronously receive a message. A EJB method performing synchronous message reception on a queue is illustrated below:
    public String recMsg() {
        QueueConnectionFactory qcf = (QueueConnectionFactory)
               ctx.lookup("java:comp/env/jms/conFact");
        Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue");
        QueueConnection qc = qcf.createQueueConnection();
        QueueSession qs = qc.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
        QueueReceiver qr = qs.createReceiver(queue);
        qc.start();
        ObjectMessage msg = (ObjectMessage) qr.receive();
        String msgtxt =  (String) msg.getObject();
        qs.close();
        qc.close();
        return msgtxt;
    }
A method that performs JMS operations should always contain the session create and close statements, as follows:
     public void doSomethingWithJMS (...) {
       ...
       session = connection.create<Queue|Topic>Session(...);
       ... // JMS operations
       session.close();
     }
Of course, the contained JMS operations will be part of the transaction, if there is one, when the JOnAS server executes the method.

Be careful to never send and receive a given message in the same transaction, since the JMS sending operations are actually performed at commit time only !

The examples above illustrate point to point messaging, you may of course develop application components using the publish/subscribe JMS API, i.e. use the Topic instead of the Queue destination type. This allows you to broadcast a message to several message consumers at a time. The example below illustrates a typical method for publishing a message on a JMS topic.

    public void sendMsg(java.lang.String s) {
        TopicConnectionFactory tcf = (TopicConnectionFactory)
                       ictx.lookup("java:comp/env/jms/conFactSender");
        Topic topic = (Topic) ictx.lookup("java:comp/env/jms/topiclistener");
        TopicConnection tc = tcf.createTopicConnection();
        TopicSession session = tc.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
        TopicPublisher tp =  session.createPublisher(topic);
        ObjectMessage message = session.createObjectMessage();
        message.setObject(s);
        tp.publish(message);
        session.close();
        tc.close();
    }

Transactions and JMS sessions within an application component

JMS session creation within an application component will result in different behaviors depending if the session is created at execution time within or outside a transaction ! In fact, the parameters of the create<Queue|Topic>Session(boolean transacted, int acknowledgeMode) method are never taken into account.

Authentification

If your JMS implementation performs user authentification you may use the following methods on Connection Factories:

Some programming rules and restrictions when using JMS within EJB

This section presents some programming restrictions and rules for using JMS operations within entity components.

Connection Management

Depending on your JMS implementation and on your application, you may choose to keep your JMS connections opened for the life of your bean instance, or for the duration of your method call. These two programming modes are illustrated below (this example illustrates a stateful session bean):
public class EjbCompBean implements SessionBean {
    ...
    QueueConnectionFactory qcf = null;
    Queue queue = null;

    public void ejbCreate() {
       ....
       ictx = new InitialContext();
       qcf = (QueueConnectionFactory)
          ictx.lookup("java:comp/env/jms/conFactSender");
       queue = (Queue) ictx.lookup("java:comp/env/jms/queue1");
    }

    public void doSomethingWithJMS (...) {
       ...
       QueueConnection qc = qcf.createQueueConnection();
       QueueSession session = qc.createQueueSession(...);
       ... // JMS operations
       session.close();
       qc.close();
     }

    ...
}
If you would like to keep the connection opened during the life of your bean instance , the following programming style could be preferred, which avoids many connection opening and closing operations:
public class EjbCompBean implements SessionBean {
    ...
    QueueConnectionFactory qcf = null;
    Queue queue = null;
    QueueConnection qc = null;

    public void ejbCreate() {
       ....
       ictx = new InitialContext();
       qcf = (QueueConnectionFactory)
          ictx.lookup("java:comp/env/jms/conFactSender");
       queue = (Queue) ictx.lookup("queue1");
       qc = qcf.createQueueConnection();
    }

    public void doSomethingWithJMS (...) {
       ...
       QueueSession session = qc.createQueueSession(...);
       ... // JMS operations
       session.close();
    }

    public void ejbRemove() {
       qc.close();
    }

    ...
}
Be careful that maintaining JMS objects in the bean state, is not always possible depending on the type of the bean:

Starting Transactions after JMS Connection or Session creation

In case of bean-managed transaction, it is currently not possible to start a transaction after the creation of a JMS session and to have the JMS operations involved in the transaction. In the following code example, the JMS operations will not occur within the ut transaction:
public class EjbCompBean implements SessionBean {
    ...

    public void doSomethingWithJMS (...) {
       ...
       QueueConnection qc = qcf.createQueueConnection();
       QueueSession session = qc.createQueueSession(...);
       ut = ejbContext.getUserTransaction();
       ut.begin();
       ... // JMS operations
       ut.commit();
       session.close();
       qc.close();
     }

    ...
}
For having the session operations involved in the transaction, you should have the session creation and close inside the transaction boundaries, and may choose to have the connection creation and close operations either both outside the transaction boundaries or both inside the transaction boundaries, as follows:
public class EjbCompBean implements SessionBean {
    ...

    public void doSomethingWithJMS (...) {
       ...
       QueueConnection qc = qcf.createQueueConnection();
       ut = ejbContext.getUserTransaction();
       ut.begin();
       QueueSession session = qc.createQueueSession(...);
       ... // JMS operations
       session.close();
       ut.commit();
       qc.close();
     }

    ...
}
or
public class EjbCompBean implements SessionBean {
    ...

    public void doSomethingWithJMS (...) {
       ...
       ut = ejbContext.getUserTransaction();
       ut.begin();
       QueueConnection qc = qcf.createQueueConnection();
       QueueSession session = qc.createQueueSession(...);
       ... // JMS operations
       session.close();
       qc.close();
       ut.commit();
     }

    ...
}
Programming EJB components with bean-managed transactions may induce complex code, that is why we recommend to use container-managed transactions, which will avoid you to face such problems as described above.

JMS administration

Applications using messaging need some JMS administered objects: connection factories and destinations. These objects are to be created via the proprietary administration interface (not standardized) of the JMS provider. For simple administration cases, and for JOnAS integrated JMS implementations (as Joram or SwiftMQ), you may avoid this administration phase by telling JOnAS to do it for you. This is what is described in the JOnAS simple JMS administration section.

JOnAS simple JMS administration

In the default configuration, JOnAS automatically creates four connection factories and two destination objects. The only thing to do for using JMS in the default configuration is to require the use of the JMS service in the jonas.properties file:
    jonas.services           security,jtm,dbm,jms,ejb
The four connection factories automatically created are described in the table below:
 
JNDI name JMS type Usage
QCF QueueConnectionFactory To be used by an application component to create a QueueConnection.
TCF TopicConnectionFactory To be used by an application component to create a TopicConnection.
JQCF QueueConnectionFactory To be used by any other Java component (for instance a client) to create a QueueConnection.
JTCF TopicConnectionFactory To be used by any other Java component (for instance a client) to create a TopicConnection.

The QCF and TCF connection factories are managed connection factories. The application components should use only managed connection factories in order to allow JOnAS to manage the JMS resources created via these connection factories (the JMS sessions).
On the contrary, JQCF and JTCF are non-managed connection factories. They are to be used by Java component implementing a JMS client behavior, but running outside the application server.

With the standard jonas.properties file, two destination objects are created when the JMS Service is started :
 

JNDI name JMS type Usage
sampleQueue Queue Can be equally used by an EJB component or a Java component.
sampleTopic Topic Can be equally used by an EJB component or a Java component.

You cannot ask JOnAS to create additional connection factories when using the default configuration. However, you may ask JOnAS to create the destination objects you want at the server launching time, by specifying it in the jonas.properties file. You have to specify the JNDI names of the Topic (resp. Queue) destination objects to be created in a jonas.service.jms.topics (resp. jonas.service.jms.queues) property as follows:

    jonas.service.jms.topics    t1,t2    // JOnAS server creates 2 topic destinations (t1,t2)
    jonas.service.jms.queues    myQueue  // JOnAS server creates 1 queue destination (myQueue)
It is recommended to programmers to use resource references and resource environment references to access the connection factories and destination objects created by JOnAS, as already presented in Writing JMS operations within an application component section.

Running an EJB performing JMS operations

In order to have an Enterprise Bean performing some JMS operations, the following steps should be followed:

Launching the Message-Oriented Middleware

If the JOnAS property jonas.services contains the jms service, then the JOnAS JMS service will be launched, and will eventually try to launch a JMS implementation (e.g. the Joram MOM or the SwiftMQ MOM).

For launching the MOM, three possibilities may be considered:

  1. Launching the MOM automatically in the server JVM

  2. This can only be done using the default values for the configuration options, i.e. keeping the JOnAS property jonas.service.jms.collocated value true in the jonas.properties file (see the jonas.properties file provided in $JONAS_ROOT/config directory).
        jonas.service.jms.collocated true
    In that case, the MOM will be launched automatically at server launching time (command jonas start).
     
  3. Launching the MOM in a separate JVM on the same host

  4. The Joram MOM may be launched with its default options by the command:
     
      JmsServer

    For other MOMs, you may use the proprietary command.


    In that case, the JOnAS property jonas.service.jms.collocated must be set to false in the jonas.properties file.

        jonas.service.jms.collocated false
  5. Launching the MOM on another host

  6. You may launch the MOM on a separate host. In this case, you must specify to the JOnAS server that the MOM is running on another host through the JOnAS property jonas.service.jms.url in the jonas.properties file. In case of Joram, its value should be the Joram URL joram://host:port where host is the host name, and port the default Joram port number, i.e. 16010 (In case of SwiftMQ, the value of the URL is something like smqp://host:4001/timeout=10000).
        jonas.service.jms.collocated false
        jonas.service.jms.url        joram://host2:16010
  7. Launching the MOM on another port number (case of Joram)

  8. If you intend to change the default Joram port number, this requests a Joram specific configuration operation (modifying the a3servers.xml configuration file located in the directory where Joram is explicitly launched. A default a3servers.xml file and the a3config.dtd file (used by the JmsServer command) are respectively provided in $JONAS_ROOT/config and $JONAS_ROOT/xml directories; this a3servers.xml file specifies that the MOM runs on the localhost using the Joram default port number.
    In order to launch the MOM on another port number you must change the args attribute of the service class="fr.dyade.aaa.mom.ConnectionFactory" element in the a3servers.xml file and to update the jonas.service.jms.url property in jonas.properties file.
    The default a3servers.xml file is the one located in $JONAS_ROOT/config if you need to change the location of this file you must pass the system property -Dfr.dyade.aaa.agent.A3CONF_DIR="your directory for a3.xml"
    To change other MOM configuration (distribution, multi-servers, ...), see the Joram documentation on http://www.objectweb.org/joram.

    Note: the MOM may be directly launched by the proprietary command. In the case of Joram, the command is:
     

      java -DTransaction=NullTransaction fr.dyade.aaa.agent.AgentServer 0 ./s0


    This corresponds to the default options used by the JmsServer command.

    The JMS messages are not persistent when launching the MOM with this command. If your messages need to be persistent, you should replace the -DTransaction=NullTransaction option by the -DTransaction=ATransaction option. See the Joram documentation for more details about this command.

A JMS EJB example

This example shows an EJB application that combines an Enterprise Bean sending a JMS message and an Enterprise Bean writing a Database (an Entity Bean) within the same global transaction. It is composed of the following elements:

The Session Bean performing JMS operations

The bean should contain some code to initialize the references to JMS administered objects that it will use. This code can be introduced in the ejbCreate method, in order to avoid repeating it in each method performing some JMS operations.
public class EjbCompBean implements SessionBean {
    ...
    TopicConnectionFactory qcf = null;
    topic queue = null;

    public void ejbCreate() {
       ....
       ictx = new InitialContext();
       qcf = (TopicConnectionFactory)
          ictx.lookup("java:comp/env/jms/conFactSender");
       topic = (Topic) ictx.lookup("java:comp/env/jms/topiclistener");
    }
    ...
}
This code has been voluntary cleared from all the elements which are not necessary for understanding the JMS logic aspects of the example, e.g. the exception management ...

The JMS administered objects TopicConnectionFactory and Topic have been made available to the bean by a resource reference for the first one, and by a resource environment reference for the second one.
The standard deployment descriptor should contain the following element:

      <resource-ref>
        <res-ref-name>jms/conFactSender</res-ref-name>
        <res-type>javax.jms.TopicConnectionFactory</res-type>
        <res-auth>Container</res-auth>
      </resource-ref>
      <resource-env-ref>
        <resource-env-ref-name>jms/topiclistener</resource-env-ref-name>
        <resource-env-ref-type>javax.jms.Topic</resource-env-ref-type>
      </resource-env-ref>
The JOnAS specific deployment descriptor should contain the following element:
      <jonas-resource>
        <res-ref-name>jms/conFactSender</res-ref-name>
        <jndi-name>TCF</jndi-name>
      </jonas-resource>
      <jonas-resource-env>
        <resource-env-ref-name>jms/topiclistener</resource-env-ref-name>
        <jndi-name>sampleTopic</jndi-name>
      </jonas-resource-env>
Note that the EjbComp SessionBean will use the administered objects automatically created by JOnAS in the default JMS configuration.

The administered objects being accessible it is now possible to perform within a method some JMS operations. This is what is done in the sendMsg method:

public class EjbCompBean implements SessionBean {
    ...
    public void sendMsg(java.lang.String s) {
        // create TopicConnection, TopicSession and TopicSender
        TopicConnection tc = null;
        TopicSession session = null;
        TopicPublisher tp = null;
        try {
            tc = tcf.createTopicConnection();   
            session = tc.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
            tp = session.createPublisher(topic);
        }
        catch (Exception e) {e.printStackTrace();}

        // send the message to the topic
        try {
            ObjectMessage message;
            message = session.createObjectMessage();
            message.setObject(s);
            tp.publish(message);
            session.close();
            tc.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    ...
}
This method sends a message containing its String argument.

The Entity Bean

The example use the simple entity bean Account for writing data into a database. See the sample eb

The Client Application

The client application calls the sendMsg method of the EjbComp bean, and creates an AccountImpl entity bean, both within the same transaction.
public class EjbCompClient {
    ...
    public static void main(String[] arg) {
    ...
    utx = (UserTransaction) initialContext.lookup("javax.transaction.UserTransaction");
    ...
    home1 = (EjbCompHome) initialContext.lookup("EjbCompHome");
    home2 = (AccountHome) initialContext.lookup("AccountImplHome");
    ...
    EjbComp aJmsBean = home1.create();
    Account aDataBean = null;
    ...
    utx.begin();
    aJmsBean.sendMsg("Hello commit"); // sending a JMS message
    aDataBean = home2.create(222, "JMS Sample OK", 0);
    utx.commit();

    utx.begin();
    aJmsBean.sendMsg("Hello rollback"); // sending a JMS message
    aDataBean = home2.create(223, "JMS Sample KO", 0);
    utx.rollback();
    ...
    }
}
The result of this client execution will be that

A pure JMS client for receiving the messages

In this example, the messages sent by the EJB component are received by a simple JMS client, running outside the JOnAS server, but listening for messages sent on the JMS topic "sampleTopic". It uses the TopicConnectionFactory automatically created by JOnAS named "JTCF"
public class MsgReceptor {

    static Context ictx = null;
    static TopicConnectionFactory tcf = null;
    static Topic topic = null;

    public static void main(String[] arg) {

        ictx = new InitialContext();
        tcf = (TopicConnectionFactory) ictx.lookup("JTCF");
        topic = (Topic) ictx.lookup("sampleTopic");
        ...
        TopicConnection tc = tcf.createTopicConnection();       
        TopicSession session = 
               tc.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);  
        TopicSubscriber ts = session.createSubscriber(topic);

        MyListenerSimple listener = new MyListenerSimple();
            ts.setMessageListener(listener);
        tc.start();

        System.in.read(); // waiting for messages

        session.close();
        qc.close();
        ...
    }
}
public MyListenerSimple implements javax.jms.MessageListener {
   MyListenerSimple() {}

   public void onMessage(javax.jms.Message msg) {
      try {
      if(msg==null)
        System.out.println("Message: message null ");
      else {
        if(msg instanceof ObjectMessage) {
            String m = (String) ((ObjectMessage)msg).getObject();
            System.out.println ("JMS client: received message ======> " + m);
        } else if(msg instanceof TextMessage) {
            String m = ((TextMessage)msg).getText();
            System.out.println ("JMS client: received message ======> " + m);
        }
      }catch(Exception exc) {
          System.out.println("Exception caught :" + exc);
          exc.printStackTrace();
      }
   } 
}

The JMS Administration

We use the the JOnAS simplified facilities for administration by using the default values for the properties in the jonas.properties file. We only has to specify the use of the jms service:
    jonas.services               security,jtm,dbm,jms,ejb
The JMS Server is collocated to the JOnAS server and a topic named sampleTopic is created when launching the JOnAS server with the jonas start command.