Message Driven Beans (MDBs) are a new type of bean added in the EJB 2.0 specification. The reason why these beans were added is that there was no way in EJB 1.1 to handle asynchronous invocation. The primary reason for this is that an EJB bean could never be invoked from other objects through anything other than its remote interface. Therefore a bean could never set itself up as a listener for asynchronous invocation.
This limitation is overcome by Message Drive Beans. An MDB is a bean without a remote interface; the container sets itself up as a listener for asynchronous invocation of the MDB and handles the invocation of the concrete bean. Otherwise, MDBs follow all the usual roles for EJB beans.
Message Driven Beans are primarily focused on JMS. An MDB is either a topic or a queue subscriber. One nice feature with MDBs is that one gets multithreaded subscribers (even for topics) without having to deal with the subtle difficulties of writing multithreaded code for JMS message consumers.
For what purpose should you use an MDB? Basically, you would use an MDB any time you are about to create a JMS subscriber. Typical conditions for doing this are:
If you already know how to code a Message Driven Bean, it is quite easy to deploy it in JBoss. The only thing you will have to do is to add a JBoss-specific deployment descriptor, or, if you already have a jboss.xml deployment descriptor for you module, add the MDB part to it.
[2.4.0. It should actually be possible to deploy an MDB without the JBoss-specific deployment descriptor, although this is something that I do not recommend; for more on this, read ahead.]
If you don't need to do any special configuration and already know how to code a Message Driven Bean, here there are five easy steps to get it up and running with JBoss.
Write the ejb-jar.xml descriptor.
Here it is an example for a bean listening on a queue using bean-managed transactions:
<?xml version="1.0"?> <!DOCTYPE ejb-jar> <ejb-jar> <enterprise-beans> <message-driven> <ejb-name>HelloQueueBMTMDB</ejb-name> <ejb-class>org.jboss.docs.jms.mdb.bean.HelloMDB</ejb-class> <message-selector></message-selector> <transaction-type>Bean</transaction-type> <acknowledge-mode>Auto-acknowledge</acknowledge-mode> <message-driven-destination> <destination-type>javax.jms.Queue</destination-type> </message-driven-destination> </message-driven> </enterprise-beans> </ejb-jar>
And here it is one for a durable topic using container-managed transactions:
<?xml version="1.0"?> <!DOCTYPE ejb-jar> <ejb-jar> <enterprise-beans> <message-driven> <ejb-name>HelloTopicDurableMDB</ejb-name> <ejb-class>org.jboss.docs.jms.mdb.bean.HelloMDB</ejb-class> <message-selector></message-selector> <transaction-type>Container</transaction-type> <message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>Durable</subscription-durability> </message-driven-destination> </message-driven> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>HelloTopicDurableMDB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
Write the jboss.xml deployment descriptor; note that one does not need to fill in the container configuration for an MDB that has no special requirements.
[2.4.0. It is possible to avoid doing this, in which case the destination will then be automatically created and named after the bean; this may lead to quite surprising results, so it's not anything I recommend.]
The destination-jndi-name element points to the queue.
Here it is one the for the queue bean:
<?xml version="1.0" encoding="Cp1252"?> <jboss> <enterprise-beans> <message-driven> <ejb-name>HelloQueueMDB</ejb-name> <configuration-name>Standard Message Driven Bean</configuration-name> <destination-jndi-name>queue/testQueue</destination-jndi-name> </message-driven> </enterprise-beans> </jboss>
And here it is the one for the durable topic:
<?xml version="1.0" encoding="Cp1252"?> <jboss> <enterprise-beans> <message-driven> <ejb-name>HelloTopicDurableMDB</ejb-name> <configuration-name>Standard Message Driven Bean</configuration-name> <destination-jndi-name>topic/testTopic</destination-jndi-name> <mdb-user>john</mdb-user> <mdb-passwd>needle</mdb-passwd> <mdb-subscriber-id>DurableSubscriberExample</mdb-subscriber-id> </message-driven> </enterprise-beans> </jboss>
Edit the file jbossmq.xml in directory conf/default and add the queue or topic. For example:
<Queue> <Name>testQueue</Name> </Queue>
for a queue and
<Topic> <Name>testDurableTopic</Name> </Topic>
plus
<User> <Name>john</Name> <Password>needle</Password> <Id>DurableSubscriberExample</Id> <DurableSubscription> <Name>DurableSubscriberExample</Name> <TopicName>testDurableTopic</TopicName> </DurableSubscription> </User>
for a durable topic.
[2.4.1. The addition of destinations is done in the jboss.jcml file, instead:
<mbean code="org.jbossmq.server.TopicManager" name="JBossMQ:service=Topic,name=testTopic"/>
Users are instead configured in the file jbossmq-state.xml, also found in directory conf/default. The entries look the same as those above.]
Deploy the bean, for example, by packing it in a JAR file and copying it into the JBoss deploy directory.
At this point, your MDB is ready to be used, so you may start sending messages to it.
Since MDBs are still a recent addition to the EJB portfolio, they are still somewhat unknown and therefore not widely used yet. We will therefore begin by giving you a couple of simple examples of how to write and use Message Driven Beans.
An MDB follows a typical EJB contract. It must implement the following two interfaces:
An MDB must therefore typically contain the following four methods:
Figure 8.44. Message Driven Bean required methods
public void setMessageDrivenContext(MessageDrivenContext ctx); public void ejbCreate(); public void ejbRemove(); public void onMessage(Message message);
The full program listing of a simple “Hello World” bean could look like this:
Figure 8.45. Hello World Message Driven Bean example, from HelloMDB.java in directory org/jboss/docs/jms/mdb/bean.
package org.jboss.docs.jms.mdb.bean; import javax.ejb.MessageDrivenBean; import javax.ejb.MessageDrivenContext; import javax.ejb.EJBException; import javax.jms.MessageListener; import javax.jms.Message; /** * Simple HelloWorld Message Driven Bean. May be bound to both a Topic * or a Queue through the deployment descriptor. * * Created: Thu Jul 26 13:20:32 2001 * * @author Peter Antman * @version $Revision: 3.1 $ $Date: 2002/01/09 00:29:47 $ */ public class HelloMDB implements MessageDrivenBean, MessageListener { private MessageDrivenContext ctx = null; public HelloMDB() { } //--- MessageDrivenBean public void setMessageDrivenContext(MessageDrivenContext ctx) throws EJBException { this.ctx = ctx; } public void ejbCreate() {} public void ejbRemove() {ctx=null;} //--- MessageListener public void onMessage(Message message) { System.err.println("Bean got message" + message.toString()); } } // HelloMDB
To deploy this MDB into JBoss we will have to write two deployment descriptors. One standard (ejb-jar.xml) and one that is specific to JBoss (jboss.xml). We will make this bean a Topic subscriber and, since it does not do anything important, we will use container-managed transactions with the NotSupported attribute (although this would not be the best thing to do, in most cases).
Figure 8.46. ejb-jar.xml deployment descriptor for topic Hello World MDB, from HelloMDB-Topic-ejb-jar.xml in directory org/jboss/docs/jms/resources
<?xml version="1.0"?> <!DOCTYPE ejb-jar> <ejb-jar> <enterprise-beans> <message-driven> <ejb-name>HelloTopicMDB</ejb-name> <ejb-class>org.jboss.docs.jms.mdb.bean.HelloMDB</ejb-class> <message-selector></message-selector> <transaction-type>Container</transaction-type> <message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>NonDurable</subscription-durability> </message-driven-destination> </message-driven> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>HelloTopicMDB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
We also need to write a small deployment descriptor (jboss.xml) that is specific to JBoss. The full-blown version of this is quite big, as it allows to configure the MDB container quite a bit. For most users this is not necessary, so we will use the standard configuration. The most important part of the descriptor is the specification of the destination. We choose the testTopic because it is always available in JBoss.
Figure 8.47. JBoss-specific deployment descriptor for topic Hello World MDB, from HelloMDB-Topic-jboss.xml in directory org/jboss/docs/jms/resources
<?xml version="1.0" encoding="Cp1252"?> <jboss> <enterprise-beans> <message-driven> <ejb-name>HelloTopicMDB</ejb-name> <configuration-name>Standard Message Driven Bean</configuration-name> <destination-jndi-name>topic/testTopic</destination-jndi-name> </message-driven> </enterprise-beans> </jboss>
Then we will have to pack the three files into a JAR file. Here it is a snippet from an Ant build file that shows how to pack the bean:
Figure 8.48. Ant build example for packing MDB in a JAR file
<target name="hello-topic-jar" depends="compile"> <delete dir="${build.jms.dir}/META-INF"/> <mkdir dir="${build.jms.dir}/META-INF"/> <copy file="${jms.resource.dir}/HelloMDB-Topic-ejb-jar.xml" tofile="${build.jms.dir}/META-INF/ejb-jar.xml" /> <copy file="${jms.resource.dir}/HelloMDB-Topic-jboss.xml" tofile="${build.jms.dir}/META-INF/jboss.xml" /> <jar jarfile="${build.jms.dir}/HelloTopicMDB.jar"> <fileset dir="${build.classes.dir}"> <include name="org/jboss/docs/jms/mdb/bean/HelloMDB.class" /> </fileset> <fileset dir="${build.jms.dir}"> <include name="META-INF/ejb-jar.xml" /> <include name="META-INF/jboss.xml" /> </fileset> </jar> </target>
We will need to copy the JAR file to the deploy directory of JBoss to get the MDB installed.
To send messages to the bean you need a JMS publisher. This involves standard JMS programming, so you may use the HelloPublisher example from the section called “Examples” to do that.
The example code for the bean is found in file HelloMDB.java in directory org/jboss/docs/jms/mdb/bean. Example deployment descriptors are available in files HelloMDB-Topic-ejb-jar.xml and HelloMDB-Topic-jboss.xml in directory org/jboss/docs/jms/resources. You may also run the example via the Ant build file:
The output in the window where you started JBoss will typically look something similar to this:
Figure 8.50. Output of running the topic Hello World MDB
[Auto deploy] Auto deploy of file:/home/pra/jboss/deploy/HelloTopicMDB.jar [J2EE Deployer Default] Deploy J2EE application: file:/home/pra/jboss/deploy/HelloTopicMDB.jar [J2EE Deployer Default] Create application HelloTopicMDB.jar [J2EE Deployer Default] install module HelloTopicMDB.jar [Container factory] Deploying:file:/home/pra/jboss/tmp/deploy/Default/HelloTopicMDB.jar [Verifier] Verifying file:/home/pra/jboss/tmp/deploy/Default/HelloTopicMDB.jar/ejb1028.jar [Container factory] Deploying HelloTopicMDB [Container factory] Deployed application: file:/home/pra/jboss/tmp/deploy/Default/HelloTopicMDB.jar [J2EE Deployer Default] J2EE application: file:/home/pra/jboss/deploy/HelloTopicMDB.jar is deployed. [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 1 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 2 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 4 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 5 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 3 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 8 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 9 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 7 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 6 [HelloTopicMDB] Bean got messageTextMessage@Hello World no. 10 [Auto deploy] Auto undeploy of file:/home/pra/jboss/deploy/HelloTopicMDB.jar [J2EE Deployer Default] Stopping module HelloTopicMDB.jar [Container factory] Undeploying:file:/home/pra/jboss/tmp/deploy/Default/HelloTopicMDB.jar [Container factory] Undeployed application: file:/home/pra/jboss/tmp/deploy/Default/HelloTopicMDB.jar [J2EE Deployer Default] Destroying application HelloTopicMDB.jar
There are several other examples available with alternative configurations. All the deployment files begin with the prefix HelloMDB-. The following Ant targets are available to run the examples based on HelloMDB:
Here we will look at an example that does something more real. One nice way to use MDBs is to have them perform the listener pattern. Normally, listeners register themselves with the object emitting events; but, this is not possible for an MDB, since the bean itself may only do some work when it receives an event (a message). The set-up of an MDB as a listener will therefore have to be done outside of the MDB. This could involve something as simple as defining a topic or queue and hardwire it into the event generator to get it to publish its events/messages to that destination. It's also possible to create a more generic framework for message-driven callbacks, something I have done with JMS and JMX. But that is for another document. Let's instead look at things on the MDB side.
One way to partition the logic with EJBs is to have one bean that does the real work (contains the logic), a stateless session bean or an entity bean, for example, and another bean acting as a listener. Let's write a working, but simplified, version of this pattern. We start with a very simple stateless session bean containing just one method: doWork. To make it easy we let it take a String as its payload. This is straightforward; first, we need a home interface:
Figure 8.52. Home interface for worker bean, from HelloWorkerHome.java in directory org/jboss/docs/jms/mdb/interfaces
package org.jboss.docs.jms.mdb.interfaces; import java.rmi.RemoteException; import javax.ejb.EJBHome; import javax.ejb.CreateException; /** * Home for HelloWorker. * * Created: Thu Jul 26 16:02:46 2001 * * @author Peter Antman * @version $Revision: 3.1 $ $Date: 2002/01/09 00:29:47 $ */ public interface HelloWorkerHome extends EJBHome { public HelloWorker create() throws RemoteException, CreateException; } // HelloWorkerHome
We also need a remote interface:
Figure 8.53. Remote interface for worker bean, from HelloWorker.java in directory org/jboss/docs/jms/mdb/interfaces
package org.jboss.docs.jms.mdb.interfaces; import java.rmi.RemoteException; import javax.ejb.EJBObject; /** * Interface for HelloWorker. * * Created: Thu Jul 26 15:50:06 2001 * * @author Peter Antman * @version $Revision: 3.1 $ $Date: 2002/01/09 00:29:47 $ */ public interface HelloWorker extends EJBObject { /** * Just a demo work method. */ public void doWork(String work) throws RemoteException; } // HelloWorker
And, finally, we need the actual implementation bean class:
Figure 8.54. Listener pattern worker bean, from HelloWorkerBean.java in directory org/jboss/docs/jms/mdb/bean
package org.jboss.docs.jms.mdb.bean; import javax.ejb.SessionBean; import javax.ejb.SessionContext; import javax.ejb.CreateException; import java.rmi.RemoteException; /** * A worker session bean. When invoked through an MDB listener, this * bean will be invoked asynchronously by sending messages to the JMS * destination to which the MDB is configured as a subscriber. * * @author Peter Antman * @version $Revision: 3.1 $ */ public class HelloWorkerBean implements SessionBean { private SessionContext ctx; public HelloWorkerBean() { } // The usual sessions ejb methods. public void setSessionContext(javax.ejb.SessionContext ctx) throws RemoteException { this.ctx = ctx; } public void unsetSessionContext() throws RemoteException { this.ctx = null; } public void ejbActivate() throws RemoteException {} public void ejbPassivate() throws RemoteException {} public void ejbRemove() throws RemoteException {} public void ejbCreate() throws CreateException {} /** * Do the work, invoked from MDB listener. I.e, this bean will be * invoked asynchronously by sending messages to the MDB listener. */ public void doWork(String work) { System.out.println("WorkerBean doing work: " + work); } } // HelloWorkerBean
We will write the deployment descriptor for the bean later, since we will pack it with the listener.
The next step is to write the listener bean. Here we will add basically one thing: the ability to look up the worker bean and invoke it. We look up the bean through a JNDI reference defined via a ejb-ref element. This might be done in ejbCreate():
Figure 8.55. Looking up the worker bean
Context initCtx = new InitialContext(); workerHome = (HelloWorkerHome)initCtx.lookup("java:comp/env/ejb/worker");
In the onMessage() method, we process the message. For example, here we could have sent an object of some kind, known to the listener. For simplicity, we choose to send the content of the TextMessage to the worker bean:
Figure 8.56. Invoke worker bean from the onMessage() method
// Get the worker HelloWorker worker = workerHome.create(); // Get the message, here we could have an ObjectMessage containing an // object of a known class. We use Text here for simplicity if (message instanceof TextMessage) { TextMessage m = (TextMessage)message; // invoke the worker bean worker.doWork(m.getText()); }
Here it is the complete listing of the class:
Figure 8.57. Listener bean, from HelloListener.java in directory org/jboss/docs/jms/mdb/bean
package org.jboss.docs.jms.mdb.bean; import javax.ejb.MessageDrivenBean; import javax.ejb.MessageDrivenContext; import javax.ejb.EJBException; import javax.ejb.CreateException; import javax.naming.InitialContext; import javax.naming.Context; import javax.jms.MessageListener; import javax.jms.Message; import javax.jms.TextMessage; import org.jboss.docs.jms.mdb.interfaces.HelloWorker; import org.jboss.docs.jms.mdb.interfaces.HelloWorkerHome; /** * Simple MDB showing the listener pattern. * Gets messages from a destination and invokes a worker session bean * (actually it could as well do that work itself). * * Created: Thu Jul 26 13:20:32 2001 * * @author Peter Antman * @version $Revision: 3.1 $ $Date: 2002/01/09 00:29:47 $ */ public class HelloListener implements MessageDrivenBean, MessageListener { /** * Session worker bean */ private HelloWorkerHome workerHome = null; private MessageDrivenContext ctx = null; public HelloListener() { } //--- MessageDrivenBean public void setMessageDrivenContext(MessageDrivenContext ctx) throws EJBException { this.ctx = ctx; } public void ejbCreate() throws CreateException{ // Get the worker bean home try { Context initCtx = new InitialContext(); workerHome = (HelloWorkerHome)initCtx.lookup("java:comp/env/ejb/worker"); } catch(Exception ex) { throw new CreateException("Could not get worker: " + ex); } } public void ejbRemove() {ctx=null;} //--- MessageListener public void onMessage(Message message) { System.out.println("HelloListen got message " + message.toString()); try { // Get the worker HelloWorker worker = workerHome.create(); // Get the message, here we could have an ObjectMessage containing // an object of a known class. We use Text here for simplicity if (message instanceof TextMessage) { TextMessage m = (TextMessage)message; // invoke the worker bean worker.doWork(m.getText()); } } catch(Exception ex) { // If a container-managed transaction with required, this will // roll back the message received and also all beans and resource // calls from this one that inherits the transactional context. throw new EJBException("Could not call worker " + ex); } } } // HelloListener
To deploy this MDB in JBoss we need to write two deployment descriptors, one standard and one specific for JBoss. Lets begin with the standard one. For ease of use we will include both beans into one JAR file. For the Message Driven Bean we have to decide whether it is a topic or not and what kind of transaction mode it should use. In this case, we choose to use a topic and container-managed transactions. We also have to specify an ejb-ref element so that the listener can look up the home of the worker bean:
Figure 8.58. Message Driven Bean part in ejb-jar.xml for listener
<message-driven> <ejb-name>HelloListener</ejb-name> <ejb-class>org.jboss.docs.jms.mdb.bean.HelloListener</ejb-class> <message-selector></message-selector> <transaction-type>Container</transaction-type> <ejb-ref> <description>The Workers home</description> <ejb-ref-name>ejb/worker</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <ejb-link>HelloWorkerBean</ejb-link> <home>org.jboss.docs.jms.mdb.interfaces.HelloWorkerHome</home> <remote>org.jboss.docs.jms.mdb.interfaces.HelloWorker</remote> </ejb-ref> <message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>NonDurable</subscription-durability> </message-driven-destination> </message-driven>
We also have to add an entry for the worker bean:
Figure 8.59. Session part in ejb-jar.xml for listener
<session> <description>Worker bean</description> <display-name>HelloWorkerBean</display-name> <ejb-name>HelloWorkerBean</ejb-name> <home>org.jboss.docs.jms.mdb.interfaces.HelloWorkerHome</home> <remote>org.jboss.docs.jms.mdb.interfaces.HelloWorker</remote> <ejb-class>org.jboss.docs.jms.mdb.bean.HelloWorkerBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session>
Here it is the complete deployment descriptor, including definitions of the transaction type.
Figure 8.60. ejb-jar.xml for listener bean example, from HelloListener-ejb-jar.xml in directory org/jboss/docs/jms/resources
<?xml version="1.0"?> <!DOCTYPE ejb-jar> <ejb-jar> <enterprise-beans> <message-driven> <ejb-name>HelloListener</ejb-name> <ejb-class>org.jboss.docs.jms.mdb.bean.HelloListener</ejb-class> <message-selector></message-selector> <transaction-type>Container</transaction-type> <ejb-ref> <description>The Workers home</description> <ejb-ref-name>ejb/worker</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <ejb-link>HelloWorkerBean</ejb-link> <home>org.jboss.docs.jms.mdb.interfaces.HelloWorkerHome</home> <remote>org.jboss.docs.jms.mdb.interfaces.HelloWorker</remote> </ejb-ref> <message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>NonDurable</subscription-durability> </message-driven-destination> </message-driven> <session> <description>Worker bean</description> <display-name>HelloWorkerBean</display-name> <ejb-name>HelloWorkerBean</ejb-name> <home>org.jboss.docs.jms.mdb.interfaces.HelloWorkerHome</home> <remote>org.jboss.docs.jms.mdb.interfaces.HelloWorker</remote> <ejb-class>org.jboss.docs.jms.mdb.bean.HelloWorkerBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>HelloListener</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>HelloWorkerBean</ejb-name> <method-intf>Remote</method-intf> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
We also need to write a jboss.xml deployment descriptor, specific for JBoss. This is needed because the destination JNDI name must be defined somewhere: we cannot simply use a default.
Figure 8.61. JBoss deployment descriptor for listener bean, from HelloListener-jboss.xml in directory org/jboss/docs/jms/resources
<?xml version="1.0"?> <jboss> <enterprise-beans> <message-driven> <ejb-name>HelloListener</ejb-name> <configuration-name>Standard Message Driven Bean</configuration-name> <destination-jndi-name>topic/testTopic</destination-jndi-name> </message-driven> <secure>false</secure> </enterprise-beans> </jboss>
Now we have to compile the beans. You will have to add several JAR files to your classpath to succeed at this. Among the most important, especially for MDB, is ejb2.0.jar in the lib/ext directory of the standard JBoss installation.
[2.4.0. It is jboss-j2ee.jar in directory client.]
Afterwards, you will need to pack the beans into a JAR file and deploy them by copying the JAR file to the JBoss directory deploy. You may use the sample publisher HelloPublisher from the section called “Examples” to publish messages to the bean.
The example code is found in the files HelloListener.java and HelloWorkerBean.java in directory org/jboss/docs/jms/mdb/bean and HelloWorker.java and HelloWorkerHome.java in directory org/jboss/docs/jms/mdb/interfaces. The deployment descriptors are in files HelloListener-ejb-jar.xml and HelloListener-jboss.xml in directory org/jboss/docs/jms/resources.
It is possible to run the example through the Ant build file:
The output should look somewhat like this:
Figure 8.63. Output of running the HelloListener example
[Default] WorkerBean doing work: Hello World no. 3 [Default] WorkerBean doing work: Hello World no. 5 [Default] WorkerBean doing work: Hello World no. 2 [Default] WorkerBean doing work: Hello World no. 6 [Default] WorkerBean doing work: Hello World no. 4 [Default] WorkerBean doing work: Hello World no. 1 [Default] HelloListen got messageTextMessage@Hello World no. 7 [Default] WorkerBean doing work: Hello World no. 7 [Default] HelloListen got messageTextMessage@Hello World no. 8 [Default] WorkerBean doing work: Hello World no. 8 [Default] HelloListen got messageTextMessage@Hello World no. 9 [Default] WorkerBean doing work: Hello World no. 9
The example may not always work, since it depends on the deployment of the beans (done by simple copying) being finished before the client starts. If it does not work, run it several times.
Another way to use an MDB is to employ it as an adapter, for example, between different messaging systems. This is rewarding, especially if you have a J2EE Connector resource adapter for the other system, since you will get then a very effective, multithreaded and pooled system, without having to write any advanced programming. Since I have written such a resource adapter for the messaging server XmlBlaster, we will look here at one way to adapt JMS to it. To get the source code, check out the XmlBlaster source.
The adapter uses an MDB to subscribe to a topic. It then republishes the messages to an XmlBlaster server through the XmlBlaster K2 J2EE Connector adapter. The code is fairly simple. A J2EE Connector resource adapter must be deployed in the JBoss server. It is then referenced from within the bean in the same manner you would reference a JDBC resource. You would look it up this way:
factory = (BlasterConnectionFactory) new InitialContext().lookup("java:comp/env/xmlBlaster");
And use it this way:
con = factory.getConnection(); // Construct Blaster Headers String key = "<key oid=\"" + message.getJMSMessageID() + "\" contentMime=\"text/xml\"></key>"; String qos = "<qos></qos>"; con.publish(new MessageUnit(key,msg.getBytes(),qos));
The complete bean is pretty straightforward (almost the same code could be used to write directly to a database, for example):
package javaclients.j2ee.k2; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.ejb.MessageDrivenBean; import javax.ejb.MessageDrivenContext; import javax.ejb.EJBException; import javax.jms.MessageListener; import javax.jms.Message; import javax.jms.TextMessage; import javax.jms.JMSException; import javax.resource.ResourceException; import org.xmlBlaster.j2ee.k2.client.*; import org.xmlBlaster.util.XmlBlasterException; import org.xmlBlaster.engine.helper.MessageUnit; public class JmsAdapter implements MessageDrivenBean, MessageListener{ private MessageDrivenContext ctx = null; private BlasterConnectionFactory factory = null; public JmsAdapter() { } public void setMessageDrivenContext(MessageDrivenContext ctx) throws EJBException { this.ctx = ctx; try { factory = (BlasterConnectionFactory) new InitialContext().lookup("java:comp/env/xmlBlaster"); } catch (NamingException ex) { throw new EJBException ("XmlBlaster not found: " + ex.getMessage()); } catch(Throwable th) { System.err.println("Throwable: " + th); th.printStackTrace(); throw new EJBException("Throwable in setContext: " + th); } } public void ejbCreate() {} public void ejbRemove() {ctx=null;} public void onMessage(Message message) { BlasterConnection con = null; try { // Get message to handle System.err.println("Got message: " + message); if (message instanceof TextMessage) { String msg = ((TextMessage)message).getText(); // Get connection con = factory.getConnection(); // Construct Blaster Headers - how to handle key here? String key = "<key oid=\"" + message.getJMSMessageID() + "\" contentMime=\"text/xml\"></key>"; String qos = "<qos></qos>"; con.publish(new MessageUnit(key,msg.getBytes(),qos)); } else { System.err.println("Got message type I cant handle"); } } catch(ResourceException re) { System.err.println("Resource ex: " + re); re.printStackTrace(); } catch(XmlBlasterException be) { System.err.println("Blaster ex: " + be); be.printStackTrace(); } catch(JMSException je) { System.err.println("JMSException ex: " + je); je.printStackTrace(); } catch(Throwable th) { System.err.println("Throwable: " + th); th.printStackTrace(); } finally { try { if (con != null) con.close (); } catch (Exception ex) {} } } } // MessageBeanImpl
The deployment descriptors for this bean are typical, except that you will have to add entries for the resource-ref element. Here it is the standard EJB deployment descriptor:
<?xml version="1.0"?> <!DOCTYPE ejb-jar> <ejb-jar> <enterprise-beans> <message-driven> <ejb-name>JmsAdapter</ejb-name> <ejb-class>javaclients.j2ee.k2.JmsAdapter</ejb-class> <message-selector></message-selector> <transaction-type>Container</transaction-type> <resource-ref> <res-ref-name>xmlBlaster</res-ref-name> <res-type>org.xmlBlaster.j2ee.k2.client.BlasterConnectionFactory</res-type> <res-auth>Container</res-auth> </resource-ref> <message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>NonDurable</subscription-durability> </message-driven-destination> </message-driven> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>JmsAdapter</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
And here it is the jboss.xml descriptor specific to JBoss:
<?xml version="1.0" encoding="Cp1252"?> <jboss> <resource-managers> <resource-manager> <res-name>xmlBlaster</res-name> <res-jndi-name>java:/XmlBlasterDS</res-jndi-name> </resource-manager> </resource-managers> <enterprise-beans> <message-driven> <ejb-name>JmsAdapter</ejb-name> <configuration-name>Standrad Message Driven Bean</configuration-name> <destination-jndi-name>topic/testTopic</destination-jndi-name> <resource-ref> <res-ref-name>xmlBlaster</res-ref-name> <resource-name>xmlBlaster</resource-name> </resource-ref> </message-driven> <secure>false</secure> </enterprise-beans> </jboss>
The implementation of MDB support in JBoss has been written in such a way that a user should not need to know a lot about the internals of JBoss to use MDBs. Apart from the few necessary lines in jboss.xml, just standard knowledge about Message Driven Beans should suffice.
Following the design of other parts of JBoss, the MDB implementation is also extremely configurable, if one wants to tune or totally change the default configuration and implementation! Here, there follow some short notes on how to configure MDBs in JBoss.
All MDBs are quite configurable. Here there are the basic choices that can be made:
A bean may be either a javax.jms.Topic or a javax.jms.Queue bean; this is specified in the destination-type element. Up to 2.4.0, this part of JBoss is not standard-compliant. The message-driven-destination element is actually an optional element in the EJB 2.0 DTD of the ejb-jar.xml, but in JBoss it is required because there is no other way for JBoss to know to which type of JMS destination the bean should be bound.. In [2.4.1] this has been solved, and the element is now optional. If no message-driven-destination is given, JBoss will try to deduce one from the given destination in jboss.xml. If this is not possible it will default to a topic type.
<message-driven-destination> <destination-type>javax.jms.Queue</destination-type> <subscription-durability>NonDurable</subscription-durability> </message-driven-destination>
If a bean is a Topic it may be either non-durable or durable, as specified in the subscription-durability element.
<message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>Durable</subscription-durability> </message-driven-destination>
A bean may be have transactions managed by either the bean or the container, as specified in the transaction-type element.
<transaction-type>Container</transaction-type>
A bean with bean-managed transactions may have an acknowledgement type of either Auto-acknowledge or Dups-ok-acknowledge. This is currently not supported in JBoss since the container is always receiving messages under a transaction, but it is specified in the acknowledge-mode element.
<transaction-type>Bean</transaction-type> <acknowledge-mode>Auto-acknowledge</acknowledge-mode>
A bean with container-managed transactions may either specify that requires transactions or that it does not support them, as specified in the trans-attribute element.
<assembly-descriptor> <container-transaction> <method> <ejb-name>HelloTopicDurableMDB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor>
An MDB may also use a selector which follows the JMS selector syntax, as specified in the message-selector element.
<message-selector>JMSType='activityCompletion'</message-selector>
Of course, inside a message-driven stanza one may also specify other elements typical of an EJB deployment descriptor, such as ejb-ref and resource-ref elements.
The meat of the MDB configuration options is in the jboss.xml deployment descriptor.
[2.4.0. This file is optional starting with this version of JBoss. In such a case, a destination to which to bind the bean is not specified and JBoss will create a destination for the bean, with the name of the bean. This might be handy, but it is also dangerous since misspelled destinations may get you way wrong; and you will also have to tie the clients to a destination that is only temporarily available, as the destination will be destroyed if the MDB bean is undeployed or redeployed.]
The configuration in this file may be divided in two parts: The mandatory one to configure a particular bean and the optional one to configure the container, which will default to the contents in standardjboss.xml. We will describe both here, but the container configuration will be broken into several parts since it involves stuff outside the jboss.xml file.
In the bean part one always has to specify the JNDI for the JMS destination. In JBossMQ this is always composed of either the prefix topic/ or the prefix queue/ followed by the name of the destination.
Figure 8.64. Defining a destination in jboss.xml
<destination-jndi-name>topic/testTopic</destination-jndi-name>
You also have to specify the bean name and the name of the container configuration. To use the one in standardjboss.xml, provide the name Standard Message Driven Bean.
Figure 8.65. Example jboss.xml for MDB
<message-driven> <ejb-name>HelloTopicMDB</ejb-name> <configuration-name>Standard Message Driven Bean</configuration-name> <destination-jndi-name>topic/testTopic</destination-jndi-name> </message-driven>
It is also possible to use a user name and a password (configurable in conf/default/jbossmq.xml) to log in to JBossMQ. If you have specified a Durable topic, they are required along with the subscription Id that identifies the durable subsripition that will be used. Using one of the pre-defined values in jbossmq.xml we could have a deployment descriptor looking like this:
Figure 8.66. Example jboss.xml for MDB with durable subscription
<message-driven> <ejb-name>HelloTopicDurableMDB</ejb-name> <configuration-name>Standard Message Driven Bean</configuration-name> <destination-jndi-name>topic/testTopic</destination-jndi-name> <mdb-user>john</mdb-user> <mdb-passwd>needle</mdb-passwd> <mdb-subscriber-id>DurableSubscriberExample</mdb-subscriber-id> </message-driven>
The standard container configuration for MDB is in standardjboss.xml. It is possible, however, to override this configuration by specifying other values in the jboss.xml deployment descriptor. We will first see the complete set before going through the individual entries.
Figure 8.67. Container configuration for MDB, from HelloMDB-Topic-FullConf-jboss.xml in directory org/jboss/docs/jms/resources
<?xml version="1.0"?> <jboss> <enterprise-beans> <message-driven> <ejb-name>HelloTopicMDB</ejb-name> <configuration-name>My Message Driven Config</configuration-name> <destination-jndi-name>topic/testTopic</destination-jndi-name> </message-driven> </enterprise-beans> <container-configurations> <container-configuration> <container-name>My Message Driven Config</container-name> <call-logging>false</call-logging> <container-invoker>org.jboss.ejb.plugins.jms.JMSContainerInvoker</container-invoker> <container-interceptors> <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor> <!-- CMT --> <interceptor transaction="Container">org.jboss.ejb.plugins.TxInterceptorCMT</interceptor> <interceptor transaction="Container" metricsEnabled="true">org.jboss.ejb.plugins.MetricsInterceptor</interceptor> <interceptor transaction="Container">org.jboss.ejb.plugins.MessageDrivenInstanceInterceptor</interceptor> <!-- BMT --> <interceptor transaction="Bean">org.jboss.ejb.plugins.MessageDrivenInstanceInterceptor</interceptor> <interceptor transaction="Bean">org.jboss.ejb.plugins.MessageDrivenTxInterceptorBMT</interceptor> <interceptor transaction="Bean" metricsEnabled="true">org.jboss.ejb.plugins.MetricsInterceptor</interceptor> </container-interceptors> <instance-pool>org.jboss.ejb.plugins.MessageDrivenInstancePool</instance-pool> <instance-cache></instance-cache> <persistence-manager></persistence-manager> <transaction-manager>org.jboss.tm.TxManager</transaction-manager> <container-invoker-conf> <JMSProviderAdapterJNDI>DefaultJMSProvider</JMSProviderAdapterJNDI> <ServerSessionPoolFactoryJNDI>StdJMSPool</ServerSessionPoolFactoryJNDI> <MaximumSize>15</MaximumSize> <MaxMessages>1</MaxMessages> <Optimized>True</Optimized> </container-invoker-conf> <container-pool-conf> <MaximumSize>100</MaximumSize> <MinimumSize>10</MinimumSize> </container-pool-conf> </container-configuration> </container-configurations> </jboss>
Let's go through some of the configurable attributes of the MDB container
The container invoker is what sends messages into the container system. It is responsible for handling everything that has to do with JMS. The rest of the MDB container parts are basically agnostic regarding what kind of message system the container invoker uses. It is therefore possible to write a new container invoker for other types of message systems. There is currently one limitation to this. The bean class still has to implement the MessageListener interface and the invoker therefore has to adopt this interface. This will be made pluggable in a later release of MDB.
The container interceptors are an integral part of the container system and each of them does some particular thing needed to fulfill the EJB container contract. All of them are pluggable, meaning that it is possible to write new implementations of them and plug them in for a bean with a particular need, even though this should be considered a very advanced task.
This is probably the most interesting part to understand. Lets look at it in smaller pieces. First it defines a JMSProviderAdapterJNDI:
Figure 8.68. Configuring the JMSProviderAdapterJNDI in jboss.xml
<JMSProviderAdapterJNDI>DefaultJMSProvider</JMSProviderAdapterJNDI>
It should contain a JNDI name for a JMX bean where one might lookup a provider adapter class. This class is then used by the invoker to find the names of the connection factories; all IntitialContext lookups are done through this class, to make it possible to get access to a JMS provider outside of JBoss.
The name used by default is bound to the JbossMQProvider in jboss.jcml, which looks like this:
Figure 8.69. Defining a JMSProviderAdapter in jboss.jcml
<mbean code="org.jboss.jms.jndi.JMSProviderLoader" name=":service=JMSProviderLoader,name=JBossMQProvider"> <attribute name="ProviderName">DefaultJMSProvider</attribute> <attribute name="ProviderAdapterClass">org.jboss.jms.jndi.JBossMQProvider</attribute> </mbean>
[2.4.0. It has some additional information in this version.]
Figure 8.70. [2.4.0] Defining a JMSProviderAdapter in jboss.jcml
<mbean code="org.jboss.jms.jndi.JMSProviderLoader" name=":service=JMSProviderLoader,name=JBossMQProvider"> <attribute name="ProviderName">DefaultJMSProvider</attribute> <attribute name="ProviderAdapterClass">org.jboss.jms.jndi.JBossMQProvider</attribute> <attribute name="QueueFactoryRef">java:/INVMXAQueueConnectionFactory</attribute> <attribute name="TopicFactoryRef">java:/INVMXATopicConnectionFactory</attribute> </mbean>
[2.4.1. The JNDI strings have changed in this version.]
Figure 8.71. [2.4.1] Defining a JMSProviderAdapter in jboss.jcml
<mbean code="org.jboss.jms.jndi.JMSProviderLoader" name=":service=JMSProviderLoader,name=JBossMQProvider"> <attribute name="ProviderName">DefaultJMSProvider</attribute> <attribute name="ProviderAdapterClass">org.jboss.jms.jndi.JBossMQProvider</attribute> <attribute name="QueueFactoryRef">java:/INVMXAConnectionFactory</attribute> <attribute name="TopicFactoryRef">java:/INVMXAConnectionFactory</attribute> </mbean>
It is possible, also, to add more JMSProviders to the jboss.jcml and use them in the container configuration. One possible reason to do this is to listen to a queue or topic in another JBoss server. In that case, one could define another provider and configure its context. This is done by adding the attribute ProviderUrl to the JMSProviderLoader MBean configuration. Let's assume that we have a JBoss server on another machine remote.com. We might then add this to jboss.jcml:
Figure 8.72. Defining a remote JMSProviderAdapter in jboss.jcml
<attribute name="ProviderUrl">remote.com:1099</attribute>
Once we added a ProviderUrl attribute, we would write in jboss.xml the following JMSProviderAdapterJNDI element:
Figure 8.73. Configuring a remote JMSProviderAdapter in jboss.xml
<JMSProviderAdapterJNDI>RemoteJMSProvider</JMSProviderAdapterJNDI>
Yet another way to use this configuration option is to integrate another JMS provider into JBoss. This was actually how MDB was first implemented in JBoss through the OpenJMS implementation. To do this, one has to implement the interface org.jboss.jms.jndi.JMSProviderAdapter. Be aware, though, of the fact that if the JMS provider does not support the full JMS Application Service Facility (ASF, Chapter 8 in the JMS specification), you will have to write a full implementation of both the ProviderAdapter and the ServerSession stuff. Doing this is really an advanced subject and it should probably be handled through contacts on the jboss-dev mailing list.
Next we have the ServerSessionPoolFactoryJNDI element:
Figure 8.74. Configuring the ServerSessionPool factory JNDI in jboss.xml
<ServerSessionPoolFactoryJNDI>StdJMSPool</ServerSessionPoolFactoryJNDI>
This also points to a class loaded through jboss.jcml. It is the entry point to the ServerSessionPool. If one needs to write a provider-specific pool or do some customization of the existing one, it is possible to load it for a particular bean. The existing one is defined this way in jboss.jcml:
Figure 8.75. Setting up the ServerSessionPool factory in jboss.jcml
<mbean code="org.jboss.jms.asf.ServerSessionPoolLoader" name=":service=ServerSessionPoolMBean,name=StdJMSPool"> <attribute name="PoolName">StdJMSPool</attribute> <attribute name="PoolFactoryClass">org.jboss.jms.asf.\ StdServerSessionPoolFactory</attribute> </mbean>
The first implementation of MDB for JBoss was based on another server session pool factory, especially written for OpenJMS. Currently, it does not work. What does work is the pluggability of server session pool factories.
The last two entries look like this:
Figure 8.76. Server session pool configuration in jboss.xml
<MaximumSize>15</MaximumSize> <MaxMessages>1</MaxMessages>
The first of these, MaximumSize, defines how large the pool will be in terms of how many session it will have ready to serve incoming messages. The second one, MaxMessages, is used to configure the maximum number of messages a session is allowed to handle at once. I have never tweaked that option, and I do not know if JBossMQ actually supports it. It might enhance performance, if used.