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.
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 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).
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
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 object that will be used by the MDB should be created and registered in JNDI.
This can be done automatically by the JMS service or explicitly by the proprietary administration facilities of the JMS provider (JMS administration). The JMS service creates the destination object if this destination is declared in the jonas.properties file (as specified in the previous section).
Deploy the MDB component in JOnAS.
Note that if the destination object is not already created when deploying a MDB, then the container asks the JMS service to create it on the basis of deployment descriptor content.
When using JMS, it is very important to stop JOnAS using the jonas stop command, and not directly by killing it.
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:
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
).
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.
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
As mentioned before, you may need to change default host or default connection port number. This
requires modifying the a3servers.xml configuration file provided by the JOnAS
delivery in JONAS_ROOT/conf directory. In this case, JOnAS must be configured with the property
jonas.service.jms.collocated set to false
,
and the property jonas.service.jms.url set to joram://host:port
.
Also, the MOM must be previously launched with the JmsServer command.
This command defines a Transaction
property set to fr.dyade.aaa.util.NullTransaction
. If your messages need to be
persistent, you should replace
the -DTransaction=fr.dyade.aaa.util.NullTransaction
option by the
-DTransaction=fr.dyade.aaa.util.ATransaction
option. See the Joram
documentation for more details about this command.
To define more complex 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
jonas admin -a sampleappli.jar
jclient sampleappli.SampleAppliClient
jonas stop
<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>