ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Using a JMS Provider with MDBs via the J2EE Connector Architecture Using a JMS Provider with MDBs via the J2EE Connector Architecture

by Debu Panda
06/23/2004

Messaging software packages such as OracleAQ, Websphere MQ, and Tibco have been an integral part of enterprise business applications. If you have been a Java developer for long, then you are probably aware how difficult it was to build messaging applications using J2EE before Message-Driven Beans (MDBs) were introduced as part of J2EE 1.3. The great thing about MDBs are that they make the life of an application developer simpler by delegating the responsibility of providing infrastructure for transactions, security, and concurrently processing messages to the EJB container. However, if you jumped on the MDB bandwagon very early, you might have found a few restrictions, such as:

  1. MDB was only meant for JMS messages.
  2. There was no standard way to integrate messaging providers with J2EE containers, and this led to limited support for messaging providers from each J2EE vendor.

You are not alone if you found that your favorite J2EE container had issues supporting the messaging provider of your choice. The J2EE 1.4 specification addresses these two issues by:

For example, Oracle Corporation has built a generic resource adapter for OracleAS Containers for J2EE (OC4J) to support major JMS providers such as Websphere MQ, Tibco, etc. Also, SwiftMQ has built a resource adapter to integrate its messaging software with J2EE-1.4-compliant application servers such as OC4J.

In this article I will provide a brief introduction to MDB and the J2EE Connector Architecture (JCA), examining how MDBs can be deployed with the JCA 1.5 resource adapter to use a JMS provider. I will also provide some best practices for using MDBs with resource adapters.

Introduction to Message-Driven Beans

An MDB is essentially a message consumer that can listen to a message destination or a message endpoint and gets activated when a message arrives. By design, MDBs are anonymous in nature and hence cannot be directly invoked by a client. The only way to invoke an MDB is to send a message to the destination or endpoint to which it is listening. As MDBs are stateless in nature and are not related to any specific client, they can be pooled for concurrent processing of messages. Figure 1 depicts a simple diagram of how an MDB works.

Figure 1
Figure 1. MDB relationships in OC4J

You should consider using MDBs if your business application requires any of the following:

An MDB does not have interfaces like other types of EJBs; it only has a bean-implementation class. The MDB bean class has to implement the javax.ejb.MessageDrivenBean interface. It must also implement the message listener interface required by the messaging type that it supports. For example, if the MDB supports JMS, then it must implement the javax.jms.MessageListener interface. If the MDB supports the Java API for XML Messaging (JAXM), it should implement either the javax.xml.messaging.OnewayListener or javax.xml.messaging.ReqRespListener interfaces.

Here is a code example for a very simple MDB that supports JMS messages. It is clear from the code that the MDB is a concrete class and MDB has a method named onMessage() that needs to be implemented by the bean developer. The onMessage() method gets executed when a message arrives at the destination or endpoint where the MDB is listening. The MDB lifecycle methods ejbCreate() and ejbRemove() are executed when an MDB instance is created or removed.

import javax.ejb.*;
import javax.jms.*;
import javax.naming.*; 
public class simpleMdb
  implements MessageDrivenBean, MessageListener
  {
  private XAQueueConnectionFactory m_xaqcf = null;
  private Queue m_replyq = null;
  private QueueConnection m_qc = null;
  private QueueSession m_qs = null;
  private QueueSender m_snd = null;
  private MessageDrivenContext m_ctx = null;

  public simpleMdb()
  {
  }
 public void setMessageDrivenContext(MessageDrivenContext ctx)
  {
  m_ctx = ctx;
  }
 public void ejbCreate()
  {
  try
  {
  Context ctx = new InitialContext();
  m_xaqcf = (XAQueueConnectionFactory)
  ctx.lookup("java:comp/env/jms/XAQCF");
  m_replyq = (Queue) ctx.lookup("java:comp/env/jms/QUEUE1");
  ctx.close();
  }
  catch (Throwable ex)
  {
   ex.printStackTrace();
  }
  }
 public void ejbRemove()
  {
  try
  {
  if (m_qc != null)
  m_qc.close();
  }
  catch (Throwable ex)
  {
   ex.printStackTrace();
  }
  }
 public void onMessage(Message msg)
  {
  try
  {
   String txt = ("MDB rcv: " + msg.getJMSMessageID());
   System.out.println(txt + " redel="
    + msg.getJMSRedelivered() + " cnt="
    + msg.getIntProperty("JMSXDeliveryCount"));

   /* Create connection, session and sender */
   QueueConnection qcon = m_xaqcf.createQueueConnection();
   QueueSession qsess = qcon.createQueueSession
    (false, Session.AUTO_ACKNOWLEDGE);
   QueueSender snd = qsess.createSender(m_replyq);
   boolean ignore = msg.getBooleanProperty ("ignoreMsg");
   if (ignore)
   {
     System.out.println(txt + " noreply");
     return;
   }
   Message rmsg = qsess.createMessage();
   rmsg.setStringProperty("RECIPIENT", "CLIENT");
   rmsg.setIntProperty("count",
   msg.getIntProperty("JMSXDeliveryCount"));
   rmsg.setJMSCorrelationID(msg.getJMSMessageID());
   snd.send(rmsg);
   if (msg.getBooleanProperty("doRollback")
    && (!msg.getJMSRedelivered()))
   {
    System.out.println(txt + " rollback: " +
     rmsg.getJMSMessageID());
    m_ctx.setRollbackOnly();
   }
  else
  {
   System.out.println(txt + " snd: " +
     rmsg.getJMSMessageID());
  }
  qcon.close();
  }
  catch (Throwable ex)
  {
  ex.printStackTrace();
  }
 }
}

With an MDB, you need to identify the destination types and configure the JMS providers to create the message destination (such as a Queue or Topic) for the MDBs. You can deploy the MDB to use the JMS provider via the proprietary interface provided by your J2EE container or use a JCA resource adapter.

Now let us turn our attention to the J2EE Connector Architecture before jumping into the details of how MDBs can be deployed using the JMS providers and JCA resource adapter.

J2EE Connector Architecture

The J2EE Connector Architecture allows for the integration of application servers with Enterprise Information Systems (EIS) and legacy systems in a standardized manner. Just as JDBC allows connectivity between J2EE applications and databases, JCA allows connectivity between J2EE applications and legacy data in EIS in a standardized manner. A JCA resource adapter (RA) is built to connect and manipulate a particular EIS, just as a JDBC driver provides the ability to connect to a database. For example, Oracle Corporation has built a generic RA for OC4J that integrates many JMS providers such as Websphere MQ, Tibco, and OracleAS JMS. For more information about the JCA, please see the ONJava article "J2EE Connector Architecture," by Anthony Lai, Jyostna Laxminarayan and Lars Ewe.

JCA introduces two new contracts that allow pluggability of JMS providers with J2EE containers in a standardized fashion.

The Message Inflow contract between a J2EE container and a resource adapter allows delivery of asynchronous messages from the EIS to message endpoints located in the application's server, through the resource adapter interface. A message endpoint is essentially an MDB deployed in a J2EE container to listen via the JCA, and is available after the deployment of an application.

The Transaction Inflow contract between a J2EE container and a resource adapter allows the resource adapter to propagate transactions initiated by the EIS to the application server. This also provides the ability for transaction recovery whenever required, thus allowing MDBs, and other components via MDBs, to participate in distributed transactions that may involve a messaging provider used with the JCA RA.

JMS via Resource Adapter

The use of JMS providers is very common in J2EE applications. In this section, I will discuss the use of JMS providers via the resource adapter. Using MDBs with the resource adapter interface or a proprietary message provider interface does not require any code changes in the MDB. If you are planning to migrate your existing MDB applications to use a JMS provider via the resource adapter, you have to make some changes in the deployment descriptor (e.g., ejb-jar.xml). Figure 2 depicts an overview diagram of how an MDB works with a JMS provider with a resource adapter.

Figure 2
Figure 2. MDB using JMS provider with a JCA resource adapter

The following steps are required to configure an MDB to use a JMS provider with a resource adapter:

I will use the simpleMdb example, presented above, to illustrate these steps.

MDB Deployment Descriptor

An MDB that is configured to listen on a message endpoint of a resource adapter requires the activation-config properties to be set in the deployment descriptor. The activation-config properties are a set of name-value pairs that are passed to the resource adapter when the MDB is deployed. The properties are defined according to the messaging provider used. However, the EJB 2.1 specification has defined the following standard properties:

If we decide to use the simpleMdb using Websphere MQ with a JCA-RA, then the deployment descriptor (ejb-jar.xml ) of the MDB will be as follows:

<messaging-type>javax.jms.MessageListener</messaging-type>
<transaction-type>Container</transaction-type>
<activation-config>
    <activation-config-property>
        <activation-config-property-name>
            DestinationType
        </activation-config-property-name>
        <activation-config-property-value>
            javax.jms.Queue
        </activation-config-property-value>
    </activation-config-property> 
    
    <activation-config-property>
        <activation-config-property-name>
            DestinationName
        </activation-config-property-name>
        <activation-config-property-value>
            java:comp/resource/MQSeries/MQQ
        </activation-config-property-value>
    </activation-config-property> 
    
    <activation-config-property>
        <activation-config-property-name>
            MessageSelector
        </activation-config-property-name>
        <activation-config-property-value>
            RECIPIENT='MDB'
        </activation-config-property-value>
    </activation-config-property> 
    
    <activation-config-property>
        <activation-config-property-name>
            ConnectionFactoryJndiName
        </activation-config-property-name>
        <activation-config-property-value>
            java:comp/resource/MQSeries/MQXAQCF
        </activation-config-property-value>
    </activation-config-property>
</activation-config>

If you look carefully at the deployment descriptor, you will find that the destination type and name on which the MDB will listen has already been specified as activation-config property and eliminates the basic need for the vendor deployment descriptor. However, the activation config properties can be overridden by the deployer in the vendor-specific deployment descriptor.

In the vendor deployment descriptor for the MDB, you have to specify the name of the resource adapter being used. In addition, you can specify additional config properties that were not specified in the ejb-jar.xml file. For example, if you are using OC4J, the vendor-specific deployment descriptor (e.g., orion-ejb-jar.xml) has the following contents:

<message-driven-deployment name="simpleMdb" 
     resource-adapter="mqjms">
    ...
</message-driven-deployment>

Configuring the Application Server

You may have to do some configuration tasks in your application server if you decide to use a third-party JMS provider. For example, if you decide to use Websphere MQ with OC4J, several Websphere-MQ-specific libraries need to be available to OC4J.

Deploy the Resource Adapter

For MDBs to work with a JMS provider via a JCA resource adapter, the resource adapter needs to be deployed in the application server. You can deploy your resource adapter in two ways: standalone or embedded. A standalone or global resource adapter in your application server can be used by any applications deployed in the application server. An embedded resource adapter, packaged within an enterprise application archive (EAR), is available only to the J2EE application with which it is packaged.

The deployment descriptor (ra.xml) and vendor-specific deployment descriptors (e.g., oc4j-ra.xml in OC4J) of the resource adapter for the JMS provider define the properties of the resource provider and the resource. However, the details of these are beyond the scope of this article.

Configure the JMS Provider

You have to create the necessary JMS objects in the JMS provider, such as a queue or topic that will be used by the MDB and other related tasks for your JMS provider.

For example, if you are using Websphere MQ with OC4J, you have to do the following:

A fully functional example of MDBs using JCA RA can be downloaded from the Oracle Technology Network. This example uses an embedded resource adapter and demonstrates the use of MDBs with Websphere MQ as the JMS provider.

Best Practices for MDBs

The following approaches and suggestions should be used during the development and deployment of MDBs:

Conclusion

This article provided a brief discussion on the enhancements made in J2EE 1.4 to provide pluggability of messaging providers and discussed the steps required to use a JMS provider with a resource adapter. This functionality is already available in several J2EE-1.4-compatible application servers such as OC4J 10.0.3 and IBM Websphere 6.0. You can start building your MDB using this feature to make your application easily portable.

References and Further Reading

Debu Panda is a Senior Principal Product Manager of the Oracle Application Server development team.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.