Add XML to your J2EE applications

Integrate an XML presentation layer in the J2EE layered architecture

Today's organizations' rapid moves to e-business bring new demands on defining flexible systems architectures. Systems need to be powerful, scalable, robust, and, most of all, capable of meeting new business demands. With that in mind, although more and more organizations are adopting a multitier, Java 2 Platform, Enterprise Edition (J2EE)-based application server architecture, they rely on HTML for the presentation layer. This prevents them from fully benefiting from the flexibility the distributed, multitier J2EE framework can offer. XML's Web publishing capabilities are key to solving this architectural shortfall.

This article proposes a replacement for the current HTML-based J2EE architecture presentation layer with a dynamic XML content layer. Through a case study, we'll see an XML-aware, open source application server within a fully operational end-to-end solution. At the heart of the case study resides Cocoon, a servlet from Apache that provides a pluggable Java presentation framework used to present XML content in a variety of formats, on demand.

The situation

J2EE defines a standard for developing multitier enterprise applications. J2EE simplifies enterprise applications by basing them on standardized, modular components, by providing a complete set of services (persistence, security, transactions, and so on) to those components, and by handling many details of application behavior declaratively, without complex programming. However, the architecture's presentation layer is primarily HTML-based, thus limiting the target audience largely to Web browsers. Tomorrow's audience will be much broader, what with a variety of new devices about to revolutionize how we send and receive information. As such, it's imperative to decouple content from presentation logic and become independent of our intended audience's devices. XML is the solution.

XML's Web publishing capabilities are possible using XSLT to transform XML documents into other textual documents. These target documents are often XML-based languages themselves (with HTML serving as a notable exception as it is based on SGML). As a metalanguage, XML can also produce other Web languages such as:

  • XHTML (for Web browsers, WebTV, and soon WAP devices)
  • WML (a language for describing two-dimensional graphics in XML)
  • SVG
  • MathML
  • VoxML (for voice)

XSLT can transform an XML document into any one of these target languages. Once we've expressed the solution in terms of XML, we can leverage it using XML's sister technologies to produce powerful, media-aware Web application that will remain invariant to target device changes.

Case study

The case study develops a software application to automate a car rental company's various processes -- reserving, renting, and returning a car. Our objectives: analyze, design, and implement the car rental agency system and build a scalable system capable of targeting multiple end user devices (Web browsers, WAP -- Wireless Application Protocol -- phones, Web TV, and so on).

For simplicity's sake, we'll implement just one use case: reserve car. Other use cases such as modify/cancel reservation, return/rent car, or maintain car could be similarly implemented.

Under the reserve car use case, the customer first calls the reservation desk to make a rental reservation. The rental reservation agent (RRA) takes the customer's name and queries the system for a match. If the customer is new, the RRA takes the customer's information and enters it into the system.

Then the RRA requests information regarding the reservation -- start date, duration of reservation, and the type of car required from the customer. Once entered into the system, the RRA checks to see if the customer's requested car type is available for the required time period. Keep in mind that other auxiliary requirements for additional information on reservation, overbooking, and vehicle availability could be added later.

After giving the customer a reservation confirmation number for future communication, the RRA calculates the amount chargeable to the customer for the terms of the reservation so that the customer knows what the rental cost will be, up front. At any point during the conversation, the customer may choose to terminate the interaction. In such a case, the reservation is canceled, but the customer information is kept should the same customer call again.

Set up the container environment

The project employs two open source containers: an EJB (Enterprise JavaBeans) container called jBoss and the Tomcat servlet container from Apache. Tomcat includes the Cocoon servlet to make it XML aware. The instructions for setting up Apache, Tomcat, and Cocoon can be found at the Cocoon Website with install cases for Unix and Windows 2000/NT. jBoss now comes with Tomcat embedded in it and can be obtained from the jBoss Website (for both sites, see Resources).

The servlet container: Tomcat enhanced with Cocoon

We'll employ Tomcat as the servlet container for this project. Tomcat, developed by the Apache Software Foundation, serves as the official reference implementation for the Java Servlet 2.2 and JavaServer Pages (JSP) 1.1 technologies. This article uses the stable Tomcat 3.2. Tomcat 4.0 (based on Servlet 2.3 and JSP 1.2 APIs) is currently in development.

Since it is based on the Servlet 2.2 API, deployment occurs with a war file -- a Web archive file (similar to a zip or jar file) used to bundle together JSP pages, associated JavaBean classes, servlets, XML files, images, and XSLT files. Since Tomcat coupled with jBoss is in essence a bare-bones application server, it makes sense, when they run on the same machine, to run them in the same JVM, which can speed up the response time by a factor of 30.

Cocoon, a 100% pure Java publishing framework also developed by Apache, relies on new W3C technologies (such as DOM, XML, and XSL) to provide Web content. Even though Cocoon is just a servlet deployed inside of Tomcat making the servlet container XML aware, it represents the backbone of our project. Cocoon allows the clear separation of content and style using the XML/XSLT paradigm. (For a clear introductory-level overview of Cocoon, see Resources.)

The EJB Container: jBoss

jBoss, an open source EJB (1.1 compliant with some 2.0 support) application server, incorporates most of the features found in a high-end application server. Because jBoss employs hot deployment for EJB jar files, deployment is as easy as dropping an EJB jar file into a deployment directory. jBoss also leverages Java 1.3's dynamic proxies features to provide dynamic compilation of subs and skeletons. Further, though not available yet, clustering capabilities using JINI technologies will soon be included.

For our project, we'll use jBoss 2.0 (with Tomcat embedded). Version 2.0 includes integration with JMS (Java Messaging Service) using SpyderMQ. JAWS (Just Another Web Storage) delivers container managed persistence for entity beans, while Minerva manages pools of database connections.

The layered architecture

The J2EE layered architecture, illustrated in Figure 1, can minimize the cost of evolving an application's functionality and updating its implementation technology. These layers can be broadly categorized as the presentation layer, application layer, service layer, domain layer, and persistence layer.

Figure 1. J2EE's layered architecture. Click on thumbnail to view full-size image. (10 KB)

Presentation layer

For our purposes, we'll focus our attention on the presentation layer, which is confined in the J2EE architecture to HTML/JSP with JavaScript for client-side validation, which therefore assumes a Web browser target device. As noted earlier, we'll use an XML solution with multiarchitecture devices as the intended targets. Once the presentation logic has been separated from the data, the rich arrangement of presentation can then be realized by using XSLT for text (HTML and XHTML), XSLFO (not a recommendation yet) for binary (PDF), and SVG for graphics.

Our presentation layer has no HTML or JSP presentation files associated with it. Instead, it uses flat XML files with other XML files dynamically created on the fly by the application layer. Then, depending on the querying device, an XSLT file delivers the presentation. The publishing engine will determine the type of the querying device and set the required XSLT file accordingly. Thus we'll separate presentation from content and clearly define the division of responsibilities.

In terms of the model-view-controller (MVC) patterns, the XML document acts as the model, the JSP in the application layer serves as the controller, and the XSLT file is the view. Then, by changing only the XSLT file, different views can be realized.

The application layer

The reserve car use case can be broken down into three areas:

  • Add customer
  • Update customer
  • Make reservation

A controller JavaServer Page -- reserve.jsp -- defines the application layer and delegates responsibility to JavaBeans. Note: in this role, the JSP serves purely as a shorthand servlet notation and contains no presentation information. Here's reserve.jsp's code:

// Reserve.jsp page
<jsp:useBean id="customer" scope="request"
class="com.valtech.bootcamp.carRental.web.customer.CustomerJB"/>
<jsp:useBean id="reservation" scope="request"class=
"com.valtech.bootcamp.carRental.web.reservation.ReservationJB"/>
<%
   if( request.getParameter("btnReset") != null ) {
      session.invalidate();
      session = request.getSession();
   } %>
<% String client = (String)session.getAttribute("USER"); %>
// Checks if the client exists
<% if(client == null){ %>
   // Checks if the name field has been filled in
   // If the name field is null, then the user is presented with a login file
   <% if (request.getParameter("name") == null)
   { %>
       <jsp:forward page="login.xml" />
    // if the address field is null, then the user is presented
    // with a customer details form
    <% } else if (request.getParameter("address") == null)
       { %>
           <jsp:setProperty name="customer" property="*"/>
         <% customer.getCustomer(); %>
         <% String customerXML = customer.toXML(); %>
         <% session.setAttribute("CURRENTDOC", customerXML); %>
         <jsp:forward page="/servlet/com.valtech.bootcamp.
           carRental.web.cocoon.CocoonFromServlet" />
      <% } else { %>
   // The user has completed the form, and the customer's
   // data has been persisted
   // A String object is also saved to the session context
   // to show that the customer has been
   // validated for this session
         <jsp:setProperty name="customer" property="*"/>
         <% customer.setCustomer(); %>
         <% session.setAttribute("USER", "true"); %>
         <jsp:forward page="reserve.jsp" />
       <% } %>
<% } else { %>
    // Checks if the stateDate field has been filled in
    // If the field is null, the user is presented with a
    // reservation page with the name of the customer filled in
   <% if (request.getParameter("startDate") == null) { %>
      <% reservation.setCustomerName(customer.getName()); %>
      <% String reservationXML = reservation.toXML(); %>
      <% session.setAttribute("CURRENTDOC", reservationXML); %>
      <jsp:forward page="/servlet/com.valtech.bootcamp.carRental.
         web.cocoon.CocoonFromServlet" />
       <!-- <jsp:forward page="reservation.xml" /> -->
   // If the startDate field has been filled in, the full reservation
   // details are displayed to the customer
   <% } else { %>
        <jsp:setProperty name="reservation" property="*"/>
        <% reservation.setReservation(); %>
      <% String reservationXML = reservation.toXML(); %>
      <% session.setAttribute("CURRENTDOC", reservationXML); %>
      <jsp:forward page="/servlet/com.valtech.bootcamp.carRental.web.
         cocoon.CocoonFromServlet" />
   <% } %>
<% } %>

As seen in the preceding code (see the useBean tags at the start of the code) the controller JavaServer Page uses two JavaBeans: a Customer bean and a Reservationbean. This simplifies the JSP by abstracting out any complex remote method invocation and delegating that responsibility to the JavaBeans. Thus the JSP now concerns itself only with simple logic. It then routes control to various other pages depending on that logic. This also makes testing of the JSP much easier as a test can now be written for each bean and tested without the added complexity of having to deploy the bean in a servlet container.

As a further responsibility, the bean produces an XML representation of itself. Each bean possesses a toXML()method that returns an xmlised string of the data represented in the bean. Presently, these functions do only a simple string concatenation of the bean's attributes, but this could be done with a more complex DOM or JDOM representation.

The Customer bean

The Customer bean gets and sets a customer. The sequence diagram shows clearly that following a POST of a customer's name from the user, the controller reserve.jsppage calls getCustomer() on the JavaBean. The bean in turn calls across the middleware to a use case stateless session EJB called ReservationAgent. If the customer exists in the backend, then the JavaBean is populated with the customer details. The reserve.jsp page then calls the toXML() function on the customerbean and forwards the resulting string, which represents the XML document, to Cocoon's engine for rendering with the corresponding XSLT document. Here's the Customer bean:

/**
 * Client side bean for the Customer
 * @author Valtech UK
 */
public class CustomerJB {
/**
 * the customer name
 */
private String name = "";
/**
 * the customer address
 */
private String address = "";
/**
 * the card number of the customer
 */
private String cardNum = "";
/**
 * the state holder object
 */
private CustomerStateHolder csh;
/**
 * @param name the name of the customer
 */
public CustomerJB(String name)
{
     this.name = name;
}
public CustomerJB(){}
/**
 * Full arguments constructor.
 * @param name the name of the customer
 * @param address the address of the customer
 * @param cardNum the card number of the customer
 */
public CustomerJB(String name, String address, String cardNum)
{
     this.name = name;
     this.address = address;
     this.cardNum = cardNum;
}
/**
 * gets the name of the customer
 */
public String getName()
{
     return name;
 }
 /**
  * sets the customer name
  */
public void setName(String name)
{
     this.name = name;
}
/**
 * gets the address of the customer
 */
public String getAddress()
{
    return address;
}
/**
 * sets the address of the customer
 */
public void setAddress(String address)
{
    this.address=address;
}
/**
 * gets the card number
 */
public String getCardNum()
{
    return cardNum;
}
/**
 * sets the card number
 */
public void setCardNum(String cardNum)
{
    this.cardNum=cardNum;
}
/**
  * Calls getCustomer() on the ReservationAgent facade and
  * extracts this state holder into the bean attribute fields.
  */
public CustomerStateHolder getCustomer() {
       CustomerStateHolder csh = null;
        System.setProperty("java.naming.factory.initial",
            "org.jnp.interfaces.NamingContextFactory");
       System.setProperty("java.naming.provider.url",
            "localhost:1099");
       try
        {
           Context initial = new InitialContext();
           ReservationAgentHome resAgentHome = (ReservationAgentHome)
              PortableRemoteObject.narrow
             (initial.lookup("ReservationAgentBean"),
             ReservationAgentHome.class);
            ReservationAgent reservationAgent = resAgentHome.create();
           csh = reservationAgent.getCustomer(this.name);
        }
        catch(Exception e)
        {
            System.out.print("Test Error");
            //e.printStackTrace();
        }
        if (csh != null)
        {
        this.address = csh.getAddress();
        this.cardNum = csh.getCardNo();
        this.name = csh.getName();
        }
        return csh;
}
/**
 *  calls setCustomer() on the ReservationAgent facade.
 */
public void setCustomer() {
System.setProperty("java.naming.factory.initial","org.jnp.interfaces.NamingCon
textFactory");
       System.setProperty("java.naming.provider.url","localhost:1099");
          try
        {
           Context initial = new InitialContext();
           ReservationAgentHome resAgentHome = (ReservationAgentHome)
              PortableRemoteObject.narrow(initial.lookup
              ("ReservationAgentBean"), ReservationAgentHome.class);
            ReservationAgent reservationAgent = resAgentHome.create();
           reservationAgent.setCustomer(this.createCustomerStateHolder());
        }
        catch(Exception e)
        {
            System.out.print("Test Error");
            e.printStackTrace();
        }
}
/*
 * places bean attributes into a CustomerStateHolder.
 */
public CustomerStateHolder createCustomerStateHolder()
{
         return new CustomerStateHolder(this.name, this.address, this.cardNum);
}
/*
 * removes the occurrence of a customer by calling
 * removeCustomer() on the ReservationAgent bean
 */
public void removeCustomer(CustomerStateHolder csh)
{
System.setProperty("java.naming.factory.initial",
"org.jnp.interfaces.NamingCon textFactory");
          System.setProperty("java.naming.provider.url","localhost:1099");
            try
            {
            Context initialb = new InitialContext();
            ReservationAgentHome raHome =
(ReservationAgentHome)PortableRemoteObject.narrow(initialb.lookup
   ("ReservationAgentBean"), ReservationAgentHome.class);
            ReservationAgent ra=raHome.create();
         ra.removeCustomer(csh.getName());
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
}
/*
 * Generates a String of XML for the customer details entry form.
 * Note that it is planned to encapsulate this detail in another object.
 */
public String toXML()
{
  return new String( "<?xml version=\"1.0\"?>" +
           "<?cocoon-process type=\"xslt\"?>" +
           "<?xml-stylesheet href=\"/stylesheet/form-html.xsl\"
              type=\"text/xsl\"?>"  +
           "<?xml-stylesheet href=\"/stylesheet/form-wml.xsl\"
              type=\"text/xsl\" media=\"wap\"?>"  +
           "<form action=\"reserve.jsp\">" +
           "<head>" +
           "<title>ABC Car Rental</title>" +
           "</head>" +
           "<input type=\"text\" name=\"name\" value=\"" +
              this.getName() + "\" size=\"11\"/>" +
           "<input type=\"text\" name=\"address\" value=\""
              + this.getAddress() + "\" size=\"11\"/>" +
           "<input type=\"text\" name=\"cardNum\" value=\""
              + this.getCardNum() + "\" size=\"11\"/>" +
           "</form>" );
}
}
Figure 2. Forward to the Cocoon servlet. Click on thumbnail to view full-s

This basic procedure repeats for all of the other use cases, but it is worth exploring in more detail what happens when the XML string, representing the customer bean, is forwarded to Cocoon's engine. A limitation of the current servlet specification is that the output of one servlet cannot be piped into the input of another. Figure 2 shows how to get around this limitation; the JavaServer Page sets the string, which contains an XML representation of the JavaBean, in the session context. It then forwards to another servlet CocoonFromServlet, which gets the String object back out from the session context and wraps the XML string object in an HTTP request object. It then launches the Cocoon engine and forwards this HTTP request object to it for processing. It should be stressed that this workaround has been fully tested only with Cocoon 1.7.4.

Whether the customer exists or not, the user is next shown a page with the customer's details. If the customer exists, these details are already filled in and can be modified or updated. Figure 3 illustrates the sequence diagram for updating the customer, while Figure 4 shows the sequence diagram for a new customer. If the customer does not exist, then only the customer name is filled in.

Figure 3. Update the Customer. Click on thumbnail to view full-size image. (9 KB)

Following submission of this form back to the controlling JSP, the page subsequently calls setCustomer() on the Customerbean, which again calls across the middleware to the use case stateless session bean. The customer now exists in persistent storage in the backend, and the controller JSP stores a string object to the session context. This object is queried every time the controller page is activated. So on subsequent session calls to this JSP, the controller will know, by this object's existence, that it is now dealing with a client whose details have been persisted.

Figure 4. Make a new customer. Click thumbnail to view full-size image. (9

The Reservation bean

The Reservation bean gets and sets a reservation. Having persisted the customer's details, Figure 5 shows how the JSP now makes a reservation. To accomplish this the controller JSP uses a second bean, the Reservation.

Figure 5. Make a reservation Click on thumbnail to view full-size image. (12 KB)

The current customer's name is set in this bean, and the JSP calls its toXML() function, then renders it as before by forwarding the string to Cocoon's engine:

/**
 * Client side java bean for the Reservation object.
 * @author Valtech UK
 * @version 1.0
 */
public class ReservationJB {
/**
 * the car type of the reservation
 */
private String carType = "";
/**
 * the name of the customer
 */
private String customerName = "";
/**
 * the end date of the reservation
 */
private String endDate = "";
/*
 * the reservation number of the reservation
 */
private String reservationNumber = "";
/**
 * the start date of the reservation
 */
private String startDate = "";
/**
 * @param reservationNumber the unique reservation number of the reservation.
 * @param startDate the start date of the reservation
 * @param endDate the end date of the reservation
 * @param carType the type of car specified in the reservation.
 * @param customerName the name of the customer making the reservation
 */
public ReservationJB(String reservationNumber, String startDate, String
endDate, String carType, String customerName)
{
        this.carType = carType;
        this.customerName = customerName;
        this.endDate = endDate;
        this.startDate = startDate;
        /*
         * note the reservation number is set to a default
         * here as the value gets reset.
         */
      this.reservationNumber = "555";
}
/**
 * populates the state holder attributes into the bean
 * @param rsh the state holder containing details of the reservation
 */
public ReservationJB(ReservationStateHolder rsh)
{
        this.carType = rsh.getCarType();
        this.customerName = rsh.getCustomerName();
        this.endDate = rsh.getEndDate();
        this.startDate = rsh.getStartDate();
        this.reservationNumber = rsh.getReservationNumber();
}
/**
  * overridden for the JUnit testing framework
  * @param rjb the ReservationJB that is being used in
  * the comparison to the calling object
  */
public boolean equals( ReservationJB rjb)
{
    return (rjb.startDate.equals(this.startDate) &&
    rjb.endDate.equals(this.endDate) && rjb.carType.equals(this.carType)
    && rjb.customerName.equals(this.customerName));
}
/**
* Default constructor
**/
public ReservationJB(){}
/**
 * gets the car type
 */
public String getCarType(){ return carType; }
/**
 * sets the car type
 */
public void setCarType(String carType){ this.carType = carType; }
/**
 * gets the customer name
 */
public String getCustomerName(){ return customerName; }
/**
 * sets the customer name
 */
public void setCustomerName(String customerName){ this.customerName =
customerName; }
/**
 * gets the end date
 */
public String getEndDate(){ return endDate; }
/**
 * sets the end date
 */
public void setEndDate(String endDate){ this.endDate = endDate; }
/**
 * gets the reservation number
 */
public String getReservationNumber(){ return reservationNumber; }
/**
 * sets the reservation number
 */
public void setReservationNumber(String reservationNumber){
this.reservationNumber = reservationNumber; }
/**
 * gets the start date
 */
public String getStartDate(){ return startDate; }
/**
 * sets the start date
 */
public void setStartDate(String startDate){ this.startDate = startDate; }
/**
 * calls setReservation() on the ReservationAgent facade.
 */
public void setReservation()
{
        System.setProperty("java.naming.factory.initial",
            "org.jnp.interfaces.NamingContextFactory");
       System.setProperty("java.naming.provider.url",
            "localhost:1099");
       try
        {
           Context initial = new InitialContext();
           ReservationAgentHome resAgentHome = 
           (ReservationAgentHome)PortableRemoteObject
           .narrow(initial.lookup("ReservationAgentBean"), 
           ReservationAgentHome.class);
            ReservationAgent reservationAgent = resAgentHome.create();
           String resCreate = reservationAgent.setReservation(getReservationStateHolder());
            System.out.println(resCreate);
            setReservationNumber(resCreate);
        }
        catch(Exception e)
        {
            System.out.print("Test Error");
            e.printStackTrace();
        }
}
/**
 * returns a state holder of the reservation.
 */
public ReservationStateHolder getReservation()
{
        ReservationStateHolder rsh = null;
        System.setProperty("java.naming.factory.initial",
            "org.jnp.interfaces.NamingContextFactory");
       System.setProperty("java.naming.provider.url",
            "localhost:1099");
       try
        {
           Context initial = new InitialContext();
           ReservationAgentHome resAgentHome = 
          (ReservationAgentHome)PortableRemoteObject.
          narrow(initial.lookup("ReservationAgentBean"), 
          ReservationAgentHome.class);
            ReservationAgent reservationAgent = resAgentHome.create();
           rsh = reservationAgent.getReservation(this.reservationNumber);
        }
        catch(Exception e)
        {
            System.out.print("Test Error");
            e.printStackTrace();
        }
        return rsh;
}
/**
 * returns a state holder constructed from the bean attributes
 */
public ReservationStateHolder getReservationStateHolder()
{
    return new  ReservationStateHolder(this.reservationNumber,
    this.startDate, this.endDate, this.carType, this.customerName);
}
public void getRegistration()
{
}
/**
 * generates an XML document for the reservation entry form.
 */
public String toXML()
{
    return new String( "<?xml version=\"1.0\"?>" +
               "<?cocoon-process type=\"xslt\"?>" +
               "<?xml-stylesheet href=\"/stylesheet/
                  form-html.xsl\" type=\"text/xsl\"?>"  +
                "<?xml-stylesheet href=\"/stylesheet/
                   form-wml.xsl\" type=\"text/xsl\" media=\"wap\"?>"  +
                   "<form action=\"reserve.jsp\">" +
                  "<head>" +
                  "<title>ABC Car Rental</title>" +
                  "</head>" +
                  "<input type=\"text\" name=\"customerName\"
                     value=\"" + this.getCustomerName() + "\" size=\"11                     "/>" +
                  "<input type=\"text\" name=\"carType\" value=
                     \"" + this.getCarType() + "\" size=\"11\"/>" +
                  "<input type=\"text\" name=\"startDate\" value=\""
                     + this.getStartDate() + "\" size=\"11\"/>" +
                        "<input type=\"text\" name=\"endDate\" value=
                        \"" + this.getEndDate() + "\" size=\"11\"/>" +
                         "<input type=\"text\" name=\"reservationNo\"
                            value=\"" + this.getReservationNumber() +
                            "\" size=\"11\"/>" +
                  "</form>" );
}
}

Having filled in the reservation details, the user posts the data back to the JSP, which calls the function setReservation()on the Reservation bean. The bean again calls across the middleware to a use case stateless session EJB called ReservationAgent, which persists the data and returns a unique reservation number.

The service layer

The service layer contains controllers and, in the J2EE layer architecture, session beans represent these controllers. In the session bean Facade design pattern, session beans always mask entity beans. Session beans represent the use cases and can therefore delimit transactional boundaries. This object positioning leads to a service-based architecture where the session beans, which represent workflow, control entity beans. Session beans are therefore an extension of the client on the server-side.

Session beans come in two flavors: stateless session beans and stateful session beans. In a stateless session bean, the bean does not maintain state between method calls. Stateless session beans, more complex to design, typically require numerous method parameters to be passed in, which typically involves heavy network traffic. Conversely, stateful session beans do maintain state with the client. They are easier to design and typically have fewer parameters passed to them from the client.

Stateless session beans support scalability, since they are not bound to any one particular client. One stateless session bean can service multiple clients, thus a few hundred stateless session beans could handle thousands of concurrent clients. By their nature, stateful session beans are bound to a client in a one-to-one manner. While there are no hard and fast rules, EJB designs for systems expecting loads of thousands of simultaneous uses should follow a stateless design.

In our situation, we represent the reserve car use case as a stateless session bean called the ReservationAgent with four methods:

  • getCustomer()
  • setCustomer()
  • getReservation()
  • setReservation()

ReservationAgentcontrols workflow and manipulates the Customer and Reservation entity beans:

/** * Remote interface for ReservationAgent session EJB

* @author Valtech UK */ public interface ReservationAgent extends EJBObject { /** * gets the customer */ CustomerStateHolder getCustomer(String name) throws RemoteException; /** * sets the customer */ void setCustomer(CustomerStateHolder customer) throws RemoteException; /** * sets the reservation */ String setReservation(ReservationStateHolder reservation) throws RemoteException; /** * gets the reservation */ ReservationStateHolder getReservation(String reservationNumber) throws RemoteException; /** * removes the customer */ void removeCustomer(String name) throws RemoteException; /** @link dependency */ /*#ReservationAgentBean lnkSession1Bean;*/ }

The getCustomer() method does a lookup by calling findByPrimaryKey()on the entity bean's remote home object and calls getCustomerStateHolder() on the entity bean. A value object (State Holder design pattern) transfers large amount of data pertaining to a particular entity object across the middleware in one serializable object. This reduces network traffic. Care should be taken when using this pattern as duplicate copies of the data now exist. Ideally, use it only when the data is read-only or when a lock can be managed on the backend data while the data is away.

Figure 6. Get a customer Click on thumbnail to view full-size image. (13 KB)

The setCustomer() method looks up the Customerentity beans and checks if the customer exists by calling findByPrimaryKey() on the entity bean's remote home object. If the customer does not exist, it creates a new entity bean to represent that customer. If the customer does exist, it updates the existing entity bean.

Figure 7. Set a customer Click on thumbnail to view full-size image. (12 KB)

The getReservation() and setReservation()methods do similar manipulation to the Reservation entity bean.

The business (domain) layer

The business layer of an EJB application contains shared business objects, typically shared by multiple clients. As a good first approximation, entity beans represent a row in a database and represent that row as a Java object (an entity bean) and allow you to manipulate that object with public accessor methods.

Entity beans come in two flavors: container manager persistent (CMP) entity beans and bean-managed persistent (BMP) entity beans. In CMP, the basic CRUD (create, read, update, and delete) database operations are delegated to the container. In BMP it is up to the developer to write the SQL code for the database operations. For integration of legacy systems with a complex database structure, BMP represent the only option at this time. One of the big improvements between the EJB 1.1 specification and the 2.0 specification: enhanced CMP functionality

For simplicity, these beans will be modeled as container-managed entity beans, which involves specifying which attributes in the bean will be persistent. Declare persistent fields in the XML deployment descriptor, ejb-jar.xml, file:

<
?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans
1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">
<ejb-jar>
<description>This contains the Service elements of
   the CarRental System< /description>
     <display-name>ServiceEJB</display-name>
     <enterprise-beans>
       <session>
    <ejb-name>ReservationAgentBean</ejb-name>
    <home>
com.valtech.bootcamp.carRental.service.
reservationAgent.ReservationAgentHome< /home>
    <remote>
com.valtech.bootcamp.carRental.service.
reservationAgent.ReservationAgent< /remote>
    <ejb-class>
com.valtech.bootcamp.carRental.service.
reservationAgent.impl.ReservationAgentBean</ejb-class>
    <session-type>Stateless</session-type>
    <transaction-type>Container</transaction-type>
    <ejb-ref>
           <ejb-ref-name>ReservationBean</ejb-ref-name>
           <ejb-ref-type>Entity</ejb-ref-type>
           <home>
com.valtech.bootcamp.carRental.business.reservation.ReservationHome</home>
           <remote>
com.valtech.bootcamp.carRental.business.reservation.Reservation</remote>
           <ejb-link>ReservationBean</ejb-link>
         </ejb-ref>
         <ejb-ref>
           <ejb-ref-name>CustomerBean</ejb-ref-name>
           <ejb-ref-type>Entity</ejb-ref-type>
           <home>
com.valtech.bootcamp.carRental.business.customer.
CustomerHome</home>
           <remote>com.valtech.bootcamp.
           carRental.business.customer.Customer<
/remote>
           <ejb-link>CustomerBean</ejb-link>
         </ejb-ref>
         </session>
   <entity>
    <ejb-name>CustomerBean</ejb-name>
    <home>com.valtech.bootcamp.carRental.business.
    customer.CustomerHome</home>
    <remote>com.valtech.bootcamp.carRental.business.
       customer.Customer</remote>
    <ejb-class>
com.valtech.bootcamp.carRental.business.customer.
   impl.CustomerBean</ejb-class>
    <persistence-type>Container</persistence-type>
    <prim-key-class>java.lang.String</prim-key-class>
    <primkey-field>name</primkey-field>
    <reentrant>False</reentrant>
         <transaction-type>Bean</transaction-type>
    <cmp-field><field-name>name</field-name></cmp-field>
    <cmp-field><field-name>address</field-name
       ></cmp-field>
    <cmp-field><field-name>cardNo</field-name
        ></cmp-field>
   </entity>
   <entity>
        <ejb-name>ReservationBean</ejb-name>
    <home>com.valtech.bootcamp.carRental.business.
reservation.ReservationHome<
/home>
    <remote>com.valtech.bootcamp.carRental.business.
reservation.Reservation<
/remote>
    <ejb-class>
com.valtech.bootcamp.carRental.business.reservation.impl.
ReservationBean<
/ejb-class>
    <persistence-type>Container</persistence-type>
    <prim-key-class>java.lang.String</prim-key-class>
    <primkey-field>reservationNumber</primkey-field>
    <reentrant>False</reentrant>
         <transaction-type>Bean</transaction-type>
    <cmp-field><field-name>reservationNumber
       </field-name></cmp-field>
    <cmp-field><field-name>startDate</field-name>
       </cmp-field>
    <cmp-field><field-name>endDate</field-name>
       </cmp-field>
    <cmp-field><field-name>carType</field-name>
       </cmp-field>
    <cmp-field><field-name>customerName</field-name>
       </cmp-field>
   </entity>
     </enterprise-beans>
   </ejb-jar>

This layer therefore contains two CMP entity beans. A CustomerBeanentity EJB represents the customer, and a ReservationBeanentity EJB represents a reservation. As both are persistent shared objects, they are therefore modeled as entity beans.

The Customer entity bean

The Customer bean has three persistent fields:

  • name
  • address
  • cardNo(for the credit card number)

For simplicity these are all modeled as strings. The bean's primary key is name. The jBoss container automatically maps these fields to a table called Customer and does the appropriate Java-type SQL conversion. The notable function contracts that this bean implements are getStateHolder()and setStateHolder(). The bean also implements the mandatory technical contract of ejbFindByPrimaryKey()and the optional ejbCreate(). The ejbCreate() creates a Customerentity bean that creates a customer row in the database:

/**
 * Remote interface for Customer EJB
 * @author Valtech UK
 */
public interface Customer extends EJBObject
{
  /**
   * Returns CustomerStateHolder containing parameters
   * mapping to Entity CMP fields
   */
  CustomerStateHolder getCustomerStateHolder() throws RemoteException;
  /**
   * sets CustomerStateHolder
   * @param csh The CustomerStateHolder
   */
  void setCustomerStateHolder(CustomerStateHolder csh) throws RemoteException;
  /**
   * sets the address
   */
  void setAddress(String address) throws RemoteException;
  /**
   * sets the card number
   */
  void setCardNo(String cardNo) throws RemoteException;
}

The Reservation entity bean

The Reservation bean has five persistent fields:

  • reservationNo
  • startDate
  • endDate
  • carType
  • customerName

reservationNois the primary key, while customerNameis the foreign key. The function contract getStateHolder()and setStateHolder(), the mandatory technical contract of ejbFindByPrimaryKey()and the optional ejbCreate()are all implemented:

/**
 * Remote interface for ReservatEion EJB
 * @author Valtech UK
 */
public interface Reservation extends EJBObject {
    /**
     * gets the reservation number
     */
    public String getReservationNumber() throws RemoteException;
    /**
     * sets the reservation number
     */
    public void setReservationNumber(String resnum) throws RemoteException;
    /**
     * gets the start date of the reservation
     */
    public String getStartDate() throws RemoteException;
    /**
     * sets the start date of the reservation
     */
    public void setStartDate(String newStartDate) throws RemoteException;
    /**
     * gets the end date
     */
    public String getEndDate() throws RemoteException;
    /**
     * sets the end date
     */
    public void setEndDate(String newStartDate) throws RemoteException;
    /**
     * sets the car type
     */
    public void setCarType(String newCarType) throws RemoteException;
    /**
     * gets the car type
     */
    public String getCarType()  throws RemoteException;
    /**
     * sets the customer name
     */
    public void setCustomerName(String newCustomerName) throws
RemoteException;
    /**
     * gets the customer name
     */
    public String getCustomerName() throws RemoteException;
    /**
     * gets the resrvation state holder
     */
    public ReservationStateHolder getReservationStateHolder() throws
RemoteException;
    /**
     * sets the reservation state holder
     */
    public void setReservationStateHolder(ReservationStateHolder rsh) throws
RemoteException;
}//end of class

The ejbCreate() creates a Reservation entity bean that creates a row in the database.

The persistence layer

In this article, we use the Hypersonic open source database bundled with jBoss. At 100KB, Hypersonic is an incredible piece of open source software. However, more serious users may choose other more mature and ACID-compliant open source databases.

The Results

Replacing the HTML/JSP presentation layer with an XML layer now gives the flexibility of targeting multiple devices. A different style sheet must be written for each device. This example features a style sheet for a WAP phone (tested on a Nokia WAP emulator) and one for a Web browser (tested with IE5, Netscape 4.7, and the latest release of the Mozilla).

Each style sheet must be listed in the XML file (static or dynamic), along with an extra tag to identify the target device. Cocoon then identifies the requesting device from the incoming header and uses the correct style sheet to transform the XML into the appropriate language understood by the querying device.

The Code

You can download the complete source code for this article from Resources. It was developed in an extreme programming environment using the techniques of iterative development, unit testing, risk driven, and pair programming. The code can be found in the src directory, which includes two packages; the com package (for the source code) and a corresponding test package. The testing was done with the JUnit testing framework. The whole project can be build with Ant (a Java-based build tool). The build file is also located in the src directory. The README file in the parent directory details the environment needed to compile the source code. The result is an ear file that can be dropped into the deploy directory of jBoss.

The code has also been released as an open source project found at http://jaba.sourceforge.net/, where you will find any code updates and a "how to" to setup Cocoon with JBoss/Tomcat. Any volunteers who wish to work on this project are more than welcome.

Conclusion

In this article we've learned how to replace J2EE's Web browser-orientated, HTML presentation layer with a more generic XML layer -- which allows us to become target-device independent by clearly separating the data (XML) from its presentation (XSLT). We can now achieve multiple representations of the same data by transforming it using different XSLT stylesheets. Making an open source application server XML aware has made all this possible.

XML becomes the clear choice as the channels for delivering media grows with the information revolution. XML allows us as programmers to concentrate on the data and delegate responsibility for its presentation elsewhere. Moreover, XML gives us invariance in a world where we no longer know how this data will be presented. Finally, XML opens a world of possibilities where ubiquitous information is finally being realized.

Eoin Lanehas been working as a senior consultant and trainer at Valtech since early 2000. Prior to Valtech, in the mid-1990s he founded the intranet document company InConn Technologies, which provides intranet Document Management Systems. With a background as a postdoctoral researcher and ten year's experience in the IT industry, over the past three years Eoin has focused on Linux, object-oriented design, Java, J2EE, EJB, and XML, with a particular interest in open source technologies.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies