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.
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.
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.
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.
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.
public boolean byCredit (CustomerRemote customer, CreditCardDO card, double amount) throws PaymentException { if (card.expiration.before (new java.util.Date ( ))
) { throw newPaymentException ("Expiration date has passed");
} else { returnprocess
(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
.
The ProcessPayment standard deployment descriptor is very similar to one you’ve already seen.
...
<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.
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.
... 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.
The JBoss-specific deployment descriptor for the ProcessPayment bean is very simple. It only maps the data source to the embedded database in Jboss.
Perform the following steps:
Open a command prompt or shell terminal and change to the
ex11_1
directory created by the extraction
process.
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
|
Add ant
to your execution path.
Windows:C:workbookex11_1> set PATH=..antin;%PATH%
|
Unix:$ export PATH=../ant/bin:$PATH
|
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.
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 voidmakeDbTable
( ) throws RemoteException; public voiddeleteDbTable
( ) 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!");
...
}
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.
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
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!