JMS User's Guide

  1. JMS installation and configuration aspects
  2. Writing JMS operations within an application component
  3. Some programming rules and restrictions when using JMS within EJB
  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. Furthermore, applications can use Message-driven Beans for asynchronous EJB method invocation, as specified by the EJB 2.0 specification.

Starting with the JOnAS 3.1 version, JOnAS supports the Java Message Service Specification 1.1. Previously in JMS 1.0.2, client programming for Point-to-point and Pub/Sub domains was achieved using similar, but separate, class hierarchies. Now, JMS 1.1 offers a domain-independent approach to programming the client application. Thus, the programming model is simpler and it is now possible to engage queues and topics in the same transaction.

Enterprise Bean providers can use JMS Connection Factory resources via resource references, and JMS Destination resources (JMS Queues and JMS Topics) via resource environment references. Thus, they are able to provide JMS code, inside an EJB method or web component method, for sending or synchronously receiving messages to/from a JMS Queue or Topic.

The EJB container and the Web container can allow for JMS operations within a global transaction, which may include other resources such as databases.

In the JOnAS JMS integration, JOnAS employs a third-party JMS implementation; currently JORAM, an open source implementation, is integrated and delivered with JOnAS. Additionally, the SwiftMQ product, and IBM's WebSphere MQ can be used. Other JMS provider implementations can also easily be integrated. The JMS-administered objects used by the application components, such as the connection factories and the destinations, can be created prior to execution via the proprietary JMS implementation administration facilities. However, JOnAS provides "wrappers" on such JMS administration APIs, allowing simple administration operations to be performed automatically by the JOnAS server itself. Thus, the deployer avoids coping with proprietary administration APIs or tools. Refer to JMS Administration.

JMS installation and configuration aspects

To use JMS with JOnAS, no additional installation or configuration operations are required. JOnAS contains:

Additionally, the SwiftMQ product and IBM's WebSphere MQ have been used with JOnAS.

Writing JMS operations within an application component

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

Refer to the JOnAS example jms as a supplement to this present reading. This example jms is described here.

Accessing the Connection Factory

The EJB 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. To date, three types of Resource Manager Connection Factories are considered: The connection factories of interest here are the second type, which should be used to get JMS Connection Factories.

Note that starting with JMS 1.1, it is recommended that only the javax.jms.ConnectionFactory be used (rather than javax.jms.QueueConnectionFactory or javax.jms.TopicConnectionFactory ). However, the new implementation is fully backwards compatible and existing applications will work as is.

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.ConnectionFactory</res-type>
      <res-auth>Container</res-auth>
      </resource-ref>
This means that the programmer will have access to a ConnectionFactory object using the JNDI name java:comp/env/jms/conFact. The source code for obtaining the factory object is the following:
      ConnectionFactory qcf = (ConnectionFactory)
                 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), CF in the example, is defined in the JOnAS-specific deployment descriptor with the following element:
      <jonas-resource>
      <res-ref-name>jms/conFact</res-ref-name>
      <jndi-name>CF</jndi-name>
      </jonas-resource>

Accessing the Destination Object

Accessing a JMS destination within the code of an application component requires using 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 defined 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 looks like the following:
     void sendMyMessage() {

       ConnectionFactory cf = (ConnectionFactory)
               ctx.lookup("java:comp/env/jms/conFact");
       Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue");
       Connection conn = cf.createConnection();
       Session sess = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);



       MessageProducer  mp = sess.createProducer((Destination)queue);
       ObjectMessage msg = sess.createObjectMessage();
       msg.setObject("Hello");
       sender.send(msg);
       sess.close();
       conn.close();
     }
It is also possible for an application component to synchronously receive a message. An EJB method performing synchronous message reception on a queue is illustrated in the following:
    public String recMsg() {
        ConnectionFactory cf = (ConnectionFactory)
               ctx.lookup("java:comp/env/jms/conFact");
        Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue");
        Connection conn = cf.createConnection();
        Session sess = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
        MessageConsumer mc = sess.createConsumer((Destination)queue);
        conn.start();
        ObjectMessage msg = (ObjectMessage) mc.receive();
        String msgtxt =  (String) msg.getObject();
        sess.close();
        conn.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.createSession(...);
       ... // JMS operations
       session.close();
     }
The contained JMS operations will be a part of the transaction, if there is one, when the JOnAS server executes the method.

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

The previous examples illustrate point-to-point messaging. However, application components can also be developed using the publish/subscribe JMS API, i.e. using the Topic instead of the Queue destination type. This offers the capability of broadcasting a message to several message consumers at the same time. The following example illustrates a typical method for publishing a message on a JMS topic and demonstrates how interfaces have been simplified since JMS 1.1.

    public void sendMsg(java.lang.String s) {
        ConnectionFactory cf = (ConnectionFactory)
                       ictx.lookup("java:comp/env/jms/conFactSender");
        Topic topic = (Topic) ictx.lookup("java:comp/env/jms/topiclistener");
        Connection conn = cf.createConnection();
        Session session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
        MessageConsumer mc = session.createConsumer((Destination)topic);
        ObjectMessage message = session.createObjectMessage();
        message.setObject(s);
        mc.send(message);
        session.close();
        conn.close();
    }

Transactions and JMS sessions within an application component

JMS session creation within an application component will result in different behaviors, depending on whether the session is created at execution time within or outside a transaction. In fact, the parameters of the createSession(boolean transacted, int acknowledgeMode) method are never taken into account.

Authentication

If your JMS implementation performs user authentication, the following methods can be used 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 the JMS implementation and the application, it may be desirable to keep the JMS connections open for the life of the bean instance or for the duration of the method call. These two programming modes are illustrated in the following example (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 (...) {
       ...
       Connection conn = qcf.createConnection();
       Session session = conn.createSession(...);
       ... // JMS operations
       session.close();
       conn.close();
     }

    ...
}
To keep the connection open during the life of a bean instance, the programming style shown in the following example is preferred, since it avoids many connection opening and closing operations:
public class EjbCompBean implements SessionBean {
    ...
    ConnectionFactory qcf = null;
    Queue queue = null;
    Connection conn = null;

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

    public void doSomethingWithJMS (...) {
       ...
       Session session = conn.createSession(...);
       ... // JMS operations
       session.close();
    }

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

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

Note that, due to a known problem with the Sun JDK 1.3 on Linux, the close of the connection can block. The problem is fixed with JDK 1.4.

Starting Transactions after JMS Connection or Session creation

Currently, it is not possible to start a bean-managed transaction after the creation of a JMS session and 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 (...) {
       ...
       Connection conn = cf.createConnection();
       Session session = conn.createSession(...);
       ut = ejbContext.getUserTransaction();
       ut.begin();
       ... // JMS operations
       ut.commit();
       session.close();
       conn.close();
     }

    ...
}
To have the session operations involved in the transaction, the session creation and close should be inside the transaction boundaries, and the connection creation and close operations can either be both outside the transaction boundaries or both inside the transaction boundaries, as follows:
public class EjbCompBean implements SessionBean {
    ...

    public void doSomethingWithJMS (...) {
       ...
       Connection conn = qcf.createConnection();
       ut = ejbContext.getUserTransaction();
       ut.begin();
       Session session = conn.createSession(...);
       ... // JMS operations
       session.close();
       ut.commit();
       conn.close();
     }

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

    public void doSomethingWithJMS (...) {
       ...
       ut = ejbContext.getUserTransaction();
       ut.begin();
       Connection conn = cf.createConnection();
       Session session = conn.createSession(...);
       ... // JMS operations
       session.close();
       conn.close();
       ut.commit();
     }

    ...
}
Programming EJB components with bean-managed transactions can result in complex code. Using container-managed transactions can help avoid problems such as those previously described.

JMS administration

Applications using messaging require some JMS-administered objects: connection factories and destinations. These objects are 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), this administration phase can be avoided by having JOnAS perform these functions, as described in the following "JOnAS simple JMS administration" section.

JOnAS simple JMS administration

In the default configuration, JOnAS automatically creates six connection factories and two destination objects. For using JMS in the default configuration, all that is necessary is requiring the use of the JMS service in the jonas.properties file:
    jonas.services           security,jtm,dbm,jms,ejb
The six connection factories automatically created are described in the following table:
 
JNDI name JMS type Usage
CF ConnectionFactory To be used by an application component to create a Connection.
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.
JCF ConnectionFactory To be used by any other Java component (for instance a client) to create a Connection.
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 CF, QCF and TCF connection factories are managed connection factories. The application components should use only managed connection factories to allow JOnAS to manage the JMS resources created via these connection factories (the JMS sessions).
In contrast, JCF, JQCF and JTCF are non-managed connection factories. They are used by Java components 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.

JOnAS will not create additional connection factories when using the default configuration. However, JOnAS can create requested destination objects at server launching time, if specified in the jonas.properties file. To do this, specify the JNDI names of the Topic and Queue destination objects to be created in a jonas.service.jms.topics and jonas.service.jms.queues property respectively, 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 that programmers use resource references and resource environment references to access the connection factories and destination objects created by JOnAS, as already presented in the "Writing JMS operations within an application component" section.

Running an EJB performing JMS operations

All that is necessary to have an Enterprise Bean perform JMS operations is:

jonas start

The Message-Oriented Middleware (the JMS provider implementation) is automatically started and the JMS-administered objects that will be used by the Enterprise Beans are automatically created and registered in JNDI.

Then, the EJB can be deployed as usual with:

jonas admin -a XX.jar

Launching the Message-Oriented Middleware

If the JOnAS property jonas.services contains the jms service, 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, consider the following possibilities:

  1. Launching the MOM automatically in the JOnAS JVM
    This is 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/conf directory).
        jonas.service.jms.collocated true
    In this case, the MOM will be launched automatically at server launching time (command jonas start).
     

    Note for using the JORAM MOM from a distant host:


     
  2. Launching the MOM in a separate JVM on the same host
    The JORAM MOM can be launched with its default options using the command:
      JmsServer

    For other MOMs, use the proprietary command.


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

        jonas.service.jms.collocated false
  3. Launching the MOM on another host
    The MOM can be launched on a separate host. In this case, the JOnAS server must be notified that the MOM is running on another host via the JOnAS property jonas.service.jms.url in the jonas.properties file. For 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 (For SwiftMQ, the value of the URL is similar to smqp://host:4001/timeout=10000).
        jonas.service.jms.collocated false
        jonas.service.jms.url        joram://host2:16010
  4. Launching the MOM on another port number (for JORAM)
    To change the default JORAM port number requires 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 is provided in the $JONAS_ROOT/conf directory; this a3servers.xml file specifies that the MOM runs on the localhost using the JORAM default port number.
    To launch the MOM on another port number, change the args attribute of the service class="fr.dyade.aaa.mom.ConnectionFactory" element in the a3servers.xml file and update the jonas.service.jms.url property in the jonas.properties file.

    The default a3servers.xml file is located in $JONAS_ROOT/conf. To change the location of this file, the system property -Dfr.dyade.aaa.agent.A3CONF_DIR="your directory for a3.xml" must be passed.

  5. Specifying JORAM's persistence mode
    When automatically starting JORAM, or when starting JORAM with the JmsServer command, the default mode is non-persistent. Meaning that in the event of a crash, the non-delivered and non-acknowledged messages are lost.

    In order to start a persistent JORAM server, guaranteeing message delivery even in case of failures, the Transaction system property should be set to fr.dyade.aaa.util.ATransaction.

    Note: the MOM may be directly launched by the proprietary command. The command for JORAM is:
      java -DTransaction=fr.dyade.aaa.util.NullTransaction fr.dyade.aaa.agent.AgentServer 0 ./s0

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

    The server is not persistent when launched with this command. If persistence is required, the -DTransaction=fr.dyade.aaa.util.NullTransaction option should be replaced with the -DTransaction=fr.dyade.aaa.util.ATransaction option.

    To change other MOM configurations (distribution, multi-servers, ...), refer to the JORAM documentation on http://joram.objectweb.org.

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 code for initializing the references to JMS administered objects that it will use. To avoid repeating this code in each method performing JMS operations, it can be introduced in the ejbCreate method.
public class EjbCompBean implements SessionBean {
    ...
    ConnectionFactory cf = null;
    Topic topic = null;

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

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

      <resource-ref>
        <res-ref-name>jms/conFactSender</res-ref-name>
        <res-type>javax.jms.ConnectionFactory</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.

Because the administered objects are now accessible, it is possible to perform JMS operations within a method. The following occurs in the sendMsg method:

public class EjbCompBean implements SessionBean {
    ...
    public void sendMsg(java.lang.String s) {
        // create Connection, Session and MessageProducer
        Connection conn = null;
        Session session = null;
        MessageProducer mp = null;
        try {
            conn = cf.createConnection();   
            session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
            mp = session.createProducer((Destination)topic);
        }
        catch (Exception e) {e.printStackTrace();}

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

The Entity Bean

The example uses the simple entity bean Account for writing data into a database. Refer to 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 messages

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

    static Context ictx = null;
    static ConnectionFactory cf = null;
    static Topic topic = null;

    public static void main(String[] arg) {

        ictx = new InitialContext();
        cf = (ConnectionFactory) ictx.lookup("JCF");
        topic = (Topic) ictx.lookup("sampleTopic");
        ...
        Connection conn = cf.createConnection();       
        Session session = 
               conn.createSession(false, Session.AUTO_ACKNOWLEDGE);  
        MessageConsumer mc = session.createConsumer((Destination)topic);

        MyListenerSimple listener = new MyListenerSimple();
            mc.setMessageListener(listener);
        conn.start();

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

        session.close();
        conn.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

Using the default values for the properties in the jonas.properties file offers simplified facilities for JMS administration, that satisfy the requirements of this example. Only the use of the jms service must be specified:
    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.