Exercise 12.2: The Message-Driven Bean

This exercise is an extension of the preceding one. You’ll add a message-driven bean (MDB), ReservationProcessor, which plays the same role as the TravelAgent EJB but receives its booking orders through a JMS queue instead of synchronous RMI invocations.

To test the MDB, you’ll build a new client application that makes multiple reservations in batch, using a JMS queue that’s bound to the MDB. You’ll also build a second client application that listens on another queue to receive booking confirmations.

Along the way, you’ll learn how to create a new JMS queue in JBoss and configure a message-driven bean (MDB).

Start Up JBoss

If JBoss is already running, there is no reason to restart it.

Initialize the Database

Because the exercise uses the ProcessPayment EJB used in recent exercises, the database must contain the PAYMENT table. The createdb and dropdb Ant targets, Java code, and clients here have been borrowed from exercise 12_1.

If you haven’t already dropped the PAYMENT table after running the examples in Exercise 12.1, do so now by running the dropdb Ant target.

C:workbookex12_2>ant dropdb
Buildfile: build.xml

prepare:

compile:

dropdb:
     [java] Looking up home interfaces..
     [java] Dropping database table...

BUILD SUCCESSFUL

Then re-create the PAYMENT database table by running the createdb Ant target:

C:workbookex12_2>ant createdb
Buildfile: build.xml

prepare:

compile:

ejbjar:

createdb:
     [java] Looking up home interfaces..
     [java] Creating database table...

On the JBoss console, the following lines are displayed:

INFO  [STDOUT] Creating table PAYMENT...
INFO  [STDOUT] ...done!

Tip

If you’re having trouble creating the database, shut down JBoss. Then run the Ant build target clean.db. This will remove all database files and allow you to start fresh.

The persistence of all other entity beans used in this exercise is managed by the container, so it will create the needed tables for them automatically.

Create a New JMS Queue

This exercise requires two different JMS queues, one for the ReservationProcessor MDB and one to receive booking confirmations.

Adding new JMS queues to JBoss is much like adding new JMS topics. As in the preceding exercise, you have two options, one involving a configuration file, the other the JMX HTTP connector.

Adding a JMS queue through a configuration file

The most common way to set up a JMS queue is to use an XML configuration file. This part of the exercise shows you how to write a JMX MBean definition for a new JMS queue. You can find the JMX configuration file in ex12_2/src/resources/services.

jbossmq-titanqueues-service.xml

<server>
  <mbean code="org.jboss.mq.server.jmx.Queue"
         name="jboss.mq.destination:service=Queue,
               name=titan-ReservationQueue">
    <depends optional-attribute-name="DestinationManager"
     >jboss.mq:service=DestinationManager</depends>
  </mbean>
  
                     
                          <mbean code="org.jboss.mq.server.jmx.Queue"
         name="jboss.mq.destination:service=Queue,
               name=titan-TicketQueue">
    <depends optional-attribute-name="DestinationManager"
     >jboss.mq:service=DestinationManager</depends>
  </mbean>
</server>

Recall that each set of MBeans must be defined within a <server> tag and each MBean declared in an <mbean> tag. Because this exercise requires two different queues, we’ve defined two MBeans. The MBean class that represents a JMS queue is org.jboss.mq.server.jmx.Queue. Its name property specifies the name of the JMS queue to be created, such as titan-ReservationQueue and titan-TicketQueue.

Remember also that the application server must deploy the DestinationManager MBean before any queue or topic is deployed. This dependency is declared within the <depends> tag in jbossmq-titanqueues-service.xml. JBoss will take care of satisfying this dependency and make sure the titan-ReservationQueue and titan-TicketQueue will not be started until the DestinationManager MBean has finished initializing and is ready to provide services to new queues and topics. Copying this file into the JBoss deploy directory will hot-deploy these JMS queues and make them ready for use.

To deploy jbossmq-titanqueues-service.xml, run the make-queues Ant target:

C:workbookex12_2>ant make-queues
Buildfile: build.xml

make-queues:
     [copy] Copying 1 file to C:jboss-4.0serverdefaultdeploy

On the server side, the following lines are displayed:

[titan-ReservationQueue] Bound to JNDI name: queue/titan-ReservationQueue
[titan-TicketQueue] Bound to JNDI name: queue/titan-TicketQueue

Warning

You must deploy the XML file containing the queues before you deploy the JAR containing your beans (see below). If you deploy your EJB JAR first, JBoss detects that the MDB’s expected queue does not exist and creates it dynamically. Then, when you try to deploy the XML file that contains the queues, an exception arises, and you’ll be told you’re trying to create a queue that already exists.

Adding a JMS queue through the JMX HTTP connector

Add each of the new JMS queues through the JMX HTTP connector the same way you added the JMS topic in the preceding exercise, with one obvious difference: instead of using the createTopic( ) operation of the JBossMQ server, use the createQueue( ) operation.

Remember that queues and topics created in the JMX HTTP Connector live only until the application server is shut down.

Examine the EJB Standard Files

The ejb-jar.xml file for this exercise is based on the one for Exercise 12.1. The only notable difference is the addition of the new ReservationProcessor MDB.

ejb-jar.xml

<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>

The MDB descriptor specifies container-managed transactions and automatic acknowledgement of messages, and that messages will be received from a queue rather than a topic. The descriptor also contains a <message-selector> tag that allows the MDB to receive only those messages that conform to a specified format. Then a set of <ejb-ref> entries identifies all the beans that ReservationProcessor beans will use during their execution:

  <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-link>ProcessPaymentEJB</ejb-link>
  </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-link>CustomerEJB</ejb-link>
  </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-link>CruiseEJB</ejb-link>
  </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-link>CabinEJB</ejb-link>
  </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-link>ReservationEJB</ejb-link>
  </ejb-local-ref>
  <security-identity>
    <run-as><role-name>everyone</role-name></run-as>
  </security-identity>

Because the MDB will send a confirmation message to a queue once the booking has been successful, it needs a reference to a javax.jms.QueueConnectionFactory, specified in the <resource-ref> at the end of the MDB descriptor:

  <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>

Note this difference from the preceding exercise: while this bean does send messages to a queue, its descriptor does not contain a <resource-env-ref> entry that refers to the destination queue. Why not? In Exercise 12.1, the destination was fixed at deployment time, but in this exercise the destination is not fixed and not even known by the MDB. It is the client application that knows the destination, and transmits it to the MDB by serializing the JMS queue object as part of the JMS message.

Examine the JBoss-Specific Files

No modifications have been made to the CMP entity beans, so the jbosscmp-jdbc.xml file is unchanged.

The jboss.xml file does need modification to take the new ReservationProcessor EJB into account.

jboss.xml

<message-driven>
  <ejb-name>ReservationProcessorEJB</ejb-name>
  <destination-jndi-name
    >queue/titan-ReservationQueue<
    /destination-jndi-name>
  <resource-ref>
    <res-ref-name>jms/QueueFactory</res-ref-name>
    <jndi-name>java:/JmsXA</jndi-name>
  </resource-ref>
</message-driven>

The <destination-jndi-name> tag maps the MDB to an existing JMS destination in the deployment environment. You should recognize the name of one of the two JMS queues you just created: titan-ReservationQueue.

Tip

By default, each MDB EJB deployed in JBoss can serve up to 15 concurrent messages.

The <resource-ref> tag maps the ConnectionFactory name used by the ReservationProcessor EJB to an actual factory in the deployment environment. This mapping is identical to the one in the exercise for the TravelAgent EJB.

Build and Deploy the Example Programs

Perform the following steps:

  1. Open a command prompt or shell terminal and change to the ex12_2 directory created by the extraction process

  2. Set the JAVA_HOME and JBOSS_HOME environment variables to point to where your JDK and JBoss 4.0 are installed. Examples:

    Windows:C:workbookex12_2> set JAVA_HOME=C:jdk1.4.2 C:workbookex12_2> set JBOSS_HOME=C:jboss-4.0
    Unix:$ export JAVA_HOME=/usr/local/jdk1.4.2 $ export JBOSS_HOME=/usr/local/jboss-4.0
  3. Add ant to your execution path.

    Windows:C:workbookex12_2> set PATH=..antin;%PATH%
    Unix:$ export PATH=../ant/bin:$PATH
  4. Perform the build by typing ant.

As in the last exercise, you will see titan.jar rebuilt, copied to the JBoss deploy directory, and redeployed by the application server.

Examine the Client Applications

In this exercise, you’ll use two client applications at the same time. The producer generates large numbers of JMS messages reporting passage bookings, destined for the ReservationProcessor MDB EJB. The consumer listens to a JMS queue for messages confirming the bookings, and displays them as they come in.

The producer first gets the cruise ID and the number of bookings from the command line.

JmsClient_ReservationProducer.java

public static void main (String [] args) throws Exception
{
   if (args.length != 2)
      throw new Exception
   ("Usage: java JmsClient_ReservationProducer <CruiseID> <count>");
   
   Integer cruiseID = new Integer (args[0]);
   int count = new Integer (args[1]).intValue ( );

The producer then looks up a QueueConnectionFactory and two JMS queues from the JBoss naming service. The first queue is the one bound to the ReservationProcessor MDB, to which passage booking messages will be sent. The second is not used directly, as you’ll see later.

QueueConnectionFactory factory = (QueueConnectionFactory)
  jndiContext.lookup ("ConnectionFactory");
Queue reservationQueue = (Queue)
  jndiContext.lookup ("queue/titan-ReservationQueue");
Queue ticketQueue = (Queue)
  jndiContext.lookup ("queue/titan-TicketQueue");
QueueConnection connect = factory.createQueueConnection ( ); 
QueueSession session = connect.createQueueSession 
  (false,Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session.createSender (reservationQueue);

The client application is now ready to send count booking messages in batch. Among other chores, it has looked up the ticket queue, the JMS queue that the ReservationProcessor MDB will use to send confirmation messages.

For each booking, it then creates a JMS MapMessage, assigns the ticket queue into the message’s JMSReplyTo property, and sets the booking data: Cruise ID, Customer ID, Cabin ID, price, credit card number, and expiration date, and so on. Note that only basic data types such as String and int can be stored in a MapMessage:

   for (int i = 0; i < count; i++)
   {
      MapMessage message = session.createMapMessage ( );
      
      // Used in ReservationProcessor to send Tickets back out
      message.setJMSReplyTo (ticketQueue);
      
      message.setStringProperty ("MessageFormat", "Version 3.4");
          
      message.setInt ("CruiseID", cruiseID.intValue ( ));
      // either Customer 1 or 2, all we've got in database
      message.setInt ("CustomerID", i%2 + 1);  
      // cabins 100-109 only
      message.setInt ("CabinID", i%10 + 100); 
      message.setDouble ("Price", (double)1000 + i);
      
      // the card expires in about 30 days
      Date expDate = new Date (System.currentTimeMillis ( ) + 
                              30*24*60*60*1000L);
      
      message.setString ("CreditCardNum", "5549861006051975");
      message.setLong ("CreditCardExpDate", expDate.getTime ( ));
      message.setString ("CreditCardType", 
                         CreditCardDO.MASTER_CARD);
      
      System.out.println ("Sending reservation message #" + i);
      sender.send (message);
   }
   
   connect.close ( );
}

One interesting property that’s set in the JMS message header is MessageFormat. Recall that the <message-selector> tag in the MDB deployment descriptor used this property to specify a constraint on the messages the MDB is to receive.

Once all messages are sent, the application closes the connection and terminates. Because messages are sent asynchronously, the application may terminate before the ReservationProcessor EJB has processed all of the messages in the batch.

The consumer application is very similar to the client application in Exercise 12.1. This time, though, it will subscribe not to a topic but to a queue.

JmsClient_TicketConsumer.java

To receive JMS messages, the client application class implements the javax.jms.MessageListener interface, which defines the onMessage( ) method. The main method simply creates an instance of the class and uses a trick to make the main thread wait indefinitely:

public class JmsClient_TicketConsumer
   implements javax.jms.MessageListener
{
   
   public static void main (String [] args) throws Exception
   {      
      new JmsClient_TicketConsumer ( );
      
      while(true) { Thread.sleep (10000); }
   }

The constructor is very simple JMS code that subscribes the client application to the JMS queue and waits for incoming messages:

 public JmsClient_TicketConsumer ( ) throws Exception
 {      
    Context jndiContext = getInitialContext ( );
      
    QueueConnectionFactory factory = (QueueConnectionFactory)
       jndiContext.lookup ("ConnectionFactory");
    Queue ticketQueue = (Queue)
       jndiContext.lookup ("queue/titan-TicketQueue");
    QueueConnection connect = factory.createQueueConnection ( );
    QueueSession session =
      connect.createQueueSession (false,Session.AUTO_ACKNOWLEDGE);
    QueueReceiver receiver = session.createReceiver (ticketQueue);
    receiver.setMessageListener (this);
      
    System.out.println ("Listening for messages on titan-
                         TicketQueue...");
    connect.start ( );
   }

When a message arrives in the queue, the consumer’s onMessage( ) method is called. The method simply displays the content of the ticket:

 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 displayed)
    {
       displayed.printStackTrace ( );
    }
 }

Run the Client Applications

When you redeployed titan.jar, JBoss dropped and recreated the database tables, destroying any existing content, so you must repopulate the database. Have Ant execute the run.client_112a target.

The run.client_112a target originated in Exercise 11.2, but we’ve duplicated it in the ex12_2 directory for your convenience.

C:workbookex12_2>ant run.client_112a
Buildfile: build.xml

prepare:

compile:

ejbjar:

run.client_112a:
  [java] Calling TravelAgentBean to create sample data..
  [java] All customers have been removed
  [java] All cabins have been removed
  [java] All ships have been removed
  [java] All cruises have been removed
  [java] All reservations have been removed
  [java] Customer with ID 1 created (Burke Bill)
  [java] Customer with ID 2 created (Labourey Sacha)
  [java] Created ship with ID 101...
  [java] Created ship with ID 102...
  [java] Created cabins on Ship A with IDs 100-109
  [java] Created cabins on Ship B with IDs 200-209
  [java] Created Alaska Cruise with ID 0 on ShipA
  [java] Created Norwegian Fjords Cruise with ID 1 on ShipA
  [java] Created Bermuda or Bust Cruise with ID 2 on ShipA
  [java] Created Indian Sea Cruise with ID 3 on ShipB
  [java] Created Australian Highlights Cruise with ID 4 on ShipB
  [java] Created Three-Hour Cruise with ID 5 on ShipB
  [java] Made reservation for Customer 1 on Cruise 0 for Cabin 103
  [java] Made reservation for Customer 1 on Cruise 5 for Cabin 208
  [java] Made reservation for Customer 2 on Cruise 1 for Cabin 105
  [java] Made reservation for Customer 2 on Cruise 5 for Cabin 202

BUILD SUCCESSFUL

At this point, you’re going to launch both the client that sends booking messages and the client that receives the tickets as passage confirmations. Launch the consumer first by invoking the Ant target run.client_122:

C:workbookex12_2>ant run.client_122
Buildfile: build.xml

prepare:

compile:

ejbjar:

run.client_122:
     [java] Listening for messages on titan-TicketQueue...

Now start the producer, adhering to the following usage:

BookInBatch <cruiseID> <count>

where cruiseID is the ID of a Cruise in the database (created when you invoked the run.client_112a Ant target) and count is the number of passages to book.

Book 100 passages on the Alaskan Cruise:

C:workbookex12_2>BookInBatch  0 100
Buildfile: build.xml

prepare:

compile:

ejbjar:

run.bookinbatch:
     [java] Sending reservation message #0
     [java] Sending reservation message #1
     [java] Sending reservation message #2
     [java] Sending reservation message #3
     ...
     [java] Sending reservation message #98
     [java] Sending reservation message #99

Shortly after the producer starts, the consumer, which has been patiently listening to its JMS queue for booking confirmations, will display:

run.client_122:
     [java] Listening for messages on titan-TicketQueue...
     [java] ********************************
     [java] Bob Smith has been booked for the Alaska Cruise cruise 
                  on ship Nordic Prince.
     [java]  Your accommodations include Suite 100 a 1 bed cabin on 
                   deck level 1.
     [java]  Total charge = 1000.0
     [java] ********************************
     [java] ********************************
     [java] Joseph Stalin has been booked for the Alaska Cruise 
            cruise on ship Nordic Prince.
     [java]  Your accommodations include Suite 101 a 1 bed cabin on 
             deck level 1.
     [java]  Total charge = 1001.0
     [java] ********************************
     [java] ********************************
     [java] Bob Smith has been booked for the Alaska Cruise cruise 
            on ship Nordic Prince.
     [java]  Your accommodations include Suite 102 a 1 bed cabin on 
             deck level 1.
     [java]  Total charge = 1002.0
     [java] ********************************
     ...
     [java] ********************************
     [java] Joseph Stalin has been booked for the Alaska Cruise 
            cruise on ship Nordic Prince.
     [java]  Your accommodations include Suite 109 a 1 bed cabin on 
             deck level 1.
     [java]  Total charge = 1099.0
     [java] ********************************

Note that because the booking confirmation messages are queued, you could start the consumer much later than the producer, rather than before. The confirmation messages sent by the ReservationProcessor MDB would then be stored on the server until the client application starts and begins to listen to the queue.

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

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