Chapter 28. Exercises for Chapter 11

Exercise 11.1: A Stateless Session Bean

In this exercise, you will build and examine a stateless session bean, ProcessPaymentEJB, which writes payment information to the database. You will also build a client application to test this ProcessPayment bean.

The bean inserts the payment information data directly into the database, without using an intermediary entity bean.

Examine the EJB

This example is based on the Customer and Address EJBs and their related data objects that you used in Exercise 6.3. The present exercise leaves these EJBs unchanged, and focuses on the ProcessPayment stateless session bean.

The ProcessPayment bean has a very simple remote interface. It offers options to process a payment by check, cash, or credit card. Each possibility is handled by a different method.

ProcessPaymentRemote.java

public interface ProcessPaymentRemote extends javax.ejb.EJBObject
{
   public boolean byCheck (CustomerRemote customer, 
                           CheckDO        check, 
                           double         amount)
   throws RemoteException, PaymentException;
   
   public boolean byCash (CustomerRemote customer, 
                          double         amount)
   throws RemoteException, PaymentException;
   
   public boolean byCredit (CustomerRemote customer, 
                            CreditCardDO   card, 
                            double         amount)
   throws RemoteException, PaymentException;
   ...
}

Each method’s third parameter is a simple transaction amount. The other two are more interesting.

The first is a CustomerRemote interface, which enables the ProcessPayment EJB to get any information it needs about the customer.

Tip

It’s possible to use EJB remote interfaces as parameters of other EJB methods because they extend EJBObject, which in turn extends java.rmi.Remote. Objects implementing either Remote or Serializable are perfectly valid RMI types. This choice of parameter type makes no difference at all to the EJB container.

The second parameter conveys the details of the transaction in a data object with a type that reflects the form of payment. A data object is a Serializable object that a client and a remote server can pass by value back and forth. Most of the time it is a simple data container, with minimal behavior. For example, the CheckDO class contains the check’s number and bar code.

CheckDO.java

public class CheckDO implements java.io.Serializable
{  
   public String checkBarCode;
                     public int checkNumber;
       
   public CheckDO (String barCode, int number)
   {
      this.checkBarCode = barCode;
      this.checkNumber = number;
   }

Focus on the ProcessPayment EJB implementation for a little while. Each remote method first performs validity tests appropriate to the type of payment. Eventually all of them call the same private method: process( ), which inserts the payment information into the database. For example, byCredit( ) implements this logic as shown.

ProcessPaymentBean.java

public boolean byCredit (CustomerRemote customer,
                         CreditCardDO   card,
                         double         amount)
throws PaymentException
{
   if (card.expiration.before (new java.util.Date ( )))
   {
      throw new PaymentException ("Expiration date has passed");
   }
   else
   {
      return
         process (getCustomerID (customer), 
                  amount,
                  CREDIT,
                  null, 
                  -1, 
                  card.number, 
                  new java.sql.Date (card.expiration.getTime ( )));
   }
}

If the credit card has expired, the method throws an application exception. If not, it simply delegates the chore of inserting the payment information into the database to process( ). Note that some parameters passed to process( ) are meaningless. For example, the fourth parameter represents the check bar code, which means nothing in a credit card payment, so byCredit( ) passes a dummy value.

The process( ) method is very similar to the ejbCreate( ) method of the BMP example in Chapter 10. It simply gets a data-source connection, creates a PreparedStatement, and inserts the payment information into the PAYMENT table:

   ...
   con = getConnection ( );

   ps = con.prepareStatement
      ("INSERT INTO payment (customer_id, amount, " + 
       "type, check_bar_code, " + 
       "check_number, credit_number, " + 
       "credit_exp_date) "+
       "VALUES (?,?,?,?,?,?,?)");
   ps.setInt (1,customerID.intValue ( ));
   ps.setDouble (2,amount);
   ps.setString (3,type);
   ps.setString (4,checkBarCode);
   ps.setInt (5,checkNumber);
   ps.setString (6,creditNumber);
   ps.setDate (7,creditExpDate);

   int retVal = ps.executeUpdate ( );
   if (retVal!=1)
   {
      throw new EJBException ("Payment insert failed");
   }

   return true;
   ...

Note that the returned value is not significant. The method either returns true or throws an application exception, so its return type could as easily be void.

Examine the EJB Standard Deployment Descriptor

The ProcessPayment standard deployment descriptor is very similar to one you’ve already seen.

ejb-jar.xml

...
<session>
  <description>
    A service that handles monetary payments
  </description>
  <ejb-name>ProcessPaymentEJB</ejb-name>
  <home>com.titan.processpayment.ProcessPaymentHomeRemote</home>
  <remote>com.titan.processpayment.ProcessPaymentRemote</remote>
  <ejb-class>com.titan.processpayment.ProcessPaymentBean</ejb-class>
  <session-type>Stateless</session-type>
                     <transaction-type>Container</transaction-type>
  <env-entry>
    <env-entry-name>minCheckNumber</env-entry-name>
    <env-entry-type>java.lang.Integer</env-entry-type>
    <env-entry-value>2000</env-entry-value>
  </env-entry>
  <resource-ref>
    <description>DataSource for the Titan database</description>
    <res-ref-name>jdbc/titanDB</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>
</session>
...

Note that the ProcessPaymentEJB’s <session-type> tag is set to Stateless and its <transaction-type> tag is set to Container. These settings ensure that the container will automatically manage the transactions and enlist any transactional resources the bean uses. Chapter 16 of the EJB section of this book explains how these tasks can be handled by the EJB itself (if it’s a session bean or a message-driven bean).

The descriptor contains a reference to a data source it will use to store the payments. You use this data source the same way you did in the BMP example in Chapter 10.

ProcessPaymentBean.java

private Connection getConnection ( ) throws SQLException
{
   try
   {
      InitialContext jndiCntx = new InitialContext ( );
      
      DataSource ds = (DataSource)
                     jndiCntx.lookup ("java:comp/env/jdbc/titanDB");
      
                     return ds.getConnection ( );
   } 
   catch(NamingException ne)
   {
      throw new EJBException (ne);
   }
}

The ejb-jar.xml file also specifies an environment property, minCheckNumber. Environment properties provide a very flexible way to parameterize a bean’s behavior at deployment time. The <env-entry> tag for minCheckNumber specifies the property’s type (java.lang.Integer) and a default value (2000). The ProcessPayment EJB accesses the value of this property through its JNDI ENC.

ProcessPaymentBean.java

...
InitialContext jndiCntx = new InitialContext ( );

Integer value = (Integer) jndiCntx.lookup 
                          ("java:comp/env/minCheckNumber");
...

One very interesting point to note is that although the ProcessPayment bean works with Customer beans (recall that each remote method’s first parameter is a Customer interface), the deployment descriptor doesn’t declare any reference to the Customer EJB. No <ejb-ref> or <ejb-local-ref> tag is needed because the ProcessPayment bean won’t find or create Customer beans through the CustomerRemoteHome interface, but instead receives Customer beans directly from the client application. Thus, from the ProcessPayment EJB’s point of view, the Customer is a standard remote Java object.

Examine the JBoss Deployment Descriptors

The JBoss-specific deployment descriptor for the ProcessPayment bean is very simple. It only maps the data source to the embedded database in Jboss.

jboss.xml

<session>
   <ejb-name>ProcessPaymentEJB</ejb-name>
   <jndi-name>ProcessPaymentHomeRemote</jndi-name>
   <resource-ref>
      <res-ref-name>jdbc/titanDB</res-ref-name>
      <jndi-name>java:/DefaultDS</jndi-name>
   </resource-ref>
</session>

The <res-ref-name> in jboss.xml maps to the same <res-ref-name> in ejb-jar.xml.

Start Up JBoss

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

Build and Deploy the Example Programs

Perform the following steps:

  1. Open a command prompt or shell terminal and change to the ex11_1 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:workbookex11_1> set JAVA_HOME=C:jdk1.4.2 C:workbookex11_1> 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:workbookex11_1> 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.

Initialize the Database

As in previous examples, you’ll use the relational database that’s embedded in JBoss to store payment information. Because the deployment descriptor of a stateless session bean does not contain any information about the database schema that the bean needs, JBoss can’t automatically create the database table, as it does for CMP beans. Instead, you will have to create the database schema for the PAYMENT table manually through JDBC. Use the createdb Ant target:

C:workbookex11_1>ant createdb
Buildfile: build.xml

prepare:

compile:

ejbjar:

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

On the JBoss console, you’ll see:

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

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

A dropdb Ant target has been added as well, if you want to destroy the PAYMENT table:

C:workbookex11_1>ant dropdb
Buildfile: build.xml

prepare:

compile:

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

BUILD SUCCESSFUL

To implement the createdb and dropdb Ant targets, the JBoss version of the ProcessPayment bean introduced in the EJB book defines two new methods: makeDbTable( ) and dropDbTable( ).

Here’s a partial view of the ProcessPayment EJB’s remote interface:

public interface ProcessPaymentRemote extends javax.ejb.EJBObject 
{
    
    public void makeDbTable ( ) throws RemoteException;
    public void deleteDbTable ( ) throws RemoteException;
}

It defines two home methods: the first creates the table needed by the ProcessPayment EJB in the JBoss embedded database, and the second drops it.

The implementation of makeDbTable( ) is essentially a CREATE TABLE SQL statement:

public void makeDbTable ( ) 
{
   PreparedStatement ps = null;
   Connection con = null;
   
   try
   {
      con = this.getConnection ( );
      System.out.println("Creating table PAYMENT...");
      ps = con.prepareStatement 
         ("CREATE TABLE PAYMENT ( " + "CUSTOMER_ID INT, " +
          "AMOUNT DECIMAL (8,2), " + "TYPE CHAR (10), " +
          "CHECK_BAR_CODE CHAR (50), " + "CHECK_NUMBER INTEGER, " +
          "CREDIT_NUMBER CHAR (20), " + "CREDIT_EXP_DATE DATE" +
          ")" );
      ps.execute ( );
      System.out.println("...done!");
   }
   catch (SQLException sql)
   {
      throw new EJBException (sql);
   }
   finally
   {
      try { ps.close ( ); } catch (Exception e) {}
      try { con.close ( ); } catch (Exception e) {}
   }
}

The deleteDbTable( ) home method differs only in the SQL statement it executes:

public void dropDbTable ( )
{
      ...
      System.out.println("Dropping table PAYMENT...");
      ps = con.prepareStatement ("DROP TABLE PAYMENT");
      ps.execute ( );
      System.out.println("...done!");
      ...
}

Examine the Client Applications

This exercise includes two example clients. The first simply prepares and creates a single Customer bean, which the second uses to insert data into the PAYMENT table.

Client_111a

Run the first application by invoking the run.client_111a Ant target:

C:workbookex11_1>ant run.client_111a
Buildfile: build.xml

prepare:

compile:

ejbjar:

run.client_111a:
     [java] Creating Customer 1..
     [java] Creating AddressDO data object..
     [java] Setting Address in Customer 1...
     [java] Acquiring Address data object from Customer 1...
     [java] Customer 1 Address data:
     [java] 1010 Colorado
     [java] Austin,TX 78701

Client_111b

The code of the client application that actually tests the PaymentProcess EJB is much more interesting. First, it acquires a reference to the remote home of the ProcessPayment EJB from a newly created JNDI context:

Context jndiContext = getInitialContext ( );

System.out.println ("Looking up home interfaces..");
Object ref = jndiContext.lookup ("ProcessPaymentHomeRemote");

ProcessPaymentHomeRemote procpayhome = (ProcessPaymentHomeRemote)
PortableRemoteObject.narrow (ref,ProcessPaymentHomeRemote.class);

This home makes it possible to create a remote reference to the stateless session bean:

ProcessPaymentRemote procpay = procpayhome.create ( );

Then the client acquires a remote home reference for the Customer EJB and uses it to find the Customer bean created in the preceding example:

ref = jndiContext.lookup ("CustomerHomeRemote");
CustomerHomeRemote custhome = (CustomerHomeRemote)
PortableRemoteObject.narrow (ref,CustomerHomeRemote.class);

CustomerRemote cust = custhome.findByPrimaryKey (new Integer (1));

The ProcessPayment EJB can now be tested by executing payments of all three kinds: cash, check, and credit card.

System.out.println ("Making a payment using byCash( )..");
procpay.byCash (cust,1000.0);

System.out.println ("Making a payment using byCheck( )..");
CheckDO check = new CheckDO ("010010101101010100011", 3001);
procpay.byCheck (cust,check,2000.0);

System.out.println ("Making a payment using byCredit( )..");
Calendar expdate = Calendar.getInstance ( );
expdate.set (2005,1,28); // month=1 is February
CreditCardDO credit = new CreditCardDO ("370000000000002", 
                                        expdate.getTime ( ), 
                                        "AMERICAN_EXPRESS");

procpay.byCredit (cust,credit,3000.0);

Finally, to check the validation logic, the client tries to execute a payment with a check whose number is too low. The ProcessPayment EJB should refuse the payment and raise an application exception.

System.out.println ("Making a payment using byCheck( ) with a low 
                     check number..");
CheckDO check2 = new CheckDO ("111000100111010110101", 1001);
try
{
   procpay.byCheck (cust,check2,9000.0);
   System.out.println("Problem! The PaymentException has 
                       not been raised!"); }
catch (PaymentException pe)
{
   System.out.println ("Caught PaymentException: "+
                       pe.getMessage ( ));
}

procpay.remove ( );

You can launch this test by invoking the run.client_111b Ant target:

C:workbookex11_1>ant run.client_111b
Buildfile: build.xml

prepare:

compile:

ejbjar:

run.client_111b:
     [java] Looking up home interfaces..
     [java] Making a payment using byCash( )..
     [java] Making a payment using byCheck( )..
     [java] Making a payment using byCredit( )..
     [java] Making a payment using byCheck( ) with a low check number..
     [java] Caught PaymentException: Check number is too low. Must be at least 2000

At the same time, the JBoss console will display:

INFO  [STDOUT] process( ) with customerID=1 amount=1000.0
INFO  [STDOUT] process( ) with customerID=1 amount=2000.0
INFO  [STDOUT] process( ) with customerID=1 amount=3000.0

Once you’ve performed the tests, you can drop the table by invoking the dropdb Ant target:

C:workbookex11_1>ant dropdb
Buildfile: build.xml

prepare:

compile:

ejbjar:

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

The JBoss console displays:

INFO  [STDOUT] Dropping table PAYMENT...
INFO  [STDOUT] ...done!
..................Content has been hidden....................

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