EJB Programmer's Guide: Message-driven Beans

The content of this guide is the following:

  1. Description of 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.1 specification defines a new kind of EJB component for receiving asynchronous messages. This implements some type of "asynchronous EJB component method invocation" mechanism. The Message-driven Bean (also referred to as MDB in the following) is an Enterprise JavaBean, not an Entity Bean or a Session Bean, which plays the role of a JMS MessageListener.

The EJB 2.1 specification contains detailed information about MDB. The Java Message Service Specification 1.1 contains detailed information about JMS. This chapter focuses on the use of Message-driven beans within the JOnAS server.

Description of a Message-driven Bean

A Message-driven Bean is an EJB component that can be considered as a JMS MessageListener, i.e., processing JMS messages asynchronously; it implements the onMessage(javax.jms.Message) method, defined in the javax.jms.MessageListener interface. It is associated with a JMS destination, i.e., a Queue for "point-to-point" messaging or a Topic for "publish/subscribe." The onMessage method is activated on receipt 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 that the Message-driven Bean should receive.

JMS messages do not carry any context, thus the onMessage method will execute without pre-existing transactional context. However, a new transaction can be initiated at this moment (refer to the "Transactional aspects" section for more details). The onMessage method can call other methods on the MDB itself or on other beans, and can 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 maintains a pool of MDB instances, allowing large volumes of messages to be processed concurrently. An MDB is similar in some ways to a stateless session bean: its instances are relatively short-lived, it retains 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 an 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 an MDB is specified in the deployment descriptor of the bean. A destination is a JMS-administered object, accessible via JNDI. The description of an 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 the MDB's 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 attributes must be used, since there can be no pre-existing transaction context.

For the message selector specified in the previous example, the sent JMS messages are expected to 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. Refer to the JMS specification for more details.

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

  <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 can 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 use 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

It is assumed 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 are not concerned with JMS administration. This means that the developer/deployer will not create or use any JMS Connection factories and will not create a JMS destination (which is necessary for performing JMS operations within an EJB component, refer to the JMS User's Guide); they will simply define the type of destination in the deployment descriptor and identify its JNDI name in the JOnAS-specific deployment descriptor, as described in the previous section. This means that JOnAS will implicitly create the necessary administered objects by 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. For 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). For SwiftMQ, a com.swiftmq.appserver.jonas.JmsAdminForSwiftMQ class can be obtained 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 example provided, the jonas.properties file should contain the following:

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 may or may not pre-exist. The EJB server will not create the corresponding JMS destination object if it already exists. (Refer also to JMS administration). The sampleTopic should be explicitly declared only if the JOnAS Server is going to create it first, even if the Message-driven bean is not loaded, or if it is used by another client before the Message-driven bean is loaded. In general, it is not necessary to declare the sampleTopic.

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

    jonas.service.ejb.mdbthreadpoolsize   50
    

Running a Message-driven Bean

To deploy and run a Message-driven Bean, perform the following steps:

Launching the Message-Oriented Middleware

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

For launching the MOM, three possibilities can 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 can 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

    To use a specific configuration for the MOM, such as changing the default host (which is localhost) or the default connection port number (which is 16010), requires defining the additional jonas.service.jms.url configuration property as presented in the following case.

  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 (by default, 16010). For SwiftMQ, the value of the URL is similar to the following: 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

Because a transactional context cannot be carried by a message (according to the EJB 2.0 specification), an MDB will never execute within an existing transaction. However, a transaction may be started during the onMessage method execution, either due to a "required" transaction attribute (container-managed transaction) or because it is explicitly 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), then 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), or sending messages (by using a "connection factory" resource).

Example

JOnAS provides examples that are located in the examples/src/mdb install directory.
samplemdb is a very simple example, the code of which is used in the previous topics for illustrating how to use Message-driven beans.
sampleappli is a more complex example that shows how the sending of JMS messages and updates in a database via JDBC may be involved in the same distributed transaction.
The following figure illustrates the architecture of this example application.

eb

There are two Message-driven beans in this example:

The example also includes 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 a Quantity (int). The method decreaseQuantity(int qty) decreases the quantity for the corresponding stockid, but can 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 known values in order to check the results of updates at the end of the test. Eleven messages are sent, the corresponding transactions are committed, and the last message sent causes the transaction to be rolled back.

Compiling this example

To compile examples/src/mdb/sampleappli, use Ant with 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 indicates that the JMS Server will be launched in the same JVM as the JOnAS Server, and the JMS-administered objects StockHandlerTopic (Topic) and OrdersQueue (Queue) will be created and registered in JNDI, if not already existing.

Tuning Message-driven Bean Pool

A pool is handled by JOnAS for each Message-driven bean. The pool can 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 beans. The default value is 0.

max-cache-size

This optional integer value represents the maximum number of instances of ServerSession that may be created in memory. The purpose of this value is to keep JOnAS scalable. The policy is the following:
When the ConnectionConsumer ask for a ServerSession instance (in order to deliver a new message) JOnAS tries to give an instance from the ServerSessionPool. If the pool is empty, a new instance is created only if the number of yet created instances is smaller than the max-cache-size parameter. When the max-cache-size is reached, the ConnectionConsumer is blocked and it cannot deliver new messages until a ServerSession is eventually returned in the pool. A ServerSession is pushed into the pool at the end of the onMessage method.
The default value is no limit (this means that a new instance of ServerSession is always created when the pool is empty).

The values for max-cache-size should be set accordingly to jonas.service.ejb.maxworkthreads value. See Configuring JMS Service.

example

  <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>