Message-driven Beans

  1. What is a Message-driven Bean
  2. Developing a Message-driven Bean
  3. Administration aspects
  4. Running a Message-driven Bean
  5. Transactional aspects
  6. Example

The EJB 2.0 specification defines a new kind of EJB component in order to receive asynchronous messages. This implements some kind of "asynchronous EJB component method invocation" mechanism. The Message-driven Bean (sometimes called MDB in the following) is an Enterprise JavaBean, neither an Entity Bean nor a Session Bean, which plays the role of a JMS MessageListener.

Details about MDB should be found in the EJB 2.0 specification, while details about JMS should be found in the Java Message Service Specification 1.0.2. This chapter focuses on the use of Message-driven beans within the JOnAS EJB server.

What is a Message-driven Bean

A Message-driven Bean is an EJB component which may be considered as a JMS MessageListener, i.e. which processes JMS messages asynchronously: it implements the onMessage(javax.jms.Message) method, defined in the javax.jms.MessageListener interface. It is associated to a JMS destination, i.e. a Queue for "point to point" messaging, or a Topic for "publish/subscribe". The onMessage method will be activated on the reception of messages sent by a client application to the corresponding JMS destination. It is possible to associate a JMS message selector to filter the messages the Message-driven Bean should receive.

JMS messages do not carry any context, therefore the onMessage method will execute without pre-existing transactional context. However a new transaction may be initiated at this moment, see the Transactional aspects section for more details. Of course, the onMessage method may call other methods on the MDB itself or on other beans, and may involve other resources by accessing databases or by sending messages. Such resources are accessed the same way as for other beans (entity or session), i.e. through resource references declared in the deployment descriptor.

The JOnAS container will maintain a pool of MDB instances, allowing to process large volumes of messages concurrently. A MDB is similar in some way to a stateless session bean: its instances are relatively short-lived, retain no state for a specific client, and several instances may be running at the same time.

Developing a Message-driven Bean

The MDB class must implement the javax.jms.MessageListener and the javax.ejb.MessageDrivenBean interfaces. In addition to the onMessage method, the following must be implemented:

The following is an example of MDB class:

public class MdbBean  implements MessageDrivenBean, MessageListener {   

    private transient MessageDrivenContext mdbContext;

    public MdbBean() {}

    public void setMessageDrivenContext(MessageDrivenContext ctx) {
	mdbContext = ctx;
    } 

    public void ejbRemove() {}

    public void ejbCreate() {}

    public void onMessage(Message message) {
	try {
	    TextMessage mess = (TextMessage)message;
	    System.out.println( "Message received: "+mess.getText());
	}catch(JMSException ex){
	    System.err.println("Exception caught: "+ex);
	}
    }
}
    

The destination associated to a MDB is specified in the deployment descriptor of the bean. A destination is a JMS administered object accessible via JNDI. The description of a MDB in the EJB 2.0 deployment descriptor contains the following elements which are specific to MDBs:

The following example illustrates such a deployment descriptor:


  <enterprise-beans>
    <message-driven>
      <description>Describe here the message driven bean Mdb</description>
      <display-name>Message Driven Bean Mdb</display-name>
      <ejb-name>Mdb</ejb-name>
      <ejb-class>samplemdb.MdbBean</ejb-class>
      <transaction-type>Container</transaction-type>
      <message-selector>Weight >= 60.00 AND LName LIKE 'Sm_th'</message-selector>
      <message-driven-destination>
        <destination-type>javax.jms.Topic</destination-type>
        <subscription-durability>NonDurable</subscription-durability>
      </message-driven-destination>           
      <acknowledge-mode>Auto-acknowledge</acknowledge-mode>
    </message-driven> 
  </enterprise-beans>
    

If the transaction type is "container", the transactional behaviour of MDBs methods are defined as for other enterprise beans in the deployment descriptor, as in the following example:

  <assembly-descriptor>
    <container-transaction>
      <method>
	<ejb-name>Mdb</ejb-name>
	<method-name>*</method-name>
      </method>
      <trans-attribute>Required</trans-attribute>
    </container-transaction>
  </assembly-descriptor>
    

For the onMessage method, only the Required or NotSupported transaction attribute must be used, since there can be no pre-existing transaction context.

For the message selector specified in the above example, it is expected that the sent JMS messages have two properties, "Weight" and "LName", for example assigned in the JMS client program sending the messages as follows:

        message.setDoubleProperty("Weight",75.5);
        message.setStringProperty("LName","Smith");
	

Such a message will be received by the Message-driven Bean. The message selector syntax is based on a subset of the SQL92. Only messages whose headers and properties match the selector are delivered. See the JMS specification for more details.

The JNDI name of a destination associated to a MDB is defined in the JOnAS specific deployment descriptor, within a jonas-message-driven element, as illustrated below:

  <jonas-message-driven>
    <ejb-name>Mdb</ejb-name>
    <jonas-message-driven-destination>
      <jndi-name>sampleTopic</jndi-name>
    </jonas-message-driven-destination>
  </jonas-message-driven>
    

Once the destination is established, a client application may send messages to the MDB through a destination object obtained via JNDI as follows :

Queue q = context.lookup("sampleTopic");

If the client sending messages to the MDB is an EJB component itself, it is preferable that it uses a resource environment reference to obtain the destination object. The use of resource environment references is described in the EJB/JMS User's Guide (Writing JMS operations within an enterprise bean / Accessing the destination object section).

Administration aspects

We considere at this point that the JOnAS EJB Server will make use of an existing JMS implementation, e.g. Joram, SwiftMQ, ...

The default policy is that the MDB developer and deployer will not care about JMS administration. This means that the developer/deployer will not create or use any JMS Connection factory and will not create any JMS destination (as it is necessary for performing JMS operations within a session or entity bean, see EJB/JMS User's Guide), she/he will just define the type of the destination in the deployment descriptor and its JNDI name in the JOnAS specific deployment descriptor, as explained in the previous section. This means that the necessary administered objects will be implicitly created at EJB server starting time, and that for this purpose the EJB server will make use of the proprietary administration APIs of the JMS implementation (since the administration APIs are not standardized). To perform such administration operations, JOnAS uses wrappers to the JMS provider administration API, in case of Joram, the wrapper is org.objectweb.jonas_jms.JmsAdminForJoram (this is the default wrapper class that is specified in the jonas.service.jms.mom property of the $JONAS_ROOT/config/jonas.properties file), in case of SwiftMQ, you may get a com.swiftmq.appserver.jonas.JmsAdminForSwiftMQ class from the SwiftMQ site. For the purpose of this implicit administration phase, the deployer must add the 'jms' service in the list of the JOnAS services included in the EJB server. For the provided example, the jonas.properties file should contain:

    jonas.services                   registry,security,jtm,dbm,jms,ejb      // The jms service must be added
    jonas.service.ejb.descriptors    samplemdb.jar
    jonas.service.jms.topics         sampleTopic	// not mandatory
    

The destination objects can pre-exist or not. The EJB server will not create the corresponding JMS destination object if it already exist. (see also JMS Administration). You should declare explicitly sampleTopic only if you want that the JOnAS Server create it first, even if the message driven bean is not loaded, or if you want to use by another program before the message driven bean is loaded. Most of the time, you don't need to declare it.

JOnAS uses a pool of threads for executing Message-driven bean instances on message reception, thus allowing to process large volumes of messages concurrently. As previously explained, MDB instances are stateless, and several instances may execute concurrently on behalf of a same MDB. The default size of the pool of thread is 10, and it may be customized through the jonas property jonas.service.ejb.mdbthreadpoolsize, to be specified in the jonas.properties file, e.g.:

    jonas.service.ejb.mdbthreadpoolsize   50
    

Running a Message-driven Bean

In order to deploy and run a Message-driven Bean the following steps should be followed:

Launching the Message-Oriented Middleware

JOnAS will use the MOM for some administration operations, and thus will need a wrapper to the proprietary administration API of the MOM. The wrapper for Joram is org.objectweb.jonas_jms.JmsAdminForJoram, it is delivered with JOnAS. The wrapper for SwiftMQ is com.swiftmq.appserver.jonas.JmsAdminForSwiftMQ, you may get it from the SwiftMQ site. You should position the jonas property jonas.service.jms.mom in the jonas.properties file to this wrapper. 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 (a MOM).

For launching the MOM, three possibilities may be considered:

  1. Launching the MOM automatically in the EJB server JVM

    This can only be done with the MOM default options, and by assigning to the jonas property jonas.service.jms.collocated the true value (its default value !) in the jonas.properties file.

        jonas.services                security,jtm,dbm,jms,ejb       // The jms service must be added
        jonas.service.jms.collocated  true
    	

    In that case, the MOM will be launched automatically at the EJB server launching time (command jonas start).

  2. Launching the MOM in a separate JVM on the same host

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

    JmsServer

    For other MOMs, the proprietary command should be used.

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

        jonas.services                security,jtm,dbm,jms,ejb       // The jms service must be added
        jonas.service.jms.collocated  false
    	
  3. Launching the MOM on another host

    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.services                security,jtm,dbm,jms,ejb       // The jms service must be added
        jonas.service.jms.collocated  false
        jonas.service.jms.url         joram://host2:16010
    	

If you intend to change the default MOM configuration, the MOM may be directly launched by the proprietary command (in the jonas.properties file, the jonas property jonas.service.jms.collocated must be set to false, and the jonas.service.jms.url property should be set if you run the MOM on a separate host). 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. 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. The directory where Joram is explicitly launched should contain the a3servers.xml configuration file (and the corresponding a3config.dtd file). 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. To change the MOM configuration (distribution, multi-servers, ...), see the Joram documentation on http://www.objectweb.org/joram.

Transactional aspects

Since according the the EJB 2.0 specification, a transactional context may not be carried by a message, a MDB will never execute within an existing transaction. However, a transaction may be started during the onMessage method execution (either because of a "required" transaction attribute (container-managed transaction) or because it is explicitely started within the method (if the MDB is bean-managed transacted). In the second case, the message receipt will not be part of the transaction. In the first case, container-managed transaction, the container will start a new transaction before dequeuing the JMS message (the receipt of which will thus be part of the started transaction), enlist the resource manager associated with the arriving message, and all the resource managers accessed by the onMessage method. If the onMessage method invokes other enterprise beans, the container passes the transaction context with the invocation. Therefore the transaction started at the onMessage method execution may involve several operations such as accessing a database (via a call to an entity bean, or by using a "datasource" resource), sending messages (by using a "connection factory" resource).

Example

JOnAS provides a couple of examples located in the example/src/mdb directory of your installation
samplemdb is very sample and the code of this example is used in the previous topics for illustrating how to use Message driven beans.
sampleappli is a little more complex one that shows how the sending of JMS messages and updates in a data base via JDBC may be involved in a same distributed transaction.
the following figure illustrates the architecture of this little application


In this application there are two message driven beans:

and a CMP entity bean Stock that handles a stock table.

A Stock item is composed of a Stockid (String) which is the primary key and of a Quantity (int), the method decreaseQuantity(int qty) decreases the quantity for the corresponding stockid but may throw a RemoteException "Negative stock".

The client application SampleAppliClient is a JMS Client that sends several messages on the topic StockHandlerTopic. It uses Map messages with three fields "CustomerId", "ProductId", "Quantity". Before sending messages this client calls the EnvBean for creating the StockTable in the database with well known values in order to be able to check the results of updates at the end of the test. 11 messages are sent, the corresponding transactions are committed, the last message sent causes the transaction to be rolled back.

Compiling this example


On examples/src/mdb/sampleappli, run the compile script compile.sh on Unix or compile.bat on Windows.
This example may be compiled via Ant by using the $JONAS_ROOT/examples/src/build.xml file.

Running this example


the default configuration of the JMS service in jonas.properties is the following:
    jonas.services		   security,jtm,dbm,jms,ejb       // The jms service must be added
    jonas.service.jms.topic        StockHandlerTopic
    jonas.service.jms.queues	   OrdersQueue
    jonas.service.jms.collocated   true
    
This means that the JMS Server will be launched in a separate JVM than the JOnAS Server, and the JMS administered objects StockHandlerTopic (Topic) and OrdersQueue (Queue)must be created and registered in JNDI.