Section 7.12. Message-Driven Beans (MDBs)


7.12. Message-Driven Beans (MDBs)

It's taken a while to get here, but we now have the core infrastructure in place to send a JMS message. To consume JMS messages with an MDB, we'll do the following:

  • Write the MDB.

  • Deploy the MDB.

  • Automate MDB deployment with XDoclet.

A Message-Driven Bean (MDB) is an EJB whose sole purpose is to consume JMS messages. When a JMS producer sends a message to a JMS destination, the MDB listening on that destination receives the message and processes it. MDBs are pooled, stateless, and do not have a Home or Component interface. An MDB implements the JMS MessageListener interface; its onMessage( ) method processes the message received from the JMS destination and implements business logic. Pooling enables concurrent behavior, so several instances of an MDB would run in parallel if multiple messages are in the queue or topic.

7.12.1. Writing an MDB

In the JAW Motors application, the CreditCheckProcessor MDB:

  • Consumes a JMS ObjectMessage that contains a CreditCheckRequestDTO

  • Invokes an emulated external credit verification service

Example 7-10 is the code for the MDB.

Example 7-10. CreditCheckProcessorBean.java
 package com.jbossatwork.ejb; import javax.ejb.*; import javax.jms.*; import com.jbossatwork.dto.*; import com.jbossatwork.util.*; public class CreditCheckProcessorBean implements MessageDrivenBean, MessageListener {     private MessageDrivenContext ctx = null;     public CreditCheckProcessorBean(  ) {  }     public void setMessageDrivenContext(MessageDrivenContext ctx)     throws EJBException {         this.ctx = ctx;     }     /**      * Required creation method for message-driven beans.      */     public void ejbCreate(  ) {         // no specific action required for message-driven beans     }      /** Required removal method for message-driven beans. */     public void ejbRemove(  ) {         ctx = null;     }     /**      * Implements the business logic for the MDB.      *      * @param message The JMS message to be processed.      */     public void onMessage(Message message) {         System.out.println(             "CreditCheckProcessorBean.onMessage(  ): Received message.");         try {             if (message instanceof ObjectMessage) {                 ObjectMessage objMessage = (ObjectMessage) message;                 Object obj = objMessage.getObject(  );                 if (obj instanceof CreditCheckRequestDTO) {                     String result = null;                     CreditCheckRequestDTO creditCheckReq =                                                    (CreditCheckRequestDTO) obj;                     System.out.println("Credit Check:");                     System.out.println("Name = [" + creditCheckReq.getName(  ) +                                        "]");                     System.out.println("SSN = [" + creditCheckReq.getSsn(  ) + "]");                     System.out.println("Email = [" + creditCheckReq.getEmail(  ) +                                        "]");                     System.out.println("Verifying Credit ...");                     result = CreditVerificationService.verifyCredit(                                                             creditCheckReq);                     System.out.println("Credit Check Result = {" + result + "]");                  } else {                     System.err.println(                                     "Expecting CreditCheckRequestDTO in Message");                 }             } else {                 System.err.println("Expecting Object Message");             }         } catch (Throwable t) {             t.printStackTrace(  );         }     } } 

The onMessage( ) method consumes a JMS message that contains the user's credit information, but we have to pull the CreditCheckRequestDTO out of the message before using it. Compare this process to peeling the layers of an onion. We first cast the Message we received into an ObjectMessage, call its getObject( ) method to get the Object out, and then cast it into a CreditCheckRequestDTO. Now that we have the original data entered by the user, we pass the CreditCheckRequestDTO to our emulated external credit verification service. The CreditVerificationService.verifyCredit( ) simulates a long-running process and returns a String value that tells whether the credit check passed or failed. The code for verifyCredit( ) isn't that interesting, so we're not showing it here in the book. If you're dying of curiosity, you can find the CreditVerificationService class in the JAW Motors application common sub-project.

You may have noticed that in the onMessage( ) method we're catching and logging all exceptions rather than re-throwing them. Exceptions thrown from an MDB have no real calling code to catch them, so they roll all the way back to the application server. Then, the JMS provider re-delivers the message to the MDB and the cycle repeats itself. To avoid this "poison message" problem, your code needs to catch any exceptions so the JMS server considers the message delivered/consumed and will not attempt to re-deliver the message. In addition to logging exceptions, you'll probably want to save them in some sort of "dead letter" queue and send an email or pager message so that system personnel can look into the problem.

Now that we've written an MDB, let's take a more detailed look at how Message-Driven Beans relate to J2EE transactions .

7.12.2. MDB Transaction Settings

Like the other EJB types, a Message-Driven Bean has two ways to manage transactions: Container-Managed Transactions (CMT) and Bean-Managed Transactions (BMT). BMT requires extra Java coding to handle transaction logic and forces the developer to manage the transaction and define his own transaction boundaries. In contrast, CMT doesn't require you to handle transaction logic, pushes transaction settings into deployment descriptors, uses JBoss transaction services, and manages transaction boundaries on behalf of the developer. We'll focus on CMT because it is considered a J2EE best practice and allows the container to do the work.

A transaction attribute tells the container how to handle CMT transactions for an MDB's onMessage( ) method. Specify transaction attributes for each Bean in the ejb-jar.xml deployment descriptor. Here are the possible transaction settings for an MDB:


Required

The onMessage( ) method always runs within the scope of a transaction. If a transaction doesn't currently exist, the container starts a new transaction.


NotSupported

The onMessage( ) method does not run within the scope of a transaction.

A Message-Driven Bean does not run in the transaction context of the client that sent the JMS message to the queue or topic because an MDB isn't tied to a caller. The JMS message producer doesn't know if the transaction in the Message-Driven Bean's onMessage( ) method committed or rolled back.

There's no need to specify a transaction setting in the CreditCheckProcessor MDB because it doesn't participate in a transaction. The Bean's onMessage( ) method only sends an email message and doesn't do anything transactional (like updating a database).

We've written a Message-Driven Bean and discussed transactions, and now we need to deploy our MDB.

7.12.3. Deploying an MDB

After developing the code for a Message-Driven Bean (MDB), deploy it by adding information about the EJB (meta-data) to the J2EE standard (ejb-jar.xml) and JBoss (jboss.xml) EJB deployment descriptors. The new <message-driven> element describes the MDB by telling the container:

  • That it's a Message-Driven Bean.

  • About the bean class.

  • That it uses container-managed transactions (CMT).

  • That its Acknowledgment mode is Auto-acknowledge.

  • That it listens on a non-durable Queue.

Example 7-11 shows the changes to ejb-jar.xml.

Example 7-11. ejb-jar.xml
 <?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns=http://java.sun.com/xml/ns/j2ee          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee          http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1">   <enterprise-beans>     ...     <message-driven>       <display-name>CreditCheckProcessorMDB</display-name>       <ejb-name>CreditCheckProcessor</ejb-name>       <ejb-class>com.jbossatwork.ejb.CreditCheckProcessorBean</ejb-class>       <messaging-type>javax.jms.MessageListener</messaging-type>       <transaction-type>Container</transaction-type>       <message-destination-type>javax.jms.Queue</message-destination-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>             acknowledgeMode           </activation-config-property-name>           <activation-config-property-value>             Auto-acknowledge           </activation-config-property-value>         </activation-config-property>         <activation-config-property>           <activation-config-property-name>             subscriptionDurability           </activation-config-property-name>           <activation-config-property-value>             NonDurable            </activation-config-property-value>         </activation-config-property>       </activation-config>     </message-driven>     ...   </enterprise-beans>   ... </ejb-jar> 

In the ejb-jar.xml file you can only indicate that your MDB listens on a Queue, but there are no elements that associate the CreditCheckProcessor MDB with the CreditCheckQueue. Since specifying the Queue name isn't part of the EJB specification, we use the jboss.xml descriptor to associate an MDB with a JMS Destination. The <message-driven> element's <destination-jndi-name> sub-element tells JBoss that the CreditCheckProcessor MDB listens on and receives messages from the CreditCheckQueue in Example 7-12.

Example 7-12. jboss.xml
 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN"                        "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd"> <jboss>   <enterprise-beans>     ...     <message-driven>       <ejb-name>CreditCheckProcessor</ejb-name>       <destination-jndi-name>queue/CreditCheckQueue</destination-jndi-name>     </message-driven>     ...   </enterprise-beans>   ... </jboss> 

7.12.4. Automating MDB Deployment with XDoclet

As in previous chapters, we don't want to hardcode your deployment descriptors. We need to add XDoclet tags to the CreditCheckProcessor MDB in Example 7-13 so that the Ant build process adds it to the J2EE standard (ejb-jar.xml) and JBoss-specific (jboss.xml) EJB deployment descriptors.

Example 7-13. CreditCheckProcessorBean.java
 /**  * @ejb.bean name="CreditCheckProcessor"  *  display-name="CreditCheckProcessorMDB"  *  acknowledge-mode="Auto-acknowledge"  *  destination-type="javax.jms.Queue"  *  subscription-durability="NonDurable"  *  transaction-type="Container"  *  * @jboss.destination-jndi-name  *  name="queue/CreditCheckQueue"  *  */ public class CreditCheckProcessorBean implements MessageDrivenBean, MessageListener {     ... } 

The @ejb.bean XDoclet tag generates the CreditCheckProcessor MDB's <message-driven> element and all its subelements in ejb-jar.xml. The @jboss.destination-jndi-name XDoclet tag generates the corresponding <message-driven> element in jboss.xml that indicates that the CreditCheckProcessor MDB listens for messages on the CreditCheckQueue.



JBoss at Work. A Practical Guide
JBoss at Work: A Practical Guide
ISBN: 0596007345
EAN: 2147483647
Year: 2004
Pages: 197

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net