3.2. The Loan Enterprise Bean

Ok, interest rates have fallen and it's time to refinance that loan you've been thinking about. How much should you borrow and for how many years? What would your new monthly payment be?

Let's design a Loan enterprise bean that provides methods to calculate long-term fixed rate loans, such as home loans. Our Loan EJB will have three business methods. The first method returns the monthly payment (principal and interest) one must pay given the loan amount, annual interest rate, and term (the length of the loan in years). The second method produces an amortization table providing a month-by-month payment schedule of the loan. A third method produces a year-by-year payment schedule.

In presenting this example, we'll show you the structure and source code for the Loan EJB first and then examine two different ways to call its methods. This includes a Java stand-alone client application called AmortClient and a JSP web component called payment that can be called from your favorite browser. Figure 3-1 provides an architectural overview of these components, their containers, and their relationship to each other.

Figure 3-1. Architectural Overview of the Loan Amortization Enterprise Application


The EJB Container manages the Loan EJB and executes under the J2EE application server on the J2EE server machine. Our stand-alone AmortClient program runs within the application client container on the client machine and communicates with the Loan EJB with the services provided by the application server and the EJB container. The JSP web component (payment) runs within the web container under management of the application server. A client communicates with this JSP web component through a browser running on the client machine.

Before we show you the Java code for this enterprise bean, let's first examine the structure required of all enterprise session beans using our Loan EJB as an example.

Enterprise Session Bean Structure

To build an enterprise session bean, we need to create at least one class and two interfaces: an interface that extends the EJBHome interface (the “home interface), an interface that extends the EJBObject interface (the “remote interface”), and a class that implements the SessionBean interface (the “bean implementation class). Each of these constructs plays a unique role in the EJB architecture and has constraints that a bean developer must follow. As we go through the structure of an enterprise bean, we will detail the role and constraints of each class and interface. In addition, an enterprise bean frequently makes use of other “helper” classes that encapsulate related data, methods, or both. We'll also explore the architectural role of these helper classes as we present our first EJB example.

Although there are no rules on what names a bean developer may choose, it's always a good idea and a common practice to follow a consistent naming strategy. Table 3.1 shows the naming conventions we use for our EJB class/interface names.

Table 3.1. Naming Conventions for EJB Class and Interface Names
EJB Class/Interface Name Example
Remote Interface Bean Name Loan
Home Interface Bean Name + Home LoanHome
Bean Implementation Class Bean Name + Bean LoanBean

Figure 3-2 shows a class diagram of the standard EJB interfaces as well as the classes and interfaces that we write to form the Loan EJB. Interface LoanHome is the home interface extending interface EJBHome. Interface Loan is the remote interface extending interface EJBObject and contains the business methods for our Loan EJB. And finally, class LoanBean is the bean implementation class which implements interface SessionBean. The ellipsis ( … ) after the methods in class LoanBean indicates there are other methods from the SessionBean interface that LoanBean must implement (but we do not show them). Note that you must provide ejbCreate() inside class LoanBean even though it does not appear in interface SessionBean. (Not all session beans have zero-argument ejbCreate() methods. When we discuss stateful session beans, you'll see that you must provide an ejbCreate() method for each create() method you define in the home interface.) During deployment, the container generates classes that implement the home and remote interfaces. These classes invoke methods in the ContainerProxy class, which forwards calls to methods in class LoanBean.

Figure 3-2. Class Diagram Showing the EJB Classes and Interfaces for a Session Bean.


Home Interface

In a session bean, the role of the home interface is to provide a create() method that the client calls to create the enterprise bean. A client uses the lookup and naming service provided by the application server to find a “handle” (reference) to the home interface. A client's call to the create() method of the object that implements the home interface makes the EJB container instantiate the EJB. In general, we need four Java statements to make all this happen, as well as some helper classes provided by the J2EE component API. Here is a code snippet that shows how a client might create our Loan EJB.

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
  ...
try {
  Context initial = new InitialContext();
  Object objref = initial.lookup(
         "java:comp/env/ejb/MyLoan");
  LoanHome home =
         (LoanHome)PortableRemoteObject.narrow(objref,
             LoanHome.class);
  Loan loanEJB = home.create();

} catch (Exception ex) {...}

The imports are necessary to access all the classes we need. A try block must enclose all statements (otherwise, the compiler complains). When a client wants to reference a remote object such as an enterprise Java bean or a database resource, a coded name must be mapped to the target object inside the deployment descriptor. Here we specify the coded name “ejb/MyLoan” with the application server-dependent prefix “java:comp/env/”. The lookup() method returns an object which we then pass to the narrow() method in PortableRemoteObject. This important step allows us to call create() with an instance of an object (home) that implements the home interface (LoanHome).

In a stateless session bean like our Loan EJB, the home interface provides a single create() method with no arguments. Note that create() returns an object that implements the remote interface (Loan). This is the object we will use to invoke the business methods specified in the remote interface.

Listing 3.1 contains the LoanHome interface found in LoanHome.java:

Listing 3.1. LoanHome.java
// LoanHome.java
import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface LoanHome extends EJBHome {

    Loan create() throws RemoteException, CreateException;
}

LoanHome is an interface extending from the EJBHome interface and therefore contains no implementation code. The create() method in a stateless session bean contains no arguments and returns the remote interface type of the enterprise bean (in this case, interface Loan). Both RemoteException and CreateException must appear in the throws clause of this method.

Where is the implementation for the create() method? Each create() method in the home interface has a corresponding ejbCreate() method in the bean implementation class. The client application invokes the create() method in the home interface. The EJB container, in turn, invokes the corresponding ejbCreate() method in the bean implementation class. We will examine the bean implementation class shortly.

Remote Interface

The business processes that an enterprise bean performs are called its business methods. Clients access the create() method through the home interface but call the business methods through the enterprise bean's remote interface. Our Loan EJB has three business methods that must appear in the remote interface. Method monthlyPayment() returns the monthly payment including principal and interest. The monthlyAmortTable() method produces a month-by-month payment table for the loan. Method annualAmortTable() produces a year-by-year payment schedule.

Listing 3.2 contains the source code in Loan.java for the Loan remote interface, which extends the EJBObject interface.

Listing 3.2. Loan.java
// Loan.java
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.util.*;

public interface Loan extends EJBObject {

   public double monthlyPayment(LoanVO loan)
      throws LoanObjectException, RemoteException;
   public ArrayList annualAmortTable(LoanVO loan)
      throws LoanObjectException, RemoteException;
   public ArrayList monthlyAmortTable(LoanVO loan)
      throws LoanObjectException, RemoteException;
}

All business methods in the remote interface must specify RemoteException in their throws clauses. If any method throws an application-specific exception, the name of the exception class must also appear in the throws clause. Here, the business methods all specify LoanObjectException in the throws clause. Like the home interface's create() method, each business method in the remote interface has a corresponding implementation method in the bean implementation class. The method in the remote interface should match its implementation method for all arguments and the return type. The arguments and return values must also be valid RMI types (built-in or serializable), since the J2EE server transmits and receives them remotely to and from the client.

LoanObjectException

LoanObjectException is an example of an application exception. Application exceptions are usually nonfatal errors produced by inconsistent user input or some other recoverable situation. The EJB container does not perform any special error handling during the processing of an application exception. The container simply propagates the exception back to the client.

By contrast, a system exception is generally a nonrecoverable condition, such as a failure to obtain a database connection, the inability to load a class, or a database query that fails. We discuss system exceptions in more detail in Chapter 4 (see “MusicDAOSysException” on page 126). Listing 3.3 shows the source for application exception LoanObjectException.

Listing 3.3. LoanObjectException.java
// LoanObjectException.java
// Application Exception
public class LoanObjectException extends Exception {

   public LoanObjectException() { }

   public LoanObjectException(String msg) {
      super(msg);
   }
}

Value Objects

In the Loan remote interface, monthlyPayment() returns a double, but annualAmortTable() and monthlyAmortTable() return ArrayList. The Java collection class ArrayList is serializable and holds any Object type. Our amortization methods return collections that hold monthly and annual payment schedules for the loan. Because this payment information is important, a helper class called PaymentVO is used to create value objects in the ArrayList collection. Similarly, our Loan EJB business methods make calculations regarding long-term fixed rate loans given certain data about the loan: its annual interest rate, amount, and term. The methods that produce an amortization table also require a start date. To make life easier for both the Loan EJB's clients and the EJB itself, we use another value object to encapsulate the values required to make these calculations. This LoanVO value object is an argument type in all business methods of our remote interface.

Design Guideline

A value object is a class that encapsulates useful data into a larger “coarse-grained” object. This avoids multiple remote calls on an EJB to store or receive information. Value objects do not implement any methods other than “setters” and “getters” to access encapsulated data. Here, our use of value objects is both convenient and efficient.


LoanVO Class

Let's look at our first value object, LoanVO, which describes the loan. This class stores the loan's annual interest rate, total amount borrowed, length of the loan in years, and the loan's start date. Listing 3.4 shows the code in LoanVO.java:

Listing 3.4. LoanVO.java
// LoanVO.java
import java.util.*;
public class LoanVO implements java.io.Serializable
{
  private double amount;         // in dollars
  private double rate;           // percent
  private int years;
  private Calendar startDate;

  public LoanVO (
    double amount,
    double rate,
    int years,
    Calendar startDate) {
      setAmount(amount);
      setRate(rate);
      setYears(years);
      setStartDate(startDate);
  }
  public LoanVO (
    double amount,
    double rate,
    int years) {
      setAmount(amount);
      setRate(rate);
      setYears(years);
      // use today since no date provided
      this.startDate = (GregorianCalendar)
            new GregorianCalendar();
  }

  // getters
  public double getAmount() { return amount; }
  public double getRate() { return rate; }
  public int getYears() { return years; }
  public Calendar getStartDate() {
      return (Calendar)startDate.clone(); }

  // setters
  public void setAmount(double a) { amount = a; }
  public void setRate(double r) { rate = r; }
  public void setYears(int y) { years = y; }
  public void setStartDate(Calendar d) {
    startDate = (GregorianCalendar)new GregorianCalendar(
      d.get(Calendar.YEAR),
      d.get(Calendar.MONTH),
      d.get(Calendar.DATE)); }
} // LoanVO

An import of java.util allows LoanVO to use Calendar and GregorianCalendar. LoanVO also needs to implement java.io.Serializable, since all data that we transmit remotely must be serializable for network calls.

The LoanVO class has two constructors to handle an optional startDate argument. If the client does not provide a date, the second constructor uses today's date (an empty constructor for GregorianCalendar). The remaining methods are getters and setters for the instance variables. Method setStartDate() changes a loan object's start date.

Here's an example of instantiating a LoanVO object with a start date of March 1, 2002, for a fifteen year $90,000 loan at 6.85%.

LoanVO myloan = new LoanVO(90000, 6.85, 15,
        new GregorianCalendar(2002, 2, 1));

Programming Tip

Rather than have getStartDate() return a reference to the Calendar object from LoanVO's startDate instance variable, we use clone() to return a copy of the Calendar object. Thus, we make sure that the Calendar object referred by a LoanVO object is not modified, except through its setter, setStartDate().


PaymentVO Class

Recall that in the Loan remote interface, business methods annualAmortTable() and monthlyAmortTable() return ArrayList. Both methods actually return an ArrayList collection of PaymentVO value objects that encapsulate payment data. Like value object LoanVO, PaymentVO is a convenient way to return the payment schedule within a collection. By returning all the data in a collection, we reduce the number of remote calls required to retrieve payment data. Listing 3.5 shows the source for value object PaymentVO.

The PaymentVO class consists of a constructor with an argument for each of its instance variables and getter methods for clients to access their values. Note that setter methods aren't necessary, since a PaymentVO object is always created by the Loan EJB and PaymentVO's instance variables don't change once they're set. A PaymentVO object stores an integer payment number, the payment date, and the current balance.

For a 15-year loan the payment number is 0 through 180, where 0 is the state of the loan before any payments have been made. On the 0th payment, the balance is equal to the total amount of the loan and is decremented by the principal part of the payment each month. The next two instance variables are the current interest and current principal, which vary each month. The final two instance variables hold the accumulated interest and the accumulated principal. Got all that? On the last payment, the accumulated principal equals the total amount of money borrowed, and the accumulated interest is how much the customer paid in interest over the entire term of the loan (ouch!).

Listing 3.5. PaymentVO.java
// PaymentVO.java
import java.util.*;

public class PaymentVO implements java.io.Serializable
{
  private int paymentNumber;
  private Calendar paymentDate;
  private double balance;

  private double currentInterest;
  private double currentPrincipal;
  private double accumInterest;
  private double accumPrincipal;

  public PaymentVO (
    int paymentNumber,
    Calendar paymentDate,
    double balance,
    double currentInterest,
    double currentPrincipal,
    double accumInterest,
    double accumPrincipal) {

      this.paymentNumber = paymentNumber;
      this.paymentDate = (GregorianCalendar)
            new GregorianCalendar(
        paymentDate.get(Calendar.YEAR),
        paymentDate.get(Calendar.MONTH),
        paymentDate.get(Calendar.DATE));

      this.balance = balance;
      this.currentInterest = currentInterest;
      this.currentPrincipal = currentPrincipal;
      this.accumInterest = accumInterest;
      this.accumPrincipal = accumPrincipal;
  }

  // getters
  public int getPaymentNumber()
      { return paymentNumber; }
  public Calendar getPaymentDate()
      { return (Calendar)paymentDate.clone(); }
  public double getBalance()
      { return balance; }
  public double getCurrentInterest()
      { return currentInterest; }

  public double getCurrentPrincipal()
      { return currentPrincipal; }
  public double getAccumInterest()
      { return accumInterest; }
  public double getAccumPrincipal()
      { return accumPrincipal; }

} // PaymentVO

Bean Implementation Class

Now we are ready to examine our bean implementation class. This is where we implement all the code in an EJB.

The bean implementation class for a session bean must implement the SessionBean interface. You also need to provide methods for ejbCreate(), ejbRemove(), ejbActivate(), ejbPassivate(), and setSessionContext(). Method ejbCreate() is the implementation of create() in the home interface. This method is typically empty since there are no instance variables to initialize. However, we can certainly add specialized code to ejbCreate() if we want to. For example, System.out.println() statements let us know when the EJB container creates our bean. You should also include code that you want to execute once at initialization time inside ejbCreate(). Likewise, the other SessionBean methods are usually empty, since no cleanup tasks are necessary when the EJB container destroys a stateless session bean. The EJB container also does not passivate or activate stateless session beans.

Regardless, the bean implementation class needs to implement the business methods defined in the remote interface. For our Loan EJB, this includes monthlyPayment(), annualAmortTable(), and monthlyAmortTable(). Listing 3.6 shows the LoanBean implementation class and the code for these methods.

Listing 3.6. LoanBean.java
// LoanBean.java
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import java.util.*;
import java.lang.Math;

public class LoanBean implements SessionBean {

  // Private helper methods

  // Private helper method checkLoanObject() makes
  // sure that the LoanVO argument is valid.
  // Valid means: interest rate is positive (> .001),
  // loan amount is positive (> $1),
  // and term is positive (> 1 year).
  // It throws LoanObjectException if there is
  // a problem.

  private void checkLoanObject(LoanVO loan)
          throws LoanObjectException {
    if (loan.getYears() < 1 || loan.getRate() < .001
        || loan.getAmount() < 1.0) {
      throw new LoanObjectException(
            "LoanBean: Bad LoanVO argument");
    }
  }

  // Private helper method getPayment calculates monthly
  // principal and interest payment based on the
  // annual interest rate, term, and loan amount.
  // Called by monthlyPayment() and doAmortTable().

  private double getPayment(LoanVO loan)
           throws LoanObjectException {
    checkLoanObject(loan);
    double monthly_interest = loan.getRate() / (1200);
    int months = loan.getYears() * 12;
    return loan.getAmount() *
      (monthly_interest /
      (1 - Math.pow(1+monthly_interest,-1*months))) ;
  }
  // Private helper method produces an ArrayList
  // of PaymentVO objects based on
  // values stored in LoanVO argument and the
  // requested time period.
  // Called by business methods annualAmortTable() and
  // monthlyAmortTable().

  private ArrayList doAmortTable(LoanVO loan,
           int time_period) throws LoanObjectException {
    ArrayList table = new ArrayList();
    // this output appears on the J2EE server console
    System.out.println("table size = " + table.size());

    // Initialize all calculations to 0
    double currentInterest = 0.;
    double currentPrincipal = 0.;
    double accumInterest = 0.;
    double accumPrincipal = 0.;

    // Obtain values from LoanVO argument
    double currentBalance = loan.getAmount();
    double monthly_interest = loan.getRate() / (1200);
    int months = loan.getYears() * 12;
    // Use helper function to calculate payment
    double monthly_payment = getPayment(loan);
    Calendar curdate = loan.getStartDate();

    // Calculate payment breakdown for each
    // month for the term of the loan
    for (int i = 0; i <= months; i++) {
      if (i % time_period == 0) {
        // create a PaymentVO object
        // depending on time_period
        // and add to ArrayList

        PaymentVO p = new PaymentVO(i, curdate,
          currentBalance,
          currentInterest,
          currentPrincipal,
          accumInterest,
          accumPrincipal);
        table.add(p);	
      }
      // Update all of the calculation
      // variables
      currentInterest = currentBalance * monthly_interest;
      currentPrincipal = monthly_payment -
             currentInterest;
      currentBalance -= currentPrincipal;
      accumInterest += currentInterest;
      accumPrincipal += currentPrincipal;
      curdate.add(Calendar.MONTH, 1);
    }

    // Output appears on J2EE console;
    // shows new ArrayList size.
    // This number depends on term and
    // whether client requested annual
    // or monthly amortization table.
    System.out.println("table size = " + table.size());
    return table;
  }

  // Business Methods

  // Amortization tables. Annual table specifies
  // time period 12 (every 12 months) and
  // monthly table specifies 1 (every month).
  public ArrayList annualAmortTable(LoanVO loan)
          throws LoanObjectException {
    return doAmortTable(loan, 12);
  }

  public ArrayList monthlyAmortTable(LoanVO loan)
          throws LoanObjectException {
    return doAmortTable(loan, 1);
  }

  // Business method monthlyPayment; calls helper
  // method getPayment().
  public double monthlyPayment(LoanVO loan)
          throws LoanObjectException {
    return getPayment(loan);
  }
  // EJB Methods
  public LoanBean() {}
  public void ejbCreate() {}
  public void ejbRemove() {}
  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void setSessionContext(SessionContext sc) {}

} // LoanBean

The getPayment() method contains the math to do payment calculations for LoanVo objects. Both monthlyAmortTable() and annualAmortTable() call private method doAmortTable() with the LoanVO object and the time_period as arguments. The doAmortTable() method builds an ArrayList of PaymentVO objects within a loop based on the number of payments in the life of the loan. Depending on the value of integer time_period, this method creates a new PaymentVO instance based on the calculations for the data and adds this payment object to the ArrayList collection. In each iteration, we decrement the balance by the principal, add the current interest to the accumulated interest, add the current principal to the accumulated principal, and add one month to the current date. This is true business logic in action, hidden inside the EJB.

Compiling, Packaging, and Deployment

Now that you've seen the Loan EJB design, let's show you how to compile, package, and deploy our stateless EJB. Although the steps are dependent on your J2EE application server and the tools you are using, we'll discuss each step separately. You can follow along even if the details of your development system differ somewhat. Figure 3-3 is an activity diagram showing the relationship of these three steps. Figure 3-4 and Figure 3-5 expand the packaging and deployment steps.

Figure 3-3. Activity Diagram Showing the Steps to Compile, Package, and Deploy an Enterprise Application


Figure 3-4. Activity Diagram Showing the Steps to Package an Enterprise Component Within an Enterprise Application .ear File


Figure 3-5. Activity Diagram Showing the Steps to Deploy an Enterprise Application


Compiling

First, we must build .class files from the Java programs. In any large-scale application, some sort of makefile tool, such as the public domain ant utility can be handy. Here, we simply use javac to compile our source files on the command line. The -classpath argument ($CPATH) specifies j2ee.jar, the J2EE JAR file that contains the EJB support classes.

$ javac -classpath "$CPATH" LoanVO.java Loan.java
							LoanBean.java LoanHome.java PaymentVO.java LoanVO.java
						

This compilation step produces all the .class files we need for our Loan EJB example, including the helper classes PaymentVO and LoanVO.

Packaging

Packaging an EJB involves not only placing the required .class files into a JAR file, but you must also package the JAR file into an enterprise application EAR (Enterprise ARchive) file. For any type of J2EE component, you must first create a J2EE application (or add a component to an existing J2EE application). An enterprise application may contain one or more EJBs, web components (such as a JSP component or a servlet), and an application client.

Sun Microsystems provides a reference implementation that includes a deployment tool called deploytool. This tool allows bean developers to perform the steps necessary to package an enterprise bean within a J2EE applica tion. As you package the components, the deployment tool steps you through the appropriate screens. The deployment tool generates each component's deployment descriptor for you from the declaration information you provide.

The deployment descriptor describes a component's deployment settings for the application server. An enterprise bean's deployment settings include the names of the home interface, the remote interface, and the bean implementation class. The deployment descriptor also specifies bean type (session or entity), and stateful or stateless for session beans.

The deployment descriptor is implemented in an XML file. Extensible Markup Language (XML) is a portable, text-based specification language for defining text-based data. Any program or tool that uses the XML API (JAXP) can read the data, making deployment descriptors portable among many application servers. Furthermore, an administrator who is in charge of deploying applications can change deployment settings without modifying a bean's source code.

Listing 3.7 is the deployment descriptor created by Sun's deploytool for our Loan EJB. We show the tag values in bold to enhance readability.

Listing 3.7. Deployment Descriptor for Loan EJB
<ejb-jar>
 <display-name>LoanJAR</display-name>
 <enterprise-beans>

   <session>
      <display-name>LoanEJB</display-name>
      <ejb-name>LoanEJB</ejb-name>
      <home>LoanHome</home>
      <remote>Loan</remote>
      <ejb-class>LoanBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Bean</transaction-type>

      <security-identity>
        <description></description>
        <use-caller-identity></use-caller-identity>
      </security-identity>

    </session>
 </enterprise-beans>
</ejb-jar>

XML encloses each tag in brackets (<tag>) then terminates the definition with </tag>. The deployment tool uses <display-name> and <ejb-name> to access the name of our bean (LoanEJB). We specify the Session bean classes and interfaces with tags <home> (home interface LoanHome), <remote> (remote interface Loan) and <ejb-class> (bean implementation class LoanBean). The tag <session-type> flags our LoanEJB as a stateless session bean. The <transaction-type> tag specifies Bean because our EJB does not have methods that require transaction control. This tells the EJB container not to generate any transaction-control code for our methods. Definitions for <security-identity> are empty.

Deployment

Before deployment, a J2EE application server must be running. The deployment tool's job is to prepare all components of an enterprise application for the J2EE server. For an EJB component, the deployment tool compiles the “stub” classes, loads the EJB JAR files, and binds coded names to the JNDI names. Although the deployment process loads the JAR file for the EJB container, the EJB does not truly exist until a remote client calls create() through the home interface.

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

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