EJB/JMS User's Guide

  1. JMS installation and configuration aspects
  2. Writing JMS operations within an enterprise bean
  3. Some programming rules and restrictions
  4. JMS administration
  5. Running an EJB performing JMS operations
  6. A JMS EJB example
Asynchronous EJB method invocation is provided by the Message-driven Beans, as specified in the EJB 2.0 specification. Moreover, it is possible for any EJB component (session, entity, Message-driven) to send or synchronously receive JMS messages, and this within the scope of a global transaction managed by the EJB Server. The bean programmer has the possibility to use resources (by the way of resource references) that may be JDBC (a DataSource) or JMS (a ConnectionFactory) connection factories. Thus, the bean programmer is able to provide JMS code inside of an EJB method, in order to send a message toward a JMS Queue or Topic. The EJB Container is able to take into account these JMS operations within a global EJB 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 EJB components, such as the connection factories and the destinations, may be created previously to the EJB 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 EJB 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 enterprise bean

In order to send (or synchronously receive) JMS messages, the bean programmer should have 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", which are objects used to create connections to a resource manager within an EJB component. Up to now, two kinds of Resource Manager Connection Factories are considered in the EJB specification The second ones should be used by an EJB to get JMS Connection Factories. The standard deployment descriptor should contain the following 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 bean programmer will have access to a QueueConnectionFactory object using the JNDI name java:comp/env/jms/conFact. To obtain the factory object the EJB should contain the code :
      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 EJB component will require to use a resource environment reference, as defined in the EJB specification 2.0. A resource environment reference 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>
And the EJB 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 EJB method performing some JMS operations will look like the following (for sending a message):
     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 EJB component to synchronously receive a message (asynchronous message reception is not possible in session or entity beans, it should be done through message-driven beans). An 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 EJB 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 EJB 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 EJB 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 EJB component

JMS session creation within an EJB 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 within an EJB.

Some programming rules and restrictions

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

Authentification

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

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

JMS applications need some JMS administered objects: connection factories and destinations. These JMS administered 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 EJB component to create a QueueConnection.
TCF TopicConnectionFactory To be used by an EJB component to create a TopicConnection.
JQCF QueueConnectionFactory To be used by a Java component (an EJB client for instance) to create a QueueConnection.
JTCF TopicConnectionFactory To be used by a Java component (an EJB client for instance) to create a TopicConnection.

The QCF and TCF connection factories are managed connection factories. The EJB 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.

The two destination objects automatically created are described in the table below:
 

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 EJB 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 EJB 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 enterprise bean 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 EJB 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 EJB server launching time (command EJBServer).
     
  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 EJB 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 EJB 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 EJBServer command.