Exercise 11.4: Stateful Session Bean




Exercise 11.4: Stateful Session Bean

This exercise uses the stateful session TravelAgent EJB introduced in Chapter 11 to implement a crude command-line-driven reservation system. Through this reservation system, you can book a cruise for a customer and pay for it with their credit card.

Start Up JBoss

If you already have JBoss running, there is no reason to restart it. Otherwise, start it up as instructed in Workbook 1.

Initialize the Database

The database tables will be created when Exercise 11.4 is deployed to JBoss. If you have problems running this example, shut down JBoss and run the clean.db Ant task.

Build and Deploy the Example Programs

Perform the following steps:

  1. Open a command prompt or shell terminal and change to the ex11_4 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:\workbook\ex11_4> set JAVA_HOME=C:\jdk1.5.0
    C:\workbook\ex11_4> set JBOSS_HOME=C:\jboss-4.0.x
    


    Unix:

    $ export JAVA_HOME=/usr/local/jdk1.5.0
    $ export JBOSS_HOME=/usr/local/jboss-4.0
    

  3. Add ant to your execution path. Ant is the build utility.


    Windows:

    C:\workbook\ex11_4> set PATH=..\ant\bin;%PATH%
    $ export PATH=../ant/bin:$PATH
    


    Unix:

    $ export PATH=../ant/bin:$PATH
    

  4. Perform the build by typing ant.

As in the earlier exercises, titan.jar is rebuilt, copied to the JBoss deploy directory, and redeployed by the application server.

Examine ProcessPaymentBean and TravelAgentBean

These two EJBs are taken directly from Chapter 11, so there is no need to go over their code.

Examine DataAccessBean

The com.titan.access.DataAccessBean class was expanded from the previous example to completely initialize the database with a set of Ship, Cruise, and Cabin entities. This initialization is done in the DataAccessBean.initializeDB( ) method. We won't go over this code because it isn't that interesting.

Examine the Client

The main client is the com.titan.clients.TravelAgentShell class. It implements a command-line console that allows you to view cruises and book reservations interactively. The heart of this class is the shell( ) method:

   public void shell( ) throws Exception
   {
      access = (DataAccess)getInitialContext( ).lookup("DataAccessBean/remote");
      access.initializeDB( );
      try
      {
         access.makePaymentDbTable( );
      }
      catch (Exception ignored) {}
      while (true)
      {
         System.out.println( );
         System.out.print("> ");

         String command = "";
         char read = '\0';

         while (read != '\r' && read != '\n')
         {
            read = (char)System.in.read( );
            command = command + read;
         }
         // clear out newlines from system input
         int available = System.in.available( );
         for (int i = 0; i < available; i++) System.in.read( );

         command = command.trim( );
         if (command.equals(""))
         {
            continue;
         }
         processCommand(command);
      }
   }

The shell( ) method starts by getting a reference to DataAccessBean and initializing the database. It then goes into an infinite loop that accepts command-line input. When it receives user input, it calls the processCommand( ) method to execute the command. The processCommand( ) method simply does a series of if-else-if blocks to determine the user command entered. We won't go over this code because it is pretty straightforward. Instead, let's look at each method that implements the commands of our console:

   public void cruises( )
   {
      System.out.println( );
      List list = access.getCruises( );
      for (Object obj : list)
      {
         Cruise cruise = (Cruise)obj;
         System.out.println(cruise.getId() + "   " + cruise.getName( ));
      }
   }

   public void cabins(String command)
   {
      StringTokenizer tokens = new StringTokenizer(command);
      tokens.nextToken( );
      int cruiseId = Integer.parseInt(tokens.nextToken().trim( ));

      System.out.println( );
      List list = access.getCabins(cruiseId);
      for (Object obj : list)
      {
         Cabin cabin = (Cabin)obj;
         System.out.println(cabin.getId() + "   " + cabin.getName( ));
      }
      System.out.println( );
   }

The cruises( ) and cabins( ) methods interact with DataAccessBean to list possible cruises and cabins the travel agent might want to book. The cabins( ) method requires an integer parameter for the Cruise's primary key so that it can query all cabins that belong to that cruise:

   private TravelAgentRemote agent;
   private DataAccess access;

   private TravelAgentRemote getAgent( )
   {
      try
      {
         if (agent == null) agent =
(TravelAgentRemote)getInitialContext( ).lookup("TravelAgentBean/remote");
      }
      catch (Exception ex){ throw new RuntimeException(ex);}
      return agent;
   }

   public void customer(String command)
   {
      StringTokenizer tokens = new StringTokenizer(command);
      tokens.nextToken( );
      String first = tokens.nextToken().trim( );
      String last = tokens.nextToken().trim( );
      getAgent( ).findOrCreateCustomer(first, last);
      System.out.println("set customer: " + first + " " + last);
   }

The customer( ) method finds or creates an existing customer with a given first and last name. It obtains a travelAgentBean session by calling the getAgent( ) method. The getAgent( ) method determines if the shell program has already created an existing TravelAgent EJB session, and if not, it does a JNDI lookup to create one. Once the customer( ) method has the TravelAgent EJB reference, it invokes the findOrCreateCustomer( ) method to initialize the session's customer:

   public void cruise(String command)
   {
      StringTokenizer tokens = new StringTokenizer(command);
      tokens.nextToken( );
      String id = tokens.nextToken().trim( );
      int cruiseId = Integer.parseInt(id);
      getAgent( ).setCruiseID(cruiseId);
      System.out.println("set cruise: " + id);
   }

   public void cabin(String command)
   {
      StringTokenizer tokens = new StringTokenizer(command);
      tokens.nextToken( );
      String id = tokens.nextToken().trim( );
      int cabinId = Integer.parseInt(id);
      getAgent( ).setCabinID(cabinId);
      System.out.println("set cabin");
   }

The cruise( ) and cabin( ) methods set the cruise and cabin for which the TravelAgent EJB will book a reservation. They expect an integer ID of the Cabin and Cruise entities:

   public void book(String command)
   {
      StringTokenizer tokens = new StringTokenizer(command);
      tokens.nextToken( );
      String number = tokens.nextToken().trim( );
      String exp = tokens.nextToken().trim( );
      String dollars = tokens.nextToken().trim( );

The book( ) method is responsible for booking the reservation. It takes a credit card number, an expiration date, and the dollar amount of the purchase:

      Date expDate = null;
      try
      {
         expDate = DateFormat.getDateInstance(DateFormat.SHORT).parse(exp);
      }
      catch (ParseException ex)
      {
         System.out.println("Illegal date format for expiration date! Format
is MM/DD/YY");
         return;
      }

The expiration date is parsed from the command line by the java.text.DateFormat class, which throws an exception if an illegal date is entered.

      if (expDate.before(new java.util.Date( )))
      {
         System.out.println("Credit Card expired: " + expDate.toString( )
                     + " today: " + (new java.util.Date()).toString( ));
         return;
      }
      double amount = Double.parseDouble(dollars);

      String type = "";
      // bet you didn't know that first digit determines type?
      if (number.startsWith("5")) type = CreditCardDO.MASTER_CARD;
      else if (number.startsWith("4")) type = CreditCardDO.VISA;
      else if (number.startsWith("3")) type = CreditCardDO.AMERICAN_EXPRESS;
      else type = "UNKNOWN";

The code checks to see if the expiration date has passed and converts the command-line dollar amount from a string to a numeric value. The credit card type can be determined from the credit card number. This is an industry standard.

      CreditCardDO card = new CreditCardDO(number, expDate, type);
      try
      {
         TicketDO ticket = getAgent( ).bookPassage(card, amount);
         System.out.println(ticket.toString( ));
         System.out.println( );
      }
      catch (IncompleteConversationalState ex)
      {
         System.out.println("You have not set either customer, cruise, or cabin yet.");
      }

The code books the reservation by calling the travelAgentRemote.bookPassage( ) method. Because this bean class method is annotated with @javax.ejb.Remove , the session is destroyed when the method completes.

      // agent was removed
      agent = null;
   }

Finally, the code nulls out the agent member variable because the stateful TravelAgent EJB session is no longer valid.

Run the Application

Run the TravelAgentShell application by invoking Shell.bat or the Shell.sh script at the command prompt. Remember to set your JBOSS_HOME and PATH environment variables. The program starts up with the following prompt:

C:\jboss\oreilly-ejb3\workbook\ex11_4>shell

********************
    Titan Cruises
********************


>

Type help to see the list of commands available from the Titan Cruises reservation system:

> help

Titan Cruises Commands

cruises - list all cruises
cabins {cruiseId} - list all cabins
customer {first} {last} - find or create a customer
cabin {id} - set cabin
cruise {id} - set cruise
book {credit, MM/DD/YY, amount} - book a cruise

>

Type cruises to see the list of cruises in the database. This will output the primary key first, and then the name of the cruise. You will need the primary key when booking a reservation.

> cruises

1   Alaskan Cruise
2   Atlantic Cruise

Type cabins followed by the primary key of a cruise. This will list all the cabins available for that cruise.

> cabins 1

1   Queen Cabin 1
2   Queen Cabin 2

Type customer followed by any first and last name. This will locate a customer in the database or create one for you by interacting with a TravelAgent EJB session.

> customer Bill Burke
set customer: Bill Burke

Execute the cruise command, passing in the primary key of the cruise for which you want to make a reservation:

> cruise 1
set cruise: 1

Execute the cabin command, passing in the primary key of the cabin you want to reserve for that cruise:

> cabin 1
set cabin

Finally, book a reservation by executing the book command. You need to specify a credit card number (any number beginning with a 3, 4, or 5), as well as an expiration date and the amount of the purchase.

> book 4444444444444444 11/1/06 550.0
Bill Burke has been booked for the Alaskan Cruise cruise on ship Queen Mary.
 Your accommodations include Queen Cabin 1 a 1 bed cabin on deck level 1.
 Total charge = 550.0

That's it. You have booked a reservation. You can continue to book more reservations if you desire.