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.
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.
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).
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
In order to deploy and run a Message-driven Bean the following steps should be followed:
The Message-Oriented Middleware (the JMS provider implementation) should be started. See the section Launching the Message-Oriented Middleware.
The JMS destination administered object that will be used by the MDB should be created and registered in JNDI.
This is done automatically by declaring it in the jonas.properties file (as specified in the previous section) or explicitly by the proprietary administration facilities of the JMS provider (JMS Administration).
Deploy the MDB component on the EJB server. I.e. launch the EJB server.
jonas start
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:
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
).
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
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.
In this application there are two message driven beans:
StockHandlerBean is a Message Driven Bean listening to a topic and receiving Map messages. The onMessage method runs in the scope of a transaction started by the container. It sends a Text message on a Queue (OrdersQueue) and updates a Stock element by decreasing the stock quantity. If the stock quantity becomes negative an exception is received and the current transaction is marked to rollback.
OrderBean is another Message Driven Bean listening on the OrdersQueue Queue, on receipt of a Text message on this queue it writes the corresponding String as a new line in a file ("Order.txt")
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.
examples/src/mdb/sampleappli
, run the compile script
compile.sh
on Unix or compile.bat
on Windows.$JONAS_ROOT/examples/src/build.xml
file.
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 trueThis 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.
jonas start
jclient sampleappli.SampleAppliClient
jonas stop