JMS-Based Message-Driven Beans

Message-driven beans (MDBs) are stateless, server-side, transaction-aware components for processing asynchronous messages delivered via the Java Message Service. While a message-driven bean is responsible for processing messages, its container manages the component’s environment, including transactions, security, resources, concurrency, and message acknowledgment. It’s particularly important to note that the container manages concurrency. The thread-safety provided by the container gives MDBs a significant advantage over traditional JMS clients, which must be custom-built to manage resources, transactions, and security in a multithreaded environment. An MDB can process hundreds of JMS messages concurrently because numerous instances of the MDB can execute concurrently in the container.

A message-driven bean is a complete enterprise bean, just like a session or entity bean, but there are some important differences. While a message-driven bean has a bean class and EJB deployment descriptor, it does not have EJB object or home interfaces. These interfaces are absent because the message-driven bean is not accessible via the Java RMI API; it responds only to asynchronous messages.

The ReservationProcessor EJB

The ReservationProcessor EJB is a message-driven bean that receives JMS messages notifying it of new reservations. The ReservationProcessor EJB is an automated version of the TravelAgent EJB that processes reservations sent via JMS. These messages might come from another application in the enterprise or from an application in some other organization—perhaps another travel agent. When the ReservationProcessor EJB receives a message, it creates a new Reservation EJB (adding it to the database), processes the payment using the ProcessPayment EJB, and sends out a ticket. This process is illustrated in Figure 12-3.

The ReservationProcessor EJB processing reservations

Figure 12-3. The ReservationProcessor EJB processing reservations

The ReservationProcessorBean Class

Here is a partial definition of the ReservationProcessorBean class. Some methods are left empty; they will be filled in later. Notice that the onMessage( ) method contains the business logic; it is similar to the business logic developed in the bookPassage( ) method of the TravelAgent EJB in Chapter 11. Here’s the code:

package com.titan.reservationprocessor;

import javax.jms.Message;
import javax.jms.MapMessage;
import com.titan.customer.*;
import com.titan.cruise.*;
import com.titan.cabin.*;
import com.titan.reservation.*;
import com.titan.processpayment.*;
import com.titan.travelagent.*;
import java.util.Date;
    
public class ReservationProcessorBean implements javax.ejb.MessageDrivenBean, 
    javax.jms.MessageListener {

    MessageDrivenContext ejbContext;
    Context jndiContext;

    public void setMessageDrivenContext(MessageDrivenContext mdc) {
        ejbContext = mdc;
        try {
            jndiContext = new InitialContext( );
        } catch(NamingException ne) {
            throw new EJBException(ne);
        }
    }

    public void ejbCreate( ) {}

    public void onMessage(Message message) {
        try {
            MapMessage reservationMsg = (MapMessage)message;
                
            Integer customerPk = (Integer)reservationMsg.getObject("CustomerID");
            Integer cruisePk = (Integer)reservationMsg.getObject("CruiseID");
            Integer cabinPk = (Integer)reservationMsg.getObject("CabinID");

            double price = reservationMsg.getDouble("Price");

            // get the credit card
            Date expirationDate = 
                new Date(reservationMsg.getLong("CreditCardExpDate"));
            String cardNumber = reservationMsg.getString("CreditCardNum");
            String cardType = reservationMsg.getString("CreditCardType");
            CreditCardDO card = new CreditCardDO(cardNumber, 
                expirationDate, cardType);
            
            CustomerRemote customer = getCustomer(customerPk);
            CruiseLocal cruise = getCruise(cruisePk);
            CabinLocal cabin = getCabin(cabinPk);

            ReservationHomeLocal resHome = (ReservationHomeLocal)
                jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); 
            ReservationLocal reservation =
                resHome.create(customer, cruise, cabin, price, new Date( ));
            Object ref = jndiContext.lookup
                ("java:comp/env/ejb/ProcessPaymentHomeRemote");
            ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
                PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class);
            ProcessPaymentRemote process = ppHome.create( );
            process.byCredit(customer, card, price);

            TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
            
            deliverTicket(reservationMsg, ticket);

        } catch(Exception e) {
            throw new EJBException(e);
        }
    }
    
    public void deliverTicket(MapMessage reservationMsg, TicketDO ticket) {
        
        // send it to the proper destination
    }
    public CustomerRemote getCustomer(Integer key)
        throws NamingException, RemoteException, FinderException {
        // get a remote reference to the Customer EJB
    }
    public CruiseLocal getCruise(Integer key)
        throws NamingException, FinderException {
        // get a local reference to the Cruise EJB
    }
    public CabinLocal getCabin(Integer key)
        throws NamingException, FinderException {
        // get a local reference to the Cabin EJB
    }

    public void ejbRemove( ) {
        try {
            jndiContext.close( );
            ejbContext = null;
        } catch(NamingException ne) { /* do nothing */ }
    }
}

MessageDrivenBean interface

The message-driven bean class is required to implement the javax.ejb.MessageDrivenBean interface, which defines callback methods similar to those in entity and session beans. Here is the definition of the MessageDrivenBean interface:

package javax.ejb;

public interface MessageDrivenBean extends javax.ejb.EnterpriseBean {
    public void setMessageDrivenContext(MessageDrivenContext context)
        throws EJBException;
    public void ejbRemove( ) throws EJBException;
}

The setMessageDrivenContext( ) method is called at the beginning of the MDB’s life cycle and provides the MDB instance with a reference to its MessageDrivenContext:

MessageDrivenContext ejbContext;
Context jndiContext;

public void setMessageDrivenContext(MessageDrivenContext mdc) {
    ejbContext = mdc;
    try {
        jndiContext = new InitialContext( );
    } catch(NamingException ne) {
        throw new EJBException(ne);
    }
}

The setMessageDrivenContext( ) method in the ReservationProcessorBean class sets the ejbContext instance field to the MessageDrivenContext, which was passed into the method. It also obtains a reference to the JNDI ENC, which it stores in the jndiContext. MDBs may have instance fields that are similar to a stateless session bean’s instance fields. These instance fields are carried with the MDB instance for its lifetime and may be reused every time it processes a new message. Like stateless session beans, MDBs do not have conversational state and are not specific to a single JMS client; MDB instances process messages from many different clients. Instead, they are tied to the specific topic or queue from which they receive messages.

ejbRemove( ) provides the MDB instance with an opportunity to clean up any resources it stores in its instance fields. In this case, we use it to close the JNDI context and set the ejbContext field to null. These operations are not absolutely necessary, but they illustrate the kind of work that an ejbRemove( ) method might do. Note that ejbRemove( ) is called at the end of the MDB’s life cycle, before it is garbage collected. It may not be called if the EJB server hosting the MDB fails or if an EJBException is thrown by the MDB instance in one of its other methods. When an EJBException (or any RuntimeException type) is thrown by any method in the MDB instance, the instance is immediately removed from memory and the transaction is rolled back.

MessageDrivenContext

The MessageDrivenContext simply extends the EJBContext; it does not add any new methods. The EJBContext is defined as:

package javax.ejb;
public interface EJBContext {
   
    // transaction methods
    public javax.transaction.UserTransaction getUserTransaction( ) 
        throws java.lang.IllegalStateException;
    public boolean getRollbackOnly( ) throws java.lang.IllegalStateException;
    public void setRollbackOnly( ) throws java.lang.IllegalStateException;

    // EJB home methods
    public EJBHome getEJBHome( );
    public EJBLocalHome getEJBLocalHome( );
   
    // security methods
    public java.security.Principal getCallerPrincipal( );
    public boolean isCallerInRole(java.lang.String roleName);

    // deprecated methods
    public java.security.Identity getCallerIdentity( );
    public boolean isCallerInRole(java.security.Identity role); 
    public java.util.Properties getEnvironment( );

}

Only the transactional methods that MessageDrivenContext inherits from EJBContext are available to message-driven beans. The home methods—getEJBHome( ) and getEJBLocalHome( ) —throw a RuntimeException if invoked, because MDBs do not have home interfaces or EJB home objects. The security methods—getCallerPrincipal( ) and isCallerInRole( ) —also throw a RuntimeException if invoked on a MessageDrivenContext. When an MDB services a JMS message, there is no “caller,” so there is no security context to be obtained from the caller. Remember that JMS is asynchronous and doesn’t propagate the sender’s security context to the receiver—that wouldn’t make sense, since senders and receivers tend to operate in different environments.

MDBs usually execute in a container-initiated or bean-initiated transaction, so the transaction methods allow the MDB to manage its context. The transaction context is not propagated from the JMS sender; it is either initiated by the container or by the bean explicitly using javax.jta.UserTransaction. The transaction methods in the EJBContext are explained in more detail in Chapter 15.

Message-driven beans also have access to their own JNDI environment naming contexts (ENCs), which provide the MDB instances access to environment entries, other enterprise beans, and resources. For example, the ReservationProcessor EJB takes advantage of the JNDI ENC to obtain references to the Customer, Cruise, Cabin, Reservation, and ProcessPayment EJBs as well as a JMS QueueConnectionFactory and Queue for sending out tickets.

MessageListener interface

In addition to the MessageDrivenBean interface, MDBs implement the javax.jms.MessageListener interface, which defines the onMessage( ) method. This method processes the JMS messages received by a bean.

package javax.jms;
public interface MessageListener {
    public void onMessage(Message message);
}

It’s interesting to consider why the MDB implements the MessageListener interface separately from the MessageDrivenBean interface. Why not just put the onMessage( ) method, MessageListener’s only method, in the MessageDrivenBean interface so that there is only one interface for the MDB class to implement? This was the solution taken by an early, proposed version of EJB 2.0. However, the developers quickly realized that message-driven beans could, in the future, process messages from other types of systems, not just JMS. To make the MDB open to other messaging systems, it was decided that the MDB should implement the javax.jms.MessageListener interface separately, thus separating the concept of the message-driven bean from the types of messages it can process. It turns out that this was a good plan. As we’ll see later in this chapter, EJB 2.1 lets you use MDBs with non-JMS messaging systems that use a different messaging interface.

Taskflow and integration for B2B (onMessage( ) )

The onMessage( ) method is where all the business logic goes. As messages arrive, the container passes them to the MDB via the onMessage( ) method. When the method returns, the MDB is ready to process a new message. In the ReservationProcessor EJB, the onMessage( ) method extracts information about a reservation from a MapMessage and uses that information to create a reservation in the system:

public void onMessage(Message message) {
    try {
        MapMessage reservationMsg = (MapMessage)message;
                
        Integer customerPk = (Integer)reservationMsg.getObject("CustomerID");
        Integer cruisePk = (Integer)reservationMsg.getObject("CruiseID");
        Integer cabinPk = (Integer)reservationMsg.getObject("CabinID");

        double price = reservationMsg.getDouble("Price");

        // get the credit card

        Date expirationDate = 
            new Date(reservationMsg.getLong("CreditCardExpDate"));
        String cardNumber = reservationMsg.getString("CreditCardNum");
        String cardType = reservationMsg.setString("CreditCardType");
        CreditCardDO card = new CreditCardDO(cardNumber, 
            expirationDate, cardType);

JMS is frequently used as an integration point for business-to-business applications, so it’s easy to imagine the reservation message coming from one of Titan’s business partners (perhaps a third-party processor or branch travel agency).

The ReservationProcessor EJB needs to access the Customer, Cruise, and Cabin EJBs in order to process the reservation. The MapMessage contains the primary keys for these entities; the ReservationProcessor EJB uses helper methods (getCustomer( ), getCruise( ), and getCabin( )) to look up the entity beans and obtain EJB object references to them:

public void onMessage(Message message) {
    ...
    CustomerRemote customer = getCustomer(customerPk);
    CruiseLocal cruise = getCruise(cruisePk);
    CabinLocal cabin = getCabin(cabinPk);
    ...
}

public CustomerRemote getCustomer(Integer key)
    throws NamingException, RemoteException, FinderException {
    
    Object ref = jndiContext.lookup("java:comp/env/ejb/CustomerHomeRemote");
    CustomerHomeRemote home = (CustomerHomeRemote)
        PortableRemoteObject.narrow(ref, CustomerHomeRemote.class);
    CustomerRemote customer = home.findByPrimaryKey(key);
    return customer;
}
public CruiseLocal getCruise(Integer key)
    throws NamingException, FinderException {
    
    CruiseHomeLocal home = (CruiseHomeLocal)
        jndiContext.lookup("java:comp/env/ejb/CruiseHomeLocal");
    CruiseLocal cruise = home.findByPrimaryKey(key);
    return cruise;
}
public CabinLocal getCabin(Integer key)
    throws NamingException, FinderException{
    
    CabinHomeLocal home = (CabinHomeLocal)
        jndiContext.lookup("java:comp/env/ejb/CabinHomeLocal");
    CabinLocal cabin = home.findByPrimaryKey(key);
    return cabin;
}

Once the information is extracted from the MapMessage, it is used to create a reservation and process the payment. This is basically the same taskflow that was used by the TravelAgent EJB in Chapter 11. A Reservation EJB is created that represents the reservation itself, and a ProcessPayment EJB is created to process the credit card payment:

ReservationHomeLocal resHome = (ReservationHomeLocal)
    jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); 
ReservationLocal reservation =
    resHome.create(customer, cruise, cabin, price, new Date( ));
                    
Object ref = jndiContext.lookup("java:comp/env/ejb/ProcessPaymentHomeRemote");
ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
    PortableRemoteObject.narrow (ref, ProcessPaymentHomeRemote.class);
ProcessPaymentRemote process = ppHome.create( );
process.byCredit(customer, card, price);
TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
deliverTicket(reservationMsg, ticket);

Like a session bean, the MDB can access any other entity or session bean and use that bean to complete a task. An MDB can manage a process and interact with other beans as well as resources. For example, it is commonplace for an MDB to use JDBC to access a database based on the contents of the message it is processing.

Sending messages from a message-driven bean

An MDB can also send messages using JMS. The deliverTicket( ) method sends the ticket information to a destination defined by the sending JMS client:

public void deliverTicket(MapMessage reservationMsg, TicketDO ticket)
    throws NamingException, JMSException{
    
    Queue queue = (Queue)reservationMsg.getJMSReplyTo( );
    QueueConnectionFactory factory = (QueueConnectionFactory)
        jndiContext.lookup("java:comp/env/jms/QueueFactory");
    QueueConnection connect = factory.createQueueConnection( );
    QueueSession session = connect.createQueueSession(true,0);
    QueueSender sender = session.createSender(queue);
    ObjectMessage message = session.createObjectMessage( );
    message.setObject(ticket);
    sender.send(message);
        
    connect.close( );
}

Every message type has two parts: a message header and a message body (a.k.a. the payload). The message header contains routing information and may also have properties for message filtering and other attributes. One of these attributes may be JMSReplyTo. The message’s sender may set the JMSReplyTo attribute to any destination accessible to its JMS provider.[39] In the case of the reservation message, the sender set the JMSReplyTo attribute to the queue to which the resulting ticket should be sent. Another application can access this queue to read tickets and distribute them to customers or store the information in the sender’s database.

You can also use the JMSReplyTo address to report business errors. For example, if the Cabin is already reserved, the ReservationProcessor EJB might send an error message to the JMSReplyTo queue explaining that the reservation could not be processed. Including this type of error handling is left as an exercise for the reader.

XML Deployment Descriptor

MDBs are described in EJB deployment descriptors the same as entity and session beans. They can be deployed alone, but it’s more often deployed with the other enterprise beans that it references. For example, the ReservationProcessor EJB uses the local interfaces of the Customer, Cruise, and Cabin beans, so all four beans would have to be deployed in the same JAR.

EJB 2.1: Deployment descriptor for MDBs

The way EJB 2.1 defines the properties of message processing for MDB is significantly different than in EJB 2.0. EJB 2.0 defined a few JMS-specific elements, which have been abandoned in EJB 2.1 so that the MDB deployment descriptor can represent Connector-based MDBs as well as JMS-based MDBs. Since Connector-based MDBs don’t necessarily use JMS as the message service, the <activation-config> element was introduced to describe the bean’s messaging properties. The <activation-config> elements are shown in bold in the following listing.

<enterprise-beans>
    ...
    <message-driven>
        <ejb-name>ReservationProcessorEJB</ejb-name>
        <ejb-class>
            com.titan.reservationprocessor.ReservationProcessorBean
        </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-property>
                     <activation-config-property-name>destinationType
                     </activation-config-property-name>
                     <activation-config-property-value>javax.jms.Queue
                     </activation-config-property-value> 
                     <activation-property> 
                     <activation-property>
                     <activation-config-property-name>messageSelector
                     </activation-config-property-name>
                     <activation-config-property-value>MessageFormat = 'Version 3.4'
               </activation-config-property-value> 
                     <activation-property> 
                     <activation-property>
                     <activation-config-property-name>acknowledgeMode
                     </activation-config-property-name>
                     <activation-config-property-value>Auto-acknowledge
                     </activation-config-property-value> 
                     <activation-property>        
                     </activation-config>                                  
        <ejb-ref>
            <ejb-ref-name>ejb/ProcessPaymentHomeRemote</ejb-ref-name>
            <ejb-ref-type>Session</ejb-ref-type>
            <home>com.titan.processpayment.ProcessPaymentHomeRemote</home>
            <remote>com.titan.processpayment.ProcessPaymentRemote</remote>
        </ejb-ref>
        <ejb-ref>
            <ejb-ref-name>ejb/CustomerHomeRemote</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <home>com.titan.customer.CustomerHomeRemote</home>
            <remote>com.titan.customer.CustomerRemote</remote>
        </ejb-ref>
        <ejb-local-ref>
            <ejb-ref-name>ejb/CruiseHomeLocal</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <local-home>com.titan.cruise.CruiseHomeLocal</local-home>
            <local>com.titan.cruise.CruiseLocal</local>
        </ejb-local-ref>
        <ejb-local-ref>
            <ejb-ref-name>ejb/CabinHomeLocal</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <local-home>com.titan.cabin.CabinHomeLocal</local-home>
            <local>com.titan.cabin.CabinLocal</local>
        </ejb-local-ref>
        <ejb-local-ref>
            <ejb-ref-name>ejb/ReservationHomeLocal</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <local-home>com.titan.reservation.ReservationHomeLocal</local-home>
            <local>com.titan.reservation.ReservationLocal</local>
        </ejb-local-ref>
        <security-identity>
            <run-as>
                <role-name>everyone</role-name>
            </run-as>
        </security-identity>
        <resource-ref>
            <res-ref-name>jms/QueueFactory</res-ref-name>
            <res-type>javax.jms.QueueConnectionFactory</res-type>
            <res-auth>Container</res-auth>
        </resource-ref>
    </message-driven>
    ...
</enterprise-beans>

The property names and values used in the <activation-config> to describe the messaging service vary depending on the type of message service used, but EJB 2.1 defines a set of fixed properties for JMS-based message-driven beans. These properties are acknowledgeMode, messageSelector, destinationType, and subscriptionDurablity. These properties are also used by EJB 2.0 deployment descriptors, so we’ll discuss them in the next section.

In addition to the <activation-config> element, EJB 2.1 introduces the <messaging-type> and <message-destination-type> elements. An MDB is declared in a <message-driven> element within the <enterprise-beans> element, alongside <session> and <entity> beans. Similar to <session> bean types, it defines an <ejb-name>, <ejb-class>, and <transaction-type>, but does not define component interfaces (local or remote). MDBs do not have remote or local interfaces, so these definitions aren’t needed.

EJB 2.0: Deployment descriptor for MDBs

Here is the deployment descriptor for MDBs in EJB 2.0:

<enterprise-beans>
    ...
    <message-driven>
        <ejb-name>ReservationProcessorEJB</ejb-name>
        <ejb-class>
            com.titan.reservationprocessor.ReservationProcessorBean
        </ejb-class>
        <transaction-type>Container</transaction-type>
        <message-selector>MessageFormat = 'Version 3.4'</message-selector>
        <acknowledge-mode>Auto-acknowledge</acknowledge-mode>       
        <message-driven-destination>
                     <destination-type>javax.jms.Queue</destination-type>
                     </message-driven-destination>      
        <ejb-ref>
            <ejb-ref-name>ejb/ProcessPaymentHomeRemote</ejb-ref-name>
            <ejb-ref-type>Session</ejb-ref-type>
            <home>com.titan.processpayment.ProcessPaymentHomeRemote</home>
            <remote>com.titan.processpayment.ProcessPaymentRemote</remote>
        </ejb-ref>
        <ejb-ref>
            <ejb-ref-name>ejb/CustomerHomeRemote</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <home>com.titan.customer.CustomerHomeRemote</home>
            <remote>com.titan.customer.CustomerRemote</remote>
        </ejb-ref>
        <ejb-local-ref>
            <ejb-ref-name>ejb/CruiseHomeLocal</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <local-home>com.titan.cruise.CruiseHomeLocal</local-home>
            <local>com.titan.cruise.CruiseLocal</local>
        </ejb-local-ref>
        <ejb-local-ref>
            <ejb-ref-name>ejb/CabinHomeLocal</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <local-home>com.titan.cabin.CabinHomeLocal</local-home>
            <local>com.titan.cabin.CabinLocal</local>
        </ejb-local-ref>
        <ejb-local-ref>
            <ejb-ref-name>ejb/ReservationHomeLocal</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <local-home>com.titan.reservation.ReservationHomeLocal</local-home>
            <local>com.titan.reservation.ReservationLocal</local>
        </ejb-local-ref>
        <security-identity>
            <run-as>
                <role-name>everyone</role-name>
            </run-as>
        </security-identity>
        <resource-ref>
                     <res-ref-name>jms/QueueFactory</res-ref-name>
                     <res-type>javax.jms.QueueConnectionFactory</res-type>
                     <res-auth>Container</res-auth>
        </resource-ref>
    </message-driven>
    ...
</enterprise-beans>

An MDB is declared in a <message-driven> element within the <enterprise-beans> element, alongside <session> and <entity> beans. Like session beans, an MDB defines an <ejb-name>, <ejb-class>, and <transaction-type>; unlike other kinds of beans, an MDB never defines local or remote component interfaces. MDBs do not have remote or local interfaces, so these definitions aren’t needed.

Message selector

An MDB can declare a message selector . Message selectors allow an MDB to be more selective about the messages it receives from a particular topic or queue. Message selectors use Message properties as criteria in conditional expressions.[40] These conditional expressions use Boolean logic to declare which messages should be delivered. In EJB 2.1, a message selector is declared using standard property name, messageSelector, in an activation configuration element:

<activation-property>
   <activation-config-property-name>messageSelector
   </activation-config-property-name>
   <activation-config-property-value>MessageFormat = 'Version 3.4'
   </activation-config-property-value> 
<activation-property>

In EJB 2.0, a message selector is declared using the <message-selector> element:

<message-selector>MessageFormat = 'Version 3.4'</message-selector>

Message selectors are based on message properties. Message properties are additional headers that can be assigned to a message; they allow vendors and developers to attach information to a message that isn’t part of the message’s body. The Message interface provides several methods for reading and writing properties. Properties can have a String value or one of several primitive values (boolean, byte, short, int, long, float, double). The naming of properties, together with their values and conversion rules, is strictly defined by JMS.

The ReservationProcessor EJB uses a message selector filter to select messages of a specific format. In this case the format is “Version 3.4”; this is a string Titan uses to identify messages of type MapMessage that contain the name values CustomerID, CruiseID, CabinID, CreditCard, and Price. In other words, adding a MessageFormat to each reservation message allows us to write MDBs that are designed to process different kinds of reservation messages. If a new business partner needs to use a different type of Message object, Titan would use a new message version and an MDB to process it.

Here’s how a JMS producer would go about setting a MessageFormat property on a Message:

Message message = session.createMapMessage( );
message.setStringPropery("MessageFormat","Version 3.4");

// set the reservation named values

sender.send(message);

The message selectors are based on a subset of the SQL-92 conditional expression syntax that is used in the WHERE clauses of SQL statements. They can become fairly complex, including the use of literal values, Boolean expressions, unary operators, and so on.

Acknowledge mode

A JMS acknowledgment means that the JMS client notifies the JMS provider (message router) when a message is received. In EJB, it’s the MDB container’s responsibility to send an acknowledgment when it receives a message. Acknowledging a message tells the JMS provider that an MDB container has received and processed the message. Without an acknowledgment, the JMS provider does not know whether the MDB container has received the message, and unwanted redeliveries can cause problems. For example, once we have processed a reservation message using the ReservationProcessor EJB, we don’t want to receive the same message again.

In EJB 2.1, the acknowledgment mode is set using the standard acknowledgeMode activation configuration property, as shown in the following XML snippet:

<activation-property>
    <activation-config-property-name>acknowledgeMode
    </activation-config-property-name>
    <activation-config-property-value>Auto-acknowledge
    </activation-config-property-value> 
<activation-property>

In EJB 2.0, the acknowledgment mode is set using a special <acknowledge-mode> element, as shown in the following XML snippet:

<acknowledge-mode>Auto-acknowledge</acknowledge-mode>

Two values can be specified for acknowledgment mode: Auto-acknowledge and Dups-ok-acknowledge . Auto-acknowledge tells the container that it should send an acknowledgment to the JMS provider soon after the message is given to an MDB instance to process. Dups-ok-acknowledge tells the container that it doesn’t have to send the acknowledgment immediately; any time after the message is given to the MDB instance will be fine. With Dups-ok-acknowledge, it’s possible for the MDB container to delay acknowledgment so long that the JMS provider assumes that the message was not received and sends a “duplicate” message. Obviously, with Dups-ok-acknowledge, your MDBs must be able to handle duplicate messages correctly.

Auto-acknowledge avoids duplicate messages because the acknowledgment is sent immediately. Therefore, the JMS provider won’t send a duplicate. Most MDBs use Auto-acknowledge to avoid processing the same message twice. Dups-ok-acknowledge exists because it can allow a JMS provider to optimize its use of the network. In practice, though, the overhead of an acknowledgment is so small, and the frequency of communication between the MDB container and JMS provider is so high, that Dups-ok-acknowledge doesn’t have a big impact on performance.

Having said all of this, the acknowledgement mode is ignored most of the time—in fact, it is ignored unless the MDB executes with bean-managed transactions, or with the container-managed transaction attribute NotSupported (see Chapter 15). In all other cases, transactions are managed by the container, and acknowledgment takes place within the context of the transaction. If the transaction succeeds, the message is acknowledged. If the transaction fails, the message is not acknowledged. When using container-managed transactions with a Required transaction attribute, the acknowledgment mode is usually not specified; however, it is included in the deployment descriptor for the sake of discussion.

EJB 2.1: <messaging-type>

The <messaging-type> element declares the messaging interfaces used by the MDB:

                     <messaging-type>javax.jms.MessageListener</messaging-type>

For JMS-based MDBs, the messaging interface is always going to be javax.jms.MessageListener, but for other Connector-based MDBs it might be something completely different. If the <messaging-type> element is omitted, the type is assumed to be javax.jms.MessageListener.

EJB 2.1: <message-destination-type>

The <message-destination-type> element indicates the type of destination from which the MDB receives messages. The allowed values for JMS-based MDBs are javax.jms.Queue and javax.jms.Topic. A Connector-based MDB might use some other type. The value must always be a fully qualified class name.

In the ReservationProcessor EJB, this value is set to javax.jms.Queue, indicating that the MDB is getting its messages via the p2p messaging model from a queue:

<message-destination-type>
    javax.jms.Queue
</message-destination-type>

When the MDB is deployed, the deployer maps the MDB so that it listens to a real queue on the network.

You may have noticed that the <message-destination-type> and the destinationType configuration property specify the same thing. This seems redundant, and it is for JMS-based MDBs—but for Connector-based MDBs, it is not. That’s because Connector-based MDBs have completely different activation configuration properties than a JMS-based MDB. It’s important that the <message-destination-type> be specified for both JMS-based and Connector-based MDBs.

EJB 2.0: <message-driven-destination>

The <message-driven-destination> element indicates the type of destination from which the MDB receives messages. The allowed values for this element are javax.jms.Queue and javax.jms.Topic. In the ReservationProcessor EJB, this value is set to javax.jms.Queue, indicating that the MDB is getting its messages via the point-to-point messaging model from a queue:

<message-driven-destination>
    <destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>

When the MDB is deployed, the deployer maps the MDB so that it listens to a real queue on the network.

Subscription durability

In EJB 2.1 and EJB 2.0, when a JMS-based MDB uses a javax.jms.Topic, the deployment descriptor must declare whether the subscription is Durable or NonDurable . A Durable subscription outlasts an MDB container’s connection to the JMS provider, so if the EJB server suffers a partial failure, shuts down, or otherwise disconnects from the JMS provider, the messages that it would have received are not lost. The provider stores any messages that are delivered while the container is disconnected; the messages are delivered to the container (and from there, to the MDB) when the container reconnects. This behavior is commonly referred to as store-and-forward messaging . Durable MDBs are tolerant of disconnections, whether intentional or the result of a partial failure.

If the subscription is NonDurable, any messages the bean would have received while it was disconnected are lost. Developers use NonDurable subscriptions when it is not critical for all messages to be processed. Using a NonDurable subscription improves the performance of the JMS provider but significantly reduces the reliability of the MDBs.

In EJB 2.1, durability is declared using the standard subscriptionDurability activation configuration property:

<activation-property>
<activation-config-property-name>subscriptionDurability
</activation-config-property-name>
<activation-config-property-value>Durable
</activation-config-property-value> 
<activation-property>

In EJB 2.0, durability is declared by the <subscription-durability> element within the <message-driven-destination> element:

<message-driven-destination>
    <destination-type>javax.jms.Topic</destination-type>
    <subscription-durability>Durable</subscription-durability>
</message-driven-destination>

When the destination type is javax.jms.Queue, as is the case in the ReservationProcessor EJB, durability is not a factor because of the nature of queue-based messaging systems. With a queue, messages may be consumed only once and remain in the queue until they are distributed to one of the queue’s listeners.

The rest of the elements in both the EJB 2.1 and EJB 2.0 deployment descriptors should already be familiar. The <ejb-ref> element provides JNDI ENC bindings for a remote EJB home object, while the <ejb-local-ref> elements provide JNDI ENC bindings for local EJB home objects. Note that the <resource-ref> element that defined the JMS QueueConnectionFactory used by the ReservationProcessor EJB to send ticket messages is not accompanied by a <resource-env-ref> element. The queue to which the tickets are sent is obtained from the JMSReplyTo header of the MapMessage itself, and not from the JNDI ENC.

The ReservationProcessor Clients

In order to test the ReservationProcessor EJB, we need to develop two new client applications: one to send reservation messages and the other to consume ticket messages produced by the ReservationProcessor EJB.

The reservation message producer

The JmsClient_ReservationProducer sends 100 reservation requests very quickly. The speed with which it sends these messages forces many containers to use multiple MDB instances to process them. The code for JmsClient_ReservationProducer looks like this:

import javax.jms.Message;
import javax.jms.MapMessage;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueConnection;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Queue;
import javax.jms.QueueSender;
import javax.jms.JMSException;
import javax.naming.InitalContext;
import java.util.Date;

import com.titan.processpayment.CreditCardDO;

public class JmsClient_ReservationProducer {

    public static void main(String [] args) throws Exception {
            
        InitialContext jndiContext = getInitialContext( );
        
        QueueConnectionFactory factory = (QueueConnectionFactory)
            jndiContext.lookup("QueueFactoryNameGoesHere");

        Queue reservationQueue = (Queue)
            jndiContext.lookup("QueueNameGoesHere");

        QueueConnection connect = factory.createQueueConnection( );

        QueueSession session = 
            connect.createQueueSession(false,Session.AUTO_ACKNOWLEDGE); 

        QueueSender sender = session.createSender(reservationQueue);
        
        Integer cruiseID = new Integer(1);
        
        for(int i = 0; i < 100; i++){
            MapMessage message = session.createMapMessage( );
            message.setStringProperty("MessageFormat","Version 3.4");
            
            message.setInt("CruiseID",1);
            message.setInt("CustomerID",i%10);
            message.setInt("CabinID",i);
            message.setDouble("Price", (double)1000+i);
            
            // the card expires in about 30 days
            Date expirationDate = new Date(System.currentTimeMillis( )+43200000);
            message.setString("CreditCardNum", "923830283029");
            message.setLong("CreditCardExpDate", expirationDate.getTime( ));
            message.setString("CreditCardType", CreditCardDO.MASTER_CARD);
            
            sender.send(message);           
        }
        connect.close( );
    }
    
    public static InitialContext getInitialContext( )
        throws JMSException {
        // create vendor-specific JNDI context here
    }
}

Note that the JmsClient_ReservationProducer sets the CustomerID, CruiseID, and CabinID as primitive int values, but the ReservationProcessorBean reads these values as java.lang.Integer types. This is not a mistake. The MapMessage automatically converts any primitive to its proper wrapper if that primitive is read using MapMessage.getObject( ) . So, for example, a named value that is loaded into a MapMessage using setInt( ) can be read as an Integer using getObject( ). For example, the following code sets a value as a primitive int and then accesses it as a java.long.Integer object:

MapMessage mapMsg = session.createMapMessage( );

mapMsg.setInt("TheValue",3);

Integer myInteger = (Integer)mapMsg.getObject("TheValue");

if(myInteger.intValue( ) == 3 )
    // this will always be true

The ticket message consumer

The JmsClient_TicketConsumer is designed to consume all the ticket messages delivered by ReservationProcessor EJB instances to the queue. It consumes the messages and prints out the descriptions:

import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueConnection;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Queue;
import javax.jms.QueueReceiver;
import javax.jms.JMSException;
import javax.naming.InitalContext;

import com.titan.travelagent.TicketDO;

public class JmsClient_TicketConsumer  
    implements javax.jms.MessageListener {

    public static void main(String [] args) throws Exception {
        
        new JmsClient_TicketConsumer( );
        
        while(true){Thread.sleep(10000);}
        
    }
        
    public JmsClient_TicketConsumer( ) throws Exception {
            
        InitialContext jndiContext = getInitialContext( );
        
        QueueConnectionFactory factory = (QueueConnectionFactory)
            jndiContext.lookup("QueueFactoryNameGoesHere");
        Queue ticketQueue = (Queue)jndiContext.lookup("QueueNameGoesHere");
        QueueConnection connect = factory.createQueueConnection( );
        QueueSession session = 
            connect.createQueueSession(false,Session.AUTO_ACKNOWLEDGE); 
        QueueReceiver receiver = session.createReceiver(ticketQueue);

        receiver.setMessageListener(this);
        
        connect.start( );
    }
    
    public void onMessage(Message message) {
        try {
            ObjectMessage objMsg = (ObjectMessage)message;
            TicketDO ticket = (TicketDO)objMsg.getObject( );
            System.out.println("********************************");
            System.out.println(ticket);
            System.out.println("********************************");
        
        } catch(JMSException jmsE) {
            jmsE.printStackTrace( );
        }
    }
    public static InitialContext getInitialContext( ) throws JMSException {
        // create vendor-specific JNDI context here
    }
}

To make the ReservationProcessor EJB work with the two client applications, JmsClient_ReservationProducer and JmsClient_TicketConsumer, you must configure your EJB container’s JMS provider so that it has two queues: one for reservation messages and another for ticket messages.

Exercise 12.2 in the Workbook shows how to deploy these examples in the JBoss EJB container.



[39] In EJB 2.0, if the destination identified by the JMSReplyTo attribute is of type Queue, the point-to-point (queue-based) messaging model must be used. If the destination type identified by the JMSReplyTo attribute is Topic, the publish-and-subscribe (topic-based) messaging model must be used. In EJB 2.1, you can use the Unified API for both publish-and-subscribe and point-to-point messaging.

[40] Message selectors are also based on message headers, which are outside the scope of this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.12.232