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
  7. Tuning Message-driven Bean Pool

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 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 behavior 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 JMS User's Guide (Writing JMS operations within an application component / Accessing the destination object section).

Administration aspects

We consider at this point that the JOnAS 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 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 by JOnAS using 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 (which is the default wrapper class defined by the jonas.service.jms.mom property in the 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. 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 it is use by another client 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

If the configuration property jonas.services contains the jms service, then the JOnAS JMS service will be launched, and will possibly try to launch a JMS implementation (a MOM).

For launching the MOM, three possibilities may be considered:

  1. Launching the MOM in the same JVM as JOnAS

    This is the default situation obtained by assigning the true value to the configuration property jonas.service.jms.collocated in the jonas.properties file.

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

    In this case, the MOM is automatically launched by the JOnAS JMS service at the JOnAS launching time (command jonas start).

  2. Launching the MOM in a separate JVM

    The Joram MOM may be launched using the command:

    JmsServer

    For other MOMs, the proprietary command should be used.

    The configuration property jonas.service.jms.collocated must be set to false in the jonas.properties file. Setting this property is sufficient if the JORAM's JVM runs on the same host as JONAS, and if the MOM is launched with its default options (unchanged a3servers.xml configuration file under JONAS_BASE/conf or JONAS_ROOT/conf if JONAS_BASE not defined).

        jonas.services                security,jtm,dbm,jms,ejb   // The jms service must be in the list   
        jonas.service.jms.collocated  false
    	

    Using some specific configuration for the MOM, like changing the default host (which is localhost) the default connection port number (which is 16010), requires defining the additional jonas.service.jms.url configuration property as presented below.

  3. Launching the MOM on another host

    This requires defining the jonas.service.jms.url configuration property. When using Joram, its value should be the Joram URL joram://host:port where host is the host name, and port is the connection port (16010 in the default case). 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 in the list
        jonas.service.jms.collocated  false
        jonas.service.jms.url         joram://host2:16010
    	

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 de-queueing 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 examples/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                 jmx,security,jtm,dbm,jms,ejb	    // The jms service must be added
    jonas.service.ejb.descriptors  sampleappli.jar
    jonas.service.jms.topics       StockHandlerTopic
    jonas.service.jms.queues       OrdersQueue
    jonas.service.jms.collocated   true
    
This means that the JMS Server will be launched in the same JVM than the JOnAS Server, and the JMS administered objects StockHandlerTopic (Topic) and OrdersQueue (Queue) must be created and registered in JNDI.

Tuning Message-driven Bean Pool

For each Message-driven bean a pool is handled by JOnAS. The pool may be configured in the jonas specific deployment descriptor with the following tags:

min-pool-size

This optional integer value represents the minimum instances that will be created in the pool when the bean is loaded. This will improve bean instance creation time, at least for the first ones. The default value is 0.

max-cache-size

This optional integer value represents the maximum of instances in memory. The purpose of this value is to keep JOnAS scalable. The policy is the following:
At bean creation time an instance is taken from the pool of the free instances. If the pool is empty a new instance is always created. When the instance must be released (at the end of onMessage method), it is pushed into the pool except if the currently number of instances created exceed the max-cache-size, in this case this instance is dropped. The default value is no limit.

exemple


  <jonas-ejb-jar>
    <jonas-message-driven>
      <ejb-name>Mdb</ejb-name>
      <jndi-name>mdbTopic</jndi-name>
      <max-cache-size>20</max-cache-size>
      <min-pool-size>10</min-pool-size>
    </jonas-message-driven>
  </jonas-ejb-jar>