The content of this guide is the following:
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.
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.
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).
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 use 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
To deploy and run a Message-driven Bean, perform the following steps:
Start the Message-Oriented Middleware (the JMS provider implementation). Refer to the section "Launching the Message-Oriented Middleware."
Create and register in JNDI the JMS destination object that will be used by the MDB.
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 an MDB, the container asks the JMS service to create it based on the deployment descriptor content.
When using JMS, it is very important to stop JOnAS using the jonas stop command; it should not be stopped directly by killing it.
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:
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 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.
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
As mentioned previously, the default host or default connection port
number may need to be changed. This requires modifying the
a3servers.xml configuration file provided by the JOnAS
delivery in JONAS_ROOT/conf directory. For this, 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
. Additionally, the MOM must have been
previously launched with the JmsServer command. This command defines a
Transaction
property set to
fr.dyade.aaa.util.NullTransaction
. If the messages need to
be persistent, replace the
-DTransaction=fr.dyade.aaa.util.NullTransaction
option with
the -DTransaction=fr.dyade.aaa.util.ATransaction
option.
Refer to the Joram documentation for more details about this command. To
define a more complex configuration (e.g., distribution, multi-servers),
refer to the Joram documentation on http://joram.objectweb.org.
There are two Message-driven beans in this example:
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 for 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").
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.
To compile examples/src/mdb/sampleappli
, use Ant
with the $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 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.
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>