The Stateless Session Bean






The Stateless Session Bean

A stateless session bean is very efficient and relatively easy to develop. A session bean can be swapped freely between EJB objects because it isn't dedicated to one client and doesn't maintain any conversational state. As soon as it is finished servicing a method invocation it can be swapped to another EJB object. Because it does not maintain conversational state, a stateless session bean does not require passivation or activation, further reducing the overhead of swapping. In short, stateless session beans are lightweight and fast.

Saying that a stateless session bean doesn't maintain any conversational state means that every method invocation is independent of previous invocations and everything the method needs to know has to be passed via the method's parameters. Since stateless session beans can't remember anything from one method invocation to the next, they must take care of an entire task in one method invocation. The only exception to this rule is information obtainable from the SessionContext and the JNDI ENC, or environment references that are injected directly into the bean (we'll talk later about dependency injection). Stateless session beans are EJB's version of the traditional transaction-processing applications, which are executed using a procedure call. The procedure executes from beginning to end and then returns the result. Once the procedure finishes, nothing about the data that was manipulated or the details of the request is remembered.

These restrictions don't mean that a stateless session bean can't have instance variables or maintain any kind of internal state. Nothing prevents you from keeping a variable that tracks the number of times a bean has been called or that saves data for debugging. An instance variable can even hold a reference to a live resource, such as a URL connection for logging, verifying credit cards through a different EJB, or anything else that might be usefulthe resource should be obtained from the JNDI ENC or be injected into fields directly using EJB's injection features. However, it is important to remember that this state can never be visible to a client. A client can't assume that the same bean instance will service all of its requests. Instance variables may have different values in different bean instances, so their values can appear to change randomly as stateless session beans are swapped from one client to another. Therefore, anything you reference in instance variables should be generic. For example, each bean instance might reasonably record debugging messagesthat might be the only way to figure out what is happening on a large server with many bean instances. The client doesn't know or care where debugging output is going. However, it would clearly be inappropriate for a stateless bean to remember that it was in the process of making a reservation for Madame Xthe next time it is called, it may be servicing another client entirely.

Stateless session beans can be used for report generation, batch processing, or some stateless services such as validating credit cards. Another good application might be a StockQuote EJB that returns a stock's current price. Any activity that can be accomplished in one method call is a good candidate for the high-performance stateless session bean.

The ProcessPayment EJB

Chapters 2 and 3 discussed the TravelAgent EJB, which has a business method called bookPassage( ) that uses the ProcessPayment EJB. The next section develops a complete definition of the TravelAgent EJB, including the logic of the bookPassage( ) method. At this point, however, we are primarily interested in the ProcessPayment EJB, which is a stateless bean the TravelAgent EJB uses to charge the customer for the price of the cruise. Charging customers is a common activity in Titan's business systems. Not only does the reservation system need to charge customers, so do Titan's gift shops, boutiques, and other related businesses. Because many different systems charge customers for services, we've encapsulated the logic for charging customers in its own bean.

Payments are recorded in a special database table called PAYMENT. The PAYMENT data is batch-processed for accounting purposes and is not normally used outside of accounting. In other words, the data is only inserted by Titan's system; it is not read, updated, or deleted. Because the process of making a charge can be completed in one method, and because the data is not updated frequently and is not shared, we will use a stateless session bean for processing payments. Several different forms of payment can be used: credit card, check, and cash. We will model these payment forms in our stateless ProcessPayment EJB.

The database table: PAYMENT

The ProcessPayment EJB accesses an existing table in Titan's system, called the PAYMENT table. In your database, create a table called PAYMENT, with this definition:

CREATE TABLE PAYMENT
(
    customer_id     INTEGER,
    amount          DECIMAL(8,2),
    type            CHAR(10),
    check_bar_code  CHAR(50),
    check_number    INTEGER,
    credit_number   CHAR(20),
    credit_exp_date DATE
)

The business interface: ProcessPayment

A stateless session bean has one or more business interfaces. The business interface for ProcessPayment obviously needs a byCredit( ) method because the TravelAgent EJB uses it. We can also identify two other methods that we'll need: byCash( ) for customers paying cash and byCheck( ) for customers paying with a personal check.

A business interface can be a remote or local interface, but not both. Remote business interfaces are able to receive method invocations from networked clients. When a client invokes a method on a session bean's remote interface, the parameter values and return value are copied. This is true irregardless of whether the client is running in the same VM or on another machine in the network. This is known as call-by-value semantics.

Local interfaces are available only within the same JVM as the session bean. Invoking on a local interface does not copy the parameters or return value. Because of this, local interfaces are said to follow what is termed call-by-reference semantics.

Since the TravelAgent EJB will be in the same deployment, the ProcessPayment EJB should provide a local interface so that invocations on ProcessPayment are efficient. The ProcessPayment EJB also has remote clients, so we'll provide a remote business interface as well.

The local and remote interfaces will publish the same API. To make our design a little bit cleaner, we will have these interfaces extend a base interface called com.titan.processpayment.ProcessPayment :

package com.titan.processpayment;
import com.titan.domain.*;

public interface ProcessPayment {
    public boolean byCheck(Customer customer, CheckDO check, double amount)
        throws PaymentException; 

    public boolean byCash(Customer customer, double amount)
        throws PaymentException;

    public boolean byCredit(Customer customer, CreditCardDO card,
        double amount) throws PaymentException;
}

The EJB specification allows you to define a common base class for your remote and local interfaces if they share the same methods. Three business methods have been defined: byCheck( ), byCash( ), and byCredit( ), which take information relevant to the form of payment used and return a boolean value that indicates whether the payment succeeded. These methods can throw application-specific exceptions, like PaymentException. PaymentException is thrown if any problems occur while processing the payment, such as a low check number or an expired credit card. Notice, however, that nothing about the ProcessPayment interface is specific to the reservation system. It could be used just about anywhere in Titan's system. In addition, each method defined in the base business interface is completely independent of the others. All the data that is required to process a payment is obtained through the method's arguments. Next, let's specify both the remote and local interfaces:

package com.titan.processpayment;

import javax.ejb.Remote;

@Remote
public interface ProcessPaymentRemote 
 extends
ProcessPayment 
 {
}

package com.titan.processpayment;

import javax.ejb.Local;

@Local
public interface ProcessPaymentLocal 
 extends ProcessPayment{
}

The ProcessPaymentRemote and ProcessPaymentLocal interfaces extend the base ProcessPayment interface so that they do not have to duplicate the method definitions. ProcessPaymentRemote is identified as a remote interface by the @javax.ejb.Remote annotation and ProcessPaymentLocal by the @javax.ejb.Local annotation.

Entities as parameters

Each method of the ProcessPayment EJB's business interface takes a Customer entity bean as a parameter. Because entity beans are plain Java objects, they can be serialized across the network as plain Java objects as long as they implement java.io.Serializable or Externalizable . This is important because the ProcessPayment EJB accesses the internal state of the Customer entity. If every call to the Customer's get methods went over a remote interface (as is required in the EJB 2.1 specification), the ProcessPayment EJB would be very inefficient, because network calls are expensive. This is yet another example of the simplicity of the EJB 3.0 specification. In EJB 2.1, because entities were first-class components, one had to write parallel value object classes if one wanted to pass around the state of an entity instead of accessing it through the old remote interface model.

Domain objects: the CreditCardDO and CheckDO classes

The ProcessPayment EJB's business interface uses two classes that are particularly interesting, CreditCardDO and CheckDO:

/* CreditCardDO.java */
package com.titan.processpayment;

import java.util.Date;

public class CreditCardDO implements java.io.Serializable {
    final static public String MASTER_CARD = "MASTER_CARD";
    final static public String VISA = "VISA";
    final static public String AMERICAN_EXPRESS = "AMERICAN_EXPRESS";
    final static public String DISCOVER = "DISCOVER";
    final static public String DINERS_CARD = "DINERS_CLUB_CARD";

    public String number;
    public Date expiration;
    public String type;

 public CreditCardDO 
(String nmbr, Date exp, String typ) {
        number = nmbr;
        expiration = exp;
        type = typ;
    }
}

/* CheckDO.java 
 */
package com.titan.processpayment;

public class CheckDO implements java.io.Serializable {
    public String checkBarCode;
    public int checkNumber;

    public CheckDO(String barCode, int number) {
        checkBarCode = barCode;
        checkNumber = number;
    }
}

CreditCardDO and CheckDO are domain objects . They are simply serializable Java classes, not enterprise beans; they provide a convenient mechanism for transporting related data. CreditCardDO, for example, collects all the credit card data together in one class, making it easier to pass the information across the network as well as making our interfaces a little cleaner.

An application exception: PaymentException

Any remote or local interface can throw application exceptions. Application exceptions should describe a business logic problemin this case, a problem making a payment. Application exceptions should be meaningful to the client, providing a brief and relevant identification of the error.

It is important to understand what exceptions you should use, and when you should use them. Exceptions such as javax.naming.NamingException and java.sql.SQLException are thrown by other Java subsystems and have nothing to do with the business process an EJB is supposed to be modeling. These types of Java subsystem exceptions expose the implementation of your session bean. Another thing (which you will learn in Chapter 16) is that checked exceptions (non-RuntimeException s), by default, do not cause a transaction rollback. Instead of throwing these types of exceptions directly, you should catch them in a try/catch block and throw a more appropriate exception. A common practice is to wrap these checked exceptions in a javax.ejb.EJBException , as these types of subsystem errors are unrecoverable.

An EJBException indicates that the container ran into problems processing a business interface invocation. EJBException is unchecked (it extends java.lang.RuntimeException ), so you won't get a compile error if you don't catch it. However, under certain circumstances, it is a good idea to catch EJBException, and in other circumstances, it should be propagated.

A PaymentException describes a specific business problem that is possibly recoverable. This makes it an application exception. The EJB container treats any exception that does not extend RuntimeException as an application exception. Here is the definition of PaymentException:

package com.titan.processpayment;

public class PaymentException extends java.lang.Exception {
    public PaymentException( ) {
        super( );
    }
    public PaymentException(String msg) {
        super(msg);
    }
}

An application exception is propagated to the calling client as is. Any instance variables you include in these exceptions should be serializable. Nonapplication exceptions are always wrapped in an EJBException . This means that any exception you throw that is or extends RuntimeException will be caught by the EJB container and wrapped in an EJBException. This is especially important for session beans that interact with entity beans. All exceptions thrown by Java Persistence interfaces are RuntimeExceptions. Your client code must be aware of this if it needs to take action on specific persistence exceptions. Exception behavior can be declared explicitly using the @javax.ejb.ApplicationException and the <application-exception> XML deployment descriptor metadata. These constructs are discussed in detail in Chapter 16, as they have a huge impact on transactional behavior.

The bean class: ProcessPaymentBean

The ProcessPayment EJB models a specific business process, so it is an excellent candidate for a stateless session bean. This bean really represents a set of independent operationsanother indication that it is a good candidate for a stateless session bean. Here is the definition of the ProcessPaymentBean class:

package com.titan.processpayment;
import com.titan.domain.*;

import java.sql.*;
import javax.ejb.*;
import javax.annotation.Resource;

import javax.sql.DataSource;
import javax.ejb.EJBException;

@Stateless
public class ProcessPaymentBean
        implements ProcessPaymentRemote, ProcessPaymentLocal {

final public static String CASH = "CASH";
    final public static String CREDIT = "CREDIT";
    final public static String CHECK = "CHECK";

    @Resource(mappedName="titanDB") DataSource dataSource;

    @Resource(name="min") int minCheckNumber;

    public boolean byCash(Customer customer, double amount)
        throws PaymentException {
        return process(customer.getId( ), amount, CASH, null, -1, null, null);
    }

    public boolean byCheck(Customer customer, CheckDO check, double amount)
        throws PaymentException {
        if (check.checkNumber > minCheckNumber) {
            return process(customer.getId( ), amount, CHECK,
                check.checkBarCode, check.checkNumber, null, null);
        }
        else {
            throw new PaymentException("Check number is too low.
                Must be at least "+minCheckNumber);
        }
    }
    public boolean byCredit(Customer 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(customer.getId( ), amount, CREDIT, null,
            -1, card.number, new java.sql.Date(card.expiration.getTime( )));
        }
    }
    private boolean process(int customerID, double amount, String type,
        String checkBarCode, int checkNumber, String creditNumber,
        java.sql.Date creditExpDate) throws PaymentException {

        Connection con = null;

        PreparedStatement ps = null;

        try {
            con = dataSource.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);
            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;
        } catch(SQLException sql) {
            throw new EJBException(sql);
        } finally {
            try {
                if (ps != null) ps.close( );
                if (con!= null) con.close( );
            } catch(SQLException se) {
                se.printStackTrace( );
            }
        }
    }
}

The bean class is annotated with the @javax.ejb.Stateless annotation to identify that it is a stateless session bean:

Package javax.ejb;

@Target(TYPE) @Retention(RUNTIME)
public @interface Stateless {
   String name( ) default "";
}

The name( ) attribute identifies the EJB name of the session bean. The EJB name defaults to the unqualified name of the bean class if you initialize this attribute. For ProcessPayment EJBs, the EJB name would default to ProcessPaymentBean . In most cases, you don't have to be aware of the concept of an EJB name. It is useful when you want to override or augment metadata with an XML deployment descriptor.

The bean class identifies its remote and local interfaces by implementing the ProcessPaymentRemote and ProcessPaymentLocal interfaces. When an EJB is deployed, the container looks at the interfaces of the bean class to see if they are annotated with @javax.ejb.Local or @javax.ejb.Remote . This introspection determines the remote and local interfaces of the bean class. Alternatively, the bean class does not have to implement any interfaces, and the @Local and @Remote annotations can be applied directly on the bean class.

@Stateless
@Local(ProcessPaymentLocal.class)
@Remote(ProcessPaymentRemote.class)
public class ProcessPaymentBean {

    final public static String CASH = "CASH";
    final public static String CREDIT = "CREDIT";
    final public static String CHECK = "CHECK";

When used on the bean class, the @Local and @Remote annotations take an array of interface classes. It is not recommended that you use this approach unless you have to, as implementing your business interfaces directly enforces the contract between the bean class and these interfaces.

The three payment methods use the private helper method process( ), which does the work of adding the payment to the database. This strategy reduces the possibility of programmer error and makes the bean easier to maintain. The process( ) method does not use entity beans but simply inserts the payment information directly into the PAYMENT table using JDBC. The JDBC connection is obtained from the datasource field of the bean class.

The byCheck( ) and byCredit( ) methods contain some logic to validate the data before processing it. byCredit( ) verifies that the credit card's expiration date does not precede the current date. If it does, a PaymentException is thrown. byCheck( ) verifies that the serial number of the check is higher than a certain minimum. This is determined by the minCheckNumber field. If the check number is lower than this value, a PaymentException is thrown.

Accessing environment properties (injection)

The datasource and minCheckNumber fields are examples of session bean fields that are initialized by the EJB's environment. Each EJB container has its own internal registry where it stores configuration values and references to external resources and services. This registry is called the Enterprise Naming Context (ENC). If you look at the declaration of these member variables, you will see that they are annotated with @javax.annotation.Resource . This tells the EJB container that when an instance of the bean class is instantiated, those fields must be initialized with values in the container's ENC.

When the EJB container is deployed, the ENC is populated with metadata embedded in annotations like @Resource , and with information stored in any EJB XML deployment descriptor. For example, the @Resource annotation that tags the datasource field contains a mappedName( ) attribute which identifies the external JDBC data source that should be mapped into the ENC. For minCheckNumber, the @Resource annotation identifies a named value within the ENC that may be used to initialize the field externally. This named value can be configured using the EJB's XML deployment descriptor:

<ejb-jar
       xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                           http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
       version="3.0">
   <enterprise-beans>
      <session>
        <ejb-name>ProcessPaymentBean</ejb-name>
         <env-entry>
            <env-entry-name>min 
</env-entry-name>
            <env-entry-type>java.lang.Integer</env-entry-type>
            <env-entry-value>250</env-entry-value>
         </env-entry>
      </session>
   </enterprise-beans>
</ejb-jar>

The XML populates the EJB's ENC under the name min with a value of 250. It is a good idea to capture thresholds and other limits in the bean's environment properties instead of hardcoding them: it gives you greater flexibility. If, for example, Titan decided to raise the minimum check number, you would need to change only the bean's XML deployment descriptor, not the class definition. The exact semantics of the <env-entry> and @Resource annotation are discussed in detail in Chapter 14.

The XML Deployment Descriptor

EJB has an optional XML deployment descriptor defined in the META-INF/ejb-jar.xml file of the EJB's JAR file. You can use this descriptor as an alternative to annotations, to augment metadata that is not declared as an annotation, or to override an annotation. The choice is up to you. Here is a deployment descriptor that provides a complete annotation-alternative definition of the ProcessPayment EJB:

<?xml version="1.0"?>
<ejb-jar
       xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                           http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
       version="3.0">
   <enterprise-beans>
      <session>
         <ejb-name>ProcessPaymentBean</ejb-name>
         <remote>com.titan.processpayment.ProcessPaymentRemote</remote>
         <local>com.titan.processpayment.ProcessPaymentLocal</local>
         <ejb-class>com.titan.processpayment.ProcessPaymentBean</ejb-class>
         <session-type>Stateless</session-type>
         <resource-ref>
             <res-ref-name>theDatasource</res-ref-name>
             <res-type>javax.sql.DataSource</res-type>
             <res-auth>Container</res-auth>
             <mapped-name>titandb</mapped-name>
             <injection-target>
                <injection-target-class>
                   com.titan.processpayment.ProcessPaymentBean
                </injection-target-class>
                <injection-target-name>dataSource</injection-target-name>
             </injection-target>
          </resource-ref>
            <env-entry>
             <env-entry-name>min</env-entry-name>
             <env-entry-type>java.lang.Integer</env-entry-type>
             <env-entry-value>250</env-entry-value>
             <injection-target>
                <injection-target-class>
                   com.titan.processpayment.ProcessPaymentBean
                </injection-target-class>
                <injection-target-name>minCheckNumber 

</injection-target-name>
             </injection-target>
          </env-entry>
       </session>
   </enterprise-beans>
</ejb-jar>

What's interesting about an XML-only deployment is that if you used RuntimeException rather than EJBException within your bean class, your Java code would have no references to any EJB-specific APIs. If you looked at the Java code, you wouldn't even know that it was an EJB.

The <enterprise-beans> element contained in <ejb-jar> defines the set of EJBs you are deploying. The <session> element denotes that you are deploying a session bean. <ejb-name> gives the session bean an identity that you can reference. The <remote> and <local> elements identify the business interfaces of the bean, and <ejb-class> declares the bean class. The <session-type> element identifies the session bean as a stateless session bean. <resource-ref> and <env-entry> initialize the datasource and minCheckNumber fields of the bean class (details are provided in Chapter 14).

The XML deployment descriptor schema also supports partial XML definitions. For example, if you just wanted to configure the minCheckNumber field in XML, you wouldn't have to declare every piece of metadata about the EJB:

<ejb-jar>
   <session>
      <ejb-name>ProcessPaymentBean</ejb-name>
      <env-entry>
         <env-entry-name>min</env-entry-name>
         <env-entry-type>java.lang.Integer</env-entry-type>
         <env-entry-value>250</env-entry-value>
      </env-entry>
   </session>
</ejb-jar>



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows