ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

EJB Message-Driven Beans
Pages: 1, 2, 3, 4, 5, 6, 7

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 is designed to send 100 reservation requests very quickly. The speed with which it sends these messages will force many MDB containers to use multiple instances to process the reservation messages. 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.createQueueConneciton();
 
    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
  }
}

You may have noticed 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.createQueueConneciton();
 
    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.

The Life Cycle of a Message-Driven Bean

Some vendors may not pool MDB instances, but may instead create and destroy instances with each new message. This is an implementation-specific decision that should not impact the specified life cycle of the stateless bean instance.

Just as the entity and session beans have well-defined life cycles, so does the MDB bean. The MDB instance's life cycle has two states: Does Not Exist and Method-Ready Pool. The Method-Ready Pool is similar to the instance pool used for stateless session beans. Like stateless beans, MDBs define instance pooling in their life cycles.

Figure 13-4 illustrates the states and transitions that an MDB instance goes through in its lifetime.

Diagram.
Figure 13-4. MDB life cycle

Does Not Exist

When an MDB instance is in the Does Not Exist state, it is not an instance in the memory of the system. In other words, it has not been instantiated yet.

The Method-Ready Pool

MDB instances enter the Method-Ready Pool as the container needs them. When the EJB server is first started, it may create a number of MDB instances and enter them into the Method-Ready Pool. (The actual behavior of the server depends on the implementation.) When the number of MDB instances handling incoming messages is insufficient, more can be created and added to the pool.

Transitioning to the Method-Ready Pool

When an instance transitions from the Does Not Exist state to the Method-Ready Pool, three operations are performed on it. First, the bean instance is instantiated when the container invokes the Class.newInstance() method on the MDB class. Second, the setMessageDrivenContext() method is invoked by the container providing the MDB instance with a reference to its EJBContext. The MessageDrivenContext reference may be stored in an instance field of the MDB.

Finally, the no-argument ejbCreate() method is invoked by the container on the bean instance. The MDB has only one ejbCreate() method, which takes no arguments. The ejbCreate() method is invoked only once in the life cycle of the MDB.

The duration of an MDB instance's life is assumed to be very long. However, some EJB servers may actually destroy and create instances with every new message, making this strategy less attractive. Consult your vendor's documentation for details on how your EJB server handles stateless instances.

MDBs are not subject to activation, so they can maintain open connections to resources for their entire life cycles. The ejbRemove() method should close any open resources before the MDB is evicted from memory at the end of its life cycle.

Life in the Method-Ready Pool

Related Reading

Enterprise JavaBeans, 3rd EditionEnterprise JavaBeans, 3rd Edition
By Richard Monson-Haefel
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

Once an instance is in the Method-Ready Pool, it is ready to handle incoming messages. When a message is delivered to an MDB, it is delegated to any available instance in the Method-Ready Pool. While the instance is executing the request, it is unavailable to process other messages. The MDB can handle many messages simultaneously, delegating the responsibility of handling each message to a different MDB instance. When a message is delegated to an instance by the container, the MDB instance's MessageDrivenContext changes to reflect the new transaction context. Once the instance has finished, it is immediately available to handle a new message.

Transitioning out of the Method-Ready Pool: The death of an MDB instance

Bean instances leave the Method-Ready Pool for the Does Not Exist state when the server no longer needs them. This occurs when the server decides to reduce the total size of the Method-Ready Pool by evicting one or more instances from memory. The process begins by invoking the ejbRemove() method on the instance. At this time, the bean instance should perform any cleanup operations, such as closing open resources. The ejbRemove() method is invoked only once in the life cycle of an MDB instance--when it is about to transition to the Does Not Exist state. During the ejbRemove() method, the MessageDrivenContext and access to the JNDI ENC are still available to the bean instance. Following the execution of the ejbRemove() method, the bean is dereferenced and eventually garbage collected.


View catalog information for Enterprise JavaBeans, 3rd Edition

Return to ONJava.com.