EJB Container Integration

The JMS API has recently become an integral part of the J2EE architecture. JMS messages can be used to drive processing in a J2EE container via Message Driven Beans (MDB). Furthermore, all the Enterprise Beans in a J2EE application can send messages to JMS destinations by accessing the JMS API via a JCA resource adapter. Both, Message Driven Beans and the JMS JCA resource adapter will be covered in this chapter.

Message Driven Beans

A Message Driven Bean is a Enterprise Bean which has no remote or home interfaces. The purpose of an MDB is to process messages that get delivered from a JMS destination. The MDB must implement the JMS MessageListener interface. The messages are delivered asynchronously to the onMessage(...) method of the MDB.

Advantages

There are many advantages that a MDB brings to the table when processing JMS messages. One of most important is allowing the J2EE container manage the transaction. Allowing the container to manage transaction removes the responsibility of correctly doing transaction management from the application developer and places that responsibility on the J2EE container.

When an MDB is used to process JMS messages, the container controls the number of threads executing used to deliver messages to MDB. Thread management is an integral part of building scalable applications.

JBoss has another great reason to use a MDB to process JMS messages. It's typically orders of magnitude faster! When the MDB is co-located on the server that is running JBossMQ, an optimized connection is used to deliver the messages to the MDB.

MDB Interface

An MDB must implement both MessageDrivenBean and MessageListener interfaces. Listing 4.1 is the source code to one of the simplest Message Driven Beans that can be created. The onMessage(...) method extracts the text of the message and display it to the console.

Listing 4.1 The source to a simple MDB which displays TextMessages on the console.

import javax.ejb.*;
import javax.jms.*;

/**
 * A MDB which receives TextMessages and displays them on the console.
 */
public class TextMDB implements MessageDrivenBean, MessageListener
{
   public void setMessageDrivenContext(MessageDrivenContext ctx) throws
   EJBException
   {
   }
   
   public void ejbCreate()
   {
   }
   
   public void ejbRemove()
   {
   }
   
   public void onMessage(Message msg)
   {
      try
      {
         TextMessage message = (TextMessage)msg;
         System.out.println("MDB received message:
         "+message.getText());
      }
      catch (JMSException e)
      {
         e.printStackTrace();
      }
   }
}

Deployment Descriptor

A MDB like all other Enterprise Java Beans must use a deployment descriptor to configure the details of how the J2EE container will manage the MDB.

The deployment descriptor configures the type of destination that will be used as the source of the delivered messages. It configures the type of transaction management that the MDB will be using. It also sets the durable subscription and/or message selector that will be used if any.

Listing 4.2 The /META-INF/ejb-jar.xml deployment descriptor for the TextMDB.

<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC 
"-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>Example</ejb-name>
<ejb-class>TextMDB</ejb-class>
<message-selector></message-selector>
<transaction-type>Container</transaction-type>
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
</message-driven>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>Example</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>

The jar file that your MDB gets packaged in should have a XML file called /META-INF/ejb-jar.xml.

You may need to generate additional container specific deployment descriptors in addition to the ejb-jar.xml file. Refer to your J2EE container's user guides for more information.

The additional deployment descriptor that is needed for JBoss should be stored as /META-INF/jboss.xml.

The sample below configures our example bean to use the standard MDB container configuration and for the messages to get delivered from the testQueue destination.

Listing 4.3 The /META-INF/jboss.xml deployment descriptor for the TextMDB.

<?xml version="1.0" encoding="utf-8"?>
<JBoss>
<enterprise-beans>
 <message-driven>
<ejb-name>Example</ejb-name>
<configuration-name>Standard Message Driven
Bean</configuration-name>
<destination-jndi-name>queue/testQueue</destination-jndi-name>
</message-driven>
 <secure>false</secure>
</enterprise-beans>
</JBoss>

If you deploy the sample we have been describing, the JBoss sever console should display messages similar to the following:

23:10:38,887 INFO [MainDeployer] Starting deployment of package: 
file:/C:/Java/JBoss-3.2.1/server/default/deploy/mdb-sample.jar
23:10:39,238 INFO [EjbModule] Creating
23:10:39,248 INFO [EjbModule] Deploying Example
23:10:39,288 INFO [MessageDrivenContainer] Creating
23:10:39,288 INFO [MessageDrivenInstancePool] Creating
23:10:39,288 INFO [MessageDrivenInstancePool] Created
23:10:39,298 INFO [JMSContainerInvoker] Creating
23:10:39,308 INFO [JMSContainerInvoker] Created
23:10:39,308 INFO [MessageDrivenContainer] Created
23:10:39,308 INFO [EjbModule] Created
23:10:39,308 INFO [EjbModule] Starting
23:10:39,308 INFO [MessageDrivenContainer] Starting
23:10:39,318 INFO [JMSContainerInvoker] Starting
23:10:39,318 INFO [DLQHandler] Creating
23:10:39,438 INFO [DLQHandler] Created
23:10:39,548 INFO [DLQHandler] Starting
23:10:39,558 INFO [DLQHandler] Started
23:10:39,558 INFO [JMSContainerInvoker] Started
23:10:39,558 INFO [MessageDrivenInstancePool] Starting
23:10:39,558 INFO [MessageDrivenInstancePool] Started
23:10:39,558 INFO [MessageDrivenContainer] Started
23:10:39,558 INFO [EjbModule] Started
23:10:39,578 INFO [EJBDeployer] Deployed:
file:/C:/Java/JBoss-3.2.1/server/default/deploy/mdb-sample.jar
23:10:39,638 INFO [MainDeployer] Deployed package: 
file:/C:/Java/JBoss-3.2.1/server/default/deploy/mdb-sample.jar

And if you send the “testQueue” a couple of text messages, you should see the MDB display them on the console:

23:10:39,718 INFO [STDOUT] MDB received message: Hello World!
23:10:39,728 INFO [STDOUT] MDB received message: Hello World!
23:10:39,728 INFO [STDOUT] MDB received message: Hello World!

JCA JMS Connections

Enterprise Java Bean developers eventual come across a problem where they need to to use the JMS API within an EJB. The developer may need to check to see how many messages are sitting in queue or they may need to send a new message to a topic. In either case, it is highly recommended that JCA based JMS connection be used by the EJB instead of the standard JMS connection.

This section will examine a MDB that uses JCA JMS Connections. The MDB's purpose is to relay the message that was received in the onMessage(...) method and send it to an output queue that is defined at deployment time. The code for the RelayMDB is shown in Listing 4.4.

Advantages

JCA based connections allow the J2EE container to:

  • Pool JMS connections and Sessions.

  • Manage the JMS transaction and a 2 phase commit to ensure atomic operations across multiple resources.

  • Secure access to the JMS provider.

Looking up the Resource Adapter

The EJB should lookup the JCA JMS connection factory using the bean environment mechanism. In other words, it should look up the connection factory in a JNDI entry under the Java:comp/env JNDI context. At application assembly time, the deployment descriptor will be configured so that a JCA JMS Connection Factory is bound to that entry.

The RelayMDB in Listing 4.4 is initialized by looking up the a QueueConnectionFactory and a Queue in the bean environment. It is important to note that the connection to the JMS provider is being established in the ejbCreate() method call. This avoids doing the JNDI look up call every time a message is processed.

public void ejbCreate()
{
   try
   {
      InitialContext ictx = new InitialContext();
      output = (Queue)ictx.lookup("Java:comp/env/jms/output");
       QueueConnectionFactory factory =
      (QueueConnectionFactory)ictx.lookup(
      "Java:comp/env/jms/connectionfactory");
       connection = factory.createQueueConnection();
   }
   catch (Exception e)
   {
      e.printStackTrace();
      throw new EJBException(e.toString());
   }
}

Since the connection is established in the ejbCreate() method call, the connection should conversely be closed in the ejbRemove() method call.

public void ejbRemove()
{
   try
   {
      connection.close();
   }
   catch (Throwable e)
   {
   }
   ctx = null;
}

Sending a Message Using a JCA Connection

Once relay RelayMDB has been initialized, it is ready to process messages.

The main part of the onMessage(Message) method is straightforward. A new session should be established as usual. The transaction arguments to the session will be ignored since the JCA session will use the J2EE container to manage transaction. The sender object is created and is then used to send the message that was just received to the output queue.

public void onMessage(Message msg)
{
    ...
    try 
    {
       session = connection.createQueueSession(true,
       Session.AUTO_ACKNOWLEDGE);
       sender = session.createSender(output);
       System.out.println("Relaying message " + msg);
       sender.send(msg);

The error handing should be explained in a little more detail. If a JMSException occurs, a message is displayed and then current transaction is marked for rollback. This would rollback the message that was sent using the JCA adapter and potentially (depending on the deployment descriptor) the transaction that delivered the message to RelayMDB.

   catch (JMSException ex)
   {
      System.out.println("Rolling back message due to error.");
      ctx.setRollbackOnly();
      throw new EJBException(ex.toString());
   }

The finally block ensures that the session is closed properly. It is important that the session always be closed after it is used. The session object is pooled by the JCA Resource Adapter so your EJB should not try to cache it. Creating new session objects is a relatively cheap operation for the JCA Resource Adapter.

   finally
   {
      try
      {
         session.close();
      }
      catch (Throwable e)
      {
   }
}

Listing 4.4 The source code for a MDB that relays messages to another queue.

import javax.ejb.*;
import javax.jms.*;
import javax.naming.*;

/**
 * A MDB which relays the received message to another Queue
 */
public class RelayMDB implements MessageDrivenBean, MessageListener
{
   private MessageDrivenContext ctx = null;

   public void setMessageDrivenContext(MessageDrivenContext ctx) throws EJBException
   {
      this.ctx = ctx;
   }
   
   Queue output;
   QueueConnection connection;
   
   public void ejbCreate()
   {
      try
      {
         InitialContext ictx = new InitialContext();
         output = (Queue)ictx.lookup("Java:comp/env/jms/output");
         QueueConnectionFactory factory =
         (QueueConnectionFactory)ictx.lookup("Java:comp/env/jms/connectionfactory");
         connection = factory.createQueueConnection();
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new EJBException(e.toString());
      }
   }
   
   public void ejbRemove()
   {
      try
      {
         connection.close();
      }
      catch (Throwable e)
      {
      }
      ctx = null;
   }
   
   public void onMessage(Message msg)
   {
      QueueSession session = null;
      QueueSender sender = null;
      try
      {
      
         session = connection.createQueueSession(true,
         Session.AUTO_ACKNOWLEDGE);
         sender = session.createSender(output);
         System.out.println("Relaying message " + msg);
         sender.send(msg);
      }
      catch (JMSException ex)
      {
         System.out.println("Rolling back message due to error.");
         ctx.setRollbackOnly();
         throw new EJBException(ex.toString());
      }
      finally
      {
         try
         {
            session.close();
         }
         catch (Throwable e)
         {
         }
      }
   }
}

Deployment Descriptor

The RelayMDB like all EJBs requires a deployment descriptor. It is very similar to the deployment descriptor in Listing 4.2. The main difference to note are the <resoruce-ref> XML element configurations. They specify that the RelayMDB will have resources bound the jms/output and jms/connectionfactory bean environment locations (located under the Java:comp/env JNDI sub context).

Listing 4.5 The /META-INF/ejb-jar.xml deployment descriptor for the RelayMDB.

<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC 
"-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"

"http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>Relay</ejb-name>
<ejb-class>RelayMDB</ejb-class>
<message-selector></message-selector>
<transaction-type>Container</transaction-type>
 <resource-ref>
<description>JMS ConnectionFactory</description>
<res-ref-name>jms/connectionfactory</res-ref-name>
<res-type>javax.jms.QueueConnectionFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-ref>
<description>The output queue</description>
<res-ref-name>jms/output</res-ref-name>
<res-type>javax.jms.Queue</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
</message-driven> 
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>Relay</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>

Creating the JBoss specific deployment descriptor is similar to the TextMDB deployment descriptor used to deploy the shown in Listing 4.3. Once of the biggest differences is that it has a <resource-managers> XML section. That XML section will be used to define all the resources that will be used by the beans in the deployment unit. In our case, we have a definition for JMS connection factory and one for the output queue.

Later in in the deployment descriptor, the <resource-ref> XML section is used to associate the JNDI location defined in the ejb-jar.xml with a resource defined in the <resource-managers> XML section.

Listing 4.6 The /META-INF/jboss.xml deployment descriptor for the RelayMDB.

<?xml version="1.0" encoding="utf-8"?>
<JBoss>
<secure>false</secure>
<resource-managers>
<resource-manager>
<res-name>JmsXA</res-name>
<res-jndi-name>Java:/JmsXA</res-jndi-name>
</resource-manager>
<resource-manager>
<res-name>dlq</res-name>
<res-jndi-name>queue/DLQ</res-jndi-name>
</resource-manager>
</resource-managers>
<enterprise-beans>
<message-driven>
<ejb-name>Relay</ejb-name>
<configuration-name>Standard Message Driven
Bean</configuration-name>
<resource-ref>
<res-ref-name>jms/connectionfactory</res-ref-name>
<resource-name>JmsXA</resource-name>
</resource-ref>
<resource-ref>
<res-ref-name>jms/output</res-ref-name>
<resource-name>dlq</resource-name>
</resource-ref> 
<destination-jndi-name>queue/testQueue</destination-jndi-name>
</message-driven>
</enterprise-beans>
</JBoss>