The Java EE 5 Tutorial

Part VII Case Studies

Part Seven presents case studies.

Chapter 36 The Coffee Break Application

This chapter describes the Coffee Break application, a set of web applications that demonstrate how to use several of the Java Web services APIs together. The Coffee Break sells coffee on the Internet. Customers communicate with the Coffee Break server to order coffee online. The server uses JavaServer Faces technology as well as Java servlets, JSP pages, and JavaBeans components. When using the server a customer enters the quantity of each coffee to order and clicks the Submit button to send the order.

Overview of the Coffee Break Application

The Coffee Break does not maintain any inventory. It handles customer and order management and billing. Each order is filled by forwarding suborders to one or more coffee suppliers. Figure 36–1 shows this process.

Figure 36–1 Coffee Break Application Flow

A diagram showing the interaction of the Coffee Break
client, server, and web services

The Coffee Break server obtains the coffee varieties and their prices by querying suppliers at startup and on demand.

  1. The Coffee Break server uses SAAJ messaging to communicate with one of the suppliers. The Coffee Break has been dealing with this supplier for some time and has previously made the necessary arrangements for doing request-response SAAJ messaging. The two parties have agreed to exchange four kinds of XML messages and have set up the DTDs those messages will follow.

  2. The Coffee Break server requests price lists from each of the coffee suppliers. The server makes the appropriate remote web service calls and waits for the response, which is a JavaBeans component representing a price list. The SAAJ supplier returns price lists as XML documents.

  3. Upon receiving the responses, the Coffee Break server processes the price lists from the JavaBeans components returned by calls to the suppliers.

  4. The Coffee Break server creates a local database of suppliers.

  5. When an order is placed, suborders are sent to one or more suppliers using the supplier’s preferred protocol.

Common Code

The Coffee Break servers share the CoffeeBreak.properties file, which contains the URLs exposed by the JAX-WS and SAAJ suppliers; the URLHelper class, which is used by the server and client classes to retrieve the URLs; the DateHelper utility class; and several generated JavaBeans components, described in JAX-WS Coffee Supplier Service. These JavaBeans components are generated from the cb-jaxws JAX-WS web service by the wsimport tool.

The source code for the shared files is in the tut-install/javaeetutorial5/examples/coffeebreak/cb-common/src/java/com/sun/cb/common/ directory.

JAX-WS Coffee Supplier Service

The Coffee Break servers are clients of the JAX-WS coffee supplier service. The service code consists of the service implementation class, and several JavaBeans components that are used for method parameters and return types. The JavaBeans components are:

These JavaBeans components are propagated to the clients by means of the wsimport tool.

Service Implementation

The Supplier class implements the placeOrder and getPriceList methods. So that you can focus on the code related to JAX-WS, these methods are short and simplistic. In a real world application, these methods would access databases and would interact with other services, such as shipping, accounting, and inventory.

The placeOrder method accepts as input a coffee order and returns a confirmation for the order. To keep things simple, the placeOrder method confirms every order and sets the ship date in the confirmation to the next day. The source code for the placeOrder method follows:

public ConfirmationBean placeOrder(OrderBean order) {

    Date tomorrow = DateHelper.addDays(new Date(), 1);
    ConfirmationBean confirmation =
        new ConfirmationBean(order.getId(), DateHelper.dateToCalendar(tomorrow));
    return confirmation;
}

The getPriceList method returns a PriceListBean object, which lists the name and price of each type of coffee that can be ordered from this service. The getPriceList method creates the PriceListBean object by invoking a private method named loadPrices. In a production application, the loadPrices method would fetch the prices from a database. However, our loadPrices method takes a shortcut by getting the prices from the SupplierPrices.properties file. Here are the getPriceList and loadPrices methods:

public PriceListBean getPriceList() {

    PriceListBean priceList = loadPrices();
    return priceList;
}

private PriceListBean loadPrices() {

    String propsName = "com.sun.cb.ws.server.SupplierPrices";
    Date today = new Date();
    Date endDate = DateHelper.addDays(today, 30);

    PriceItemBean[] priceItems = PriceLoader.loadItems(propsName);
    PriceListBean priceList =
        new PriceListBean(DateHelper.dateToCalendar(today),
            DateHelper.dateToCalendar(endDate), priceItems);

    return priceList;
}

SAAJ Coffee Supplier Service

The SAAJ supplier service implements the arrangements that the supplier and the Coffee Break have made regarding their exchange of XML documents. These arrangements include the kinds of messages they will send, the form of those messages, and the kind of messaging they will do. They have agreed to do request-response messaging using the SAAJ API (the javax.xml.soap package).

The Coffee Break servers send two kinds of messages:

The SAAJ coffee supplier responds with two kinds of messages:

All the messages they send conform to an agreed-upon XML structure, which is specified in a DTD for each kind of message. This allows them to exchange messages even though they use different document formats internally.

The four kinds of messages exchanged by the Coffee Break servers and the SAAJ supplier are specified by the following DTDs:

These DTDs can be found at tut-install/javaeetutorial5/examples/coffeebreak/cb-saaj/dtds/. The dtds directory also contains a sample of what the XML documents specified in the DTDs might look like.

The corresponding XML files for the DTDs are as follows:

Because of the DTDs, both parties know ahead of time what to expect in a particular kind of message and can therefore extract its content using the SAAJ API.

Code for the client and server applications is in this directory:

tut-install/javaeetutorial5/examples/coffeebreak/cb-saaj/src/java

SAAJ Client

The Coffee Break server, which is a SAAJ client in this scenario, sends requests to the SAAJ supplier. The SAAJ client application uses the SOAPConnection method call to send messages.

SOAPMessage response = con.call(request, endpoint);

Accordingly, the client code has two major tasks. The first is to create and send the request; the second is to extract the content from the response. These tasks are handled by the classes PriceListRequest and OrderRequest.

Sending the Request

This section covers the code for creating and sending the request for an updated price list. This is done in the getPriceList method of PriceListRequest, which follows the DTD price-list.dtd.

The getPriceList method begins by creating the connection that will be used to send the request. Then it gets the default MessageFactory object to be used for creating the SOAPMessage object msg.

SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
SOAPConnection con = scf.createConnection();
SOAPFactory soapFactory = SOAPFactory.newInstance();

MessageFactory mf = MessageFactory.newInstance();
SOAPMessage msg = mf.createMessage();

The next step is to access the message’s SOAPBody object, to which the message’s content will be added.

SOAPBody body = msg.getSOAPBody();

The file price-list.dtd specifies that the topmost element inside the body is request-prices and that it contains the element request. The text node added to request is the text of the request being sent. Every new element that is added to the message must have a QName object to identify it. The following lines of code create the top-level element in the SOAPBody object body. The first element created in a SOAPBody object is always a SOAPBodyElement object.

Name bodyName = new QName("http://sonata.coffeebreak.com",
    "request-prices", "RequestPrices");
SOAPBodyElement requestPrices =
    body.addBodyElement(bodyName);

In the next few lines, the code adds the element request to the element request-prices (represented by the SOAPBodyElement requestPrices). Then the code adds a text node containing the text of the request. Next, because there are no other elements in the request, the code calls the method saveChanges on the message to save what has been done.

QName requestName = new QName("request");
SOAPElement request = requestPrices.addChildElement(requestName);
request.addTextNode("Send updated price list.");

msg.saveChanges();

With the creation of the request message completed, the code sends the message to the SAAJ coffee supplier. The message being sent is the SOAPMessage object msg, to which the elements created in the previous code snippets were added. The endpoint is the URI for the SAAJ coffee supplier, http://localhost:8080/saaj-coffee-supplier/getPriceList. The SOAPConnection object con is used to send the message, and because it is no longer needed, it is closed.

URL endpoint = new URL(url);
SOAPMessage response = con.call(msg, endpoint);
con.close();

When the call method is executed, the Application Server executes the servlet PriceListServlet. This servlet creates and returns a SOAPMessage object whose content is the SAAJ supplier’s price list. (PriceListServlet is discussed in Returning the Price List.) The Application Server knows to execute PriceListServlet because the given endpoint is mapped to that servlet.

Extracting the Price List

This section demonstrates (1) retrieving the price list that is contained in response, the SOAPMessage object returned by the method call, and (2) returning the price list as a PriceListBean.

The code creates an empty Vector object that will hold the coffee-name and price elements that are extracted from response. Then the code uses response to access its SOAPBody object, which holds the message’s content.

Vector<String> list = new Vector<String>();

SOAPBody responseBody = response.getSOAPBody();

The next step is to retrieve the SOAPBodyElement object. The method getChildElements returns an Iterator object that contains all the child elements of the element on which it is called, so in the following lines of code, it1 contains the SOAPBodyElement object bodyEl, which represents the price-list element.

Iterator it1 = responseBody.getChildElements();
 while (it1.hasNext()) {
     SOAPBodyElement bodyEl = (SOAPBodyElement)it1.next();

The Iterator object it2 holds the child elements of bodyEl, which represent coffee elements. Calling the method next on it2 retrieves the first coffee element in bodyEl. As long as it2 has another element, the method next will return the next coffee element.

    Iterator it2 = bodyEl.getChildElements();
    while (it2.hasNext()) {
        SOAPElement child2 = (SOAPElement)it2.next();

The next lines of code drill down another level to retrieve the coffee-name and price elements contained in it3. Then the message getValue retrieves the text (a coffee name or a price) that the SAAJ coffee supplier added to the coffee-name and price elements when it gave content to response. The final line in the following code fragment adds the coffee name or price to the Vector object list. Note that because of the nested while loops, for each coffee element that the code retrieves, both of its child elements (the coffee-name and price elements) are retrieved.

        Iterator it3 = child2.getChildElements();
        while (it3.hasNext()) {
            SOAPElement child3 = (SOAPElement)it3.next();
            String value = child3.getValue();
            list.addElement(value);
        }
    }
}

The final code fragment adds the coffee names and their prices (as a PriceListItem) to the ArrayList priceItems, and prints each pair on a separate line. Finally it constructs and returns a PriceListBean.

ArrayList<PriceItemBean> items = new ArrayList<PriceItemBean>();
for (int i = 0; i < list.size(); i = i + 2) {
    PriceItemBean pib = new PriceItemBean();
    pib.setCoffeeName(list.elementAt(i).toString());
    pib.setPricePerPound(new BigDecimal(list.elementAt(i + 1).toString()));
    items.add(pib);
    System.out.print(list.elementAt(i) + "        ");
    System.out.println(list.elementAt(i + 1));
}

Date today = new Date();
Date endDate = DateHelper.addDays(today, 30);
GregorianCalendar todayCal = new GregorianCalendar();
todayCal.setTime(today);
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(endDate);
plb = new PriceListBean();
plb.setStartDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(todayCal));

List<PriceItemBean> priceItems = new ArrayList<PriceItemBean>();
Iterator<PriceItemBean> i = items.iterator();
while (i.hasNext()) {
    PriceItemBean pib = i.next();
    plb.getPriceItems().add(pib);
}

plb.setEndDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(cal));

Ordering Coffee

The other kind of message that the Coffee Break servers can send to the SAAJ supplier is an order for coffee. This is done in the placeOrder method of OrderRequest, which follows the DTD coffee-order.dtd.

Creating the Order

As with the client code for requesting a price list, the placeOrder method starts by creating a SOAPConnection object and a SOAPMessage object and accessing the message’s SOAPBody object.

SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
SOAPConnection con = scf.createConnection();

MessageFactory mf = MessageFactory.newInstance();
SOAPMessage msg = mf.createMessage();

SOAPBody body = msg.getSOAPBody();

Next, the code creates and adds XML elements to form the order. As is required, the first element is a SOAPBodyElement, which in this case is coffee-order.

QName bodyName = new QName("http://sonata.coffeebreak.com",
    "coffee-order", "PO");
SOAPBodyElement order = body.addBodyElement(bodyName);

The application then adds the next level of elements, the first of these being orderID. The value given to orderID is extracted from the OrderBean object passed to the OrderRequest.placeOrder method.

QName orderIDName = new QName("orderID");
SOAPElement orderID = order.addChildElement(orderIDName);
orderID.addTextNode(orderBean.getId());

The next element, customer, has several child elements that give information about the customer. This information is also extracted from the Customer component of OrderBean.

QName childName = new QName("customer");
SOAPElement customer = order.addChildElement(childName);

childName = new QName("last-name");
SOAPElement lastName = customer.addChildElement(childName);
lastName.addTextNode(orderBean.getCustomer().getLastName());

childName = new QName("first-name");
SOAPElement firstName = customer.addChildElement(childName);
firstName.addTextNode(orderBean.getCustomer().getFirstName());

childName = new QName("phone-number");
SOAPElement phoneNumber = customer.addChildElement(childName);
phoneNumber.addTextNode(orderBean.getCustomer().getPhoneNumber());

childName = new QName("email-address");
SOAPElement emailAddress = customer.addChildElement(childName);
emailAddress.addTextNode(orderBean.getCustomer().getEmailAddress());

The address element, added next, has child elements for the street, city, state, and zip code. This information is extracted from the Address component of OrderBean.

childName = new QName("address");
SOAPElement address = order.addChildElement(childName);

childName = new QName("street");
SOAPElement street = address.addChildElement(childName);
street.addTextNode(orderBean.getAddress().getStreet());

childName = new QName("city");
SOAPElement city = address.addChildElement(childName);
city.addTextNode(orderBean.getAddress().getCity());

childName = new QName("state");
SOAPElement state = address.addChildElement(childName);
state.addTextNode(orderBean.getAddress().getState());

childName = new QName("zip");
SOAPElement zip = address.addChildElement(childName);
zip.addTextNode(orderBean.getAddress().getZip());

The element line-item has three child elements: coffeeName, pounds, and price. This information is extracted from the LineItems list contained in OrderBean.

List<LineItemBean> lineItems = orderBean.getLineItems();
Iterator<LineItemBean> i = lineItems.iterator();
while (i.hasNext()) {
    LineItemBean lib = i.next();

    childName = new QName("line-item");
    SOAPElement lineItem = order.addChildElement(childName);

    childName = new QName("coffeeName");
    SOAPElement coffeeName = lineItem.addChildElement(childName);
    coffeeName.addTextNode(lib.getCoffeeName());

    childName = new QName("pounds");
    SOAPElement pounds = lineItem.addChildElement(childName);
    pounds.addTextNode(lib.getPounds().toString());

    childName = new QName("price");
    SOAPElement price = lineItem.addChildElement(childName);
    price.addTextNode(lib.getPrice().toString());
}

// total
childName = new QName("total");
SOAPElement total = order.addChildElement(childName);
total.addTextNode(orderBean.getTotal().toString());

With the order complete, the application sends the message to the endpoint http://localhost:8080/saaj-coffee-supplier/orderCoffee and closes the connection.

URL endpoint = new URL(url);
SOAPMessage reply = con.call(msg, endpoint);
con.close();

Because the given endpoint is mapped to ConfirmationServlet, the Application Server executes that servlet (discussed in Returning the Order Confirmation) to create and return the SOAPMessage object reply.

Retrieving the Order Confirmation

The rest of the placeOrder method retrieves the information returned in reply. The client knows what elements are in it because they are specified in confirm.dtd. After accessing the SOAPBody object, the code retrieves the confirmation element and gets the text of the orderID and ship-date elements. Finally, it constructs and returns a ConfirmationBean with this information.

SOAPBody sBody = reply.getSOAPBody();
Iterator bodyIt = sBody.getChildElements();
SOAPBodyElement sbEl = (SOAPBodyElement)bodyIt.next();
Iterator bodyIt2 = sbEl.getChildElements();

SOAPElement ID = (SOAPElement)bodyIt2.next();
String id = ID.getValue();

SOAPElement sDate = (SOAPElement)bodyIt2.next();
String shippingDate = sDate.getValue();

SimpleDateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy");
Date date = df.parse(shippingDate);
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
cb = new ConfirmationBean();
cb.setOrderId(id);
cb.setShippingDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(cal));

SAAJ Service

The SAAJ coffee supplier (the SAAJ server in this scenario) provides the response part of the request-response paradigm. When SAAJ messaging is being used, the server code is a servlet. The core part of each servlet is made up of three javax.servlet.HttpServlet methods: init, doPost, and onMessage. The init and doPost methods set up the response message, and the onMessage method gives the message its content.

Returning the Price List

This section takes you through the servlet PriceListServlet. This servlet creates the message containing the current price list that is returned to the method call, invoked in PriceListRequest.

Any servlet extends a javax.servlet class. Being part of a web application, this servlet extends HttpServlet. It first creates a static MessageFactory object that will be used later to create the SOAPMessage object that is returned.

public class PriceListServlet extends HttpServlet {
    static final Logger logger =
        Logger.getLogger("com.sun.cb.saaj.PriceListServlet");
    static MessageFactory messageFactory = null;

    static {
        try {
            messageFactory = MessageFactory.newInstance();
        } catch (Exception ex) {
            logger.severe("Exception: " + ex.toString());
        }
     };

Every servlet has an init method. This init method initializes the servlet with the configuration information that the Application Server passed to it.

public void init(ServletConfig servletConfig)
        throws ServletException {
    super.init(servletConfig);
}

The next method defined in PriceListServlet is doPost, which does the real work of the servlet by calling the onMessage method. (The onMessage method is discussed later in this section.) The Application Server passes the doPost method two arguments. The first argument, the HttpServletRequest object req, holds the content of the message sent in PriceListRequest. The doPost method gets the content from req and puts it in the SOAPMessage object msg so that it can pass it to the onMessage method. The second argument, the HttpServletResponse object resp, will hold the message generated by executing the method onMessage.

In the following code fragment, doPost calls the methods getHeaders and putHeaders, defined immediately after doPost, to read and write the headers in req. It then gets the content of req as a stream and passes the headers and the input stream to the method MessageFactory.createMessage. The result is that the SOAPMessage object msg contains the request for a price list. Note that in this case, msg does not have any headers because the message sent in PriceListRequest did not have any headers.

public void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    try {
        // Get all the headers from the HTTP request
        MimeHeaders headers = getHeaders(req);

        // Get the body of the HTTP request
        InputStream is = req.getInputStream();

        // Now internalize the contents of the HTTP request
        // and create a SOAPMessage
        SOAPMessage msg = messageFactory.createMessage(headers, is);

Next, the code declares the SOAPMessage object reply and populates it by calling the method onMessage.

        SOAPMessage reply = null;
        reply = onMessage(msg);

If reply has anything in it, its contents are saved, the status of resp is set to OK, and the headers and content of reply are written to resp. If reply is empty, the status of resp is set to indicate that there is no content.

        if (reply != null) {

            /*
             * Need to call saveChanges because we’re
             * going to use the MimeHeaders to set HTTP
             * response information. These MimeHeaders
             * are generated as part of the save.
             */
            if (reply.saveRequired()) {
                reply.saveChanges();
            }

            resp.setStatus(HttpServletResponse.SC_OK);
            putHeaders(reply.getMimeHeaders(), resp);

            // Write out the message on the response stream
            logger.info("Reply message:");
            OutputStream os = resp.getOutputStream();
            reply.writeTo(os);
            os.flush();
        } else {
            resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
        }
    } catch (Exception ex) {
        throw new ServletException( "SAAJ POST failed: " + ex.getMessage());
    }
}

The methods getHeaders and putHeaders are not standard methods in a servlet, as init, doPost, and onMessage are. The method doPost calls getHeaders and passes it the HttpServletRequest object req that the Application Server passed to it. It returns a MimeHeaders object populated with the headers from req.

static MimeHeaders getHeaders(HttpServletRequest req) {

    Enumeration headerNames = req.getHeaderNames();
    MimeHeaders headers = new MimeHeaders();

    while (headerNames.hasMoreElements()) {
        String headerName = (String)headerNames.nextElement();
        String headerValue = req.getHeader(headerName);

        StringTokenizer values = new StringTokenizer(headerValue, ",");
        while (values.hasMoreTokens()) {
            headers.addHeader(headerName, values.nextToken().trim());
        }
    }
    return headers;
}

The doPost method calls putHeaders and passes it the MimeHeaders object headers, which was returned by the method getHeaders. The method putHeaders writes the headers in headers to res, the second argument passed to it. The result is that res, the response that the Application Server will return to the method call, now contains the headers that were in the original request.

static void putHeaders(MimeHeaders headers, HttpServletResponse res) {

    Iterator it = headers.getAllHeaders();
    while (it.hasNext()) {
        MimeHeader header = (MimeHeader)it.next();

        String[] values = headers.getHeader(header.getName());
        if (values.length == 1)
            res.setHeader(header.getName(), header.getValue());
        else {
            StringBuffer concat = new StringBuffer();
            int i = 0;
            while (i < values.length) {
                if (i != 0) {
                    concat.append(’,’);
                }
                concat.append(values[i++]);
            }
            res.setHeader(header.getName(), concat.toString());
        }
    }
}

The method onMessage is the application code for responding to the message sent by PriceListRequest and internalized into msg. It uses the static MessageFactory object messageFactory to create the SOAPMessage object message and then populates it with the supplier’s current coffee prices.

The method doPost invokes onMessage and passes it msg. In this case, onMessage does not need to use msg because it simply creates a message containing the supplier’s price list. The onMessage method in ConfirmationServlet (see Returning the Order Confirmation), on the other hand, uses the message passed to it to get the order ID.

public SOAPMessage onMessage(SOAPMessage msg) {
    SOAPMessage message = null;

    try {
        message = messageFactory.createMessage();

        SOAPBody body = message.getSOAPBody();

        QName bodyName =
             new QName("http://sonata.coffeebreak.com", "price-list", "PriceList");
        SOAPBodyElement list = body.addBodyElement(bodyName);

        QName coffeeN = new QName("coffee");
        SOAPElement coffee = list.addChildElement(coffeeN);

        QName coffeeNm1 = new QName("coffee-name");
        SOAPElement coffeeName = coffee.addChildElement(coffeeNm1);
        coffeeName.addTextNode("Arabica");

        QName priceName1 = new QName("price");
        SOAPElement price1 = coffee.addChildElement(priceName1);
        price1.addTextNode("4.50");

        QName coffeeNm2 = new QName("coffee-name");
        SOAPElement coffeeName2 = coffee.addChildElement(coffeeNm2);
        coffeeName2.addTextNode("Espresso");

        QName priceName2 = new QName("price");
        SOAPElement price2 = coffee.addChildElement(priceName2);
        price2.addTextNode("5.00");

        QName coffeeNm3 = new QName("coffee-name");
        SOAPElement coffeeName3 = coffee.addChildElement(coffeeNm3);
        coffeeName3.addTextNode("Dorada");

        QName priceName3 = new QName("price");
        SOAPElement price3 = coffee.addChildElement(priceName3);
        price3.addTextNode("6.00");

        QName coffeeNm4 = snew QName("coffee-name");
        SOAPElement coffeeName4 = coffee.addChildElement(coffeeNm4);
        coffeeName4.addTextNode("House Blend");

        QName priceName4 = new QName("price");
        SOAPElement price4 = coffee.addChildElement(priceName4);
        price4.addTextNode("5.00");

        message.saveChanges();

    } catch(Exception e) {
        logger.severe("onMessage: Exception: " + e.toString());
    }
    return message;
}

Returning the Order Confirmation

ConfirmationServlet creates the confirmation message that is returned to the call method that is invoked in OrderRequest. It is very similar to the code in PriceListServlet except that instead of building a price list, its onMessage method builds a confirmation containing the order number and shipping date.

The onMessage method for this servlet uses the SOAPMessage object passed to it by the doPost method to get the order number sent in OrderRequest. Then it builds a confirmation message containing the order ID and shipping date. The shipping date is calculated as today’s date plus two days.

public SOAPMessage onMessage(SOAPMessage message) {
    logger.info("onMessage");    
    SOAPMessage confirmation = null;

    try {

        // Retrieve orderID from message received
        SOAPBody sentSB = message.getSOAPBody();
        Iterator sentIt = sentSB.getChildElements();
        SOAPBodyElement sentSBE = (SOAPBodyElement)sentIt.next();
        Iterator sentIt2 = sentSBE.getChildElements();
        SOAPElement sentSE = (SOAPElement)sentIt2.next();

        // Get the orderID test to put in confirmation
        String sentID = sentSE.getValue();

        // Create the confirmation message
        confirmation = messageFactory.createMessage();
        SOAPBody sb = message.getSOAPBody();

        QName newBodyName =
            new QName("http://sonata.coffeebreak.com", "confirmation", "Confirm");
        SOAPBodyElement confirm = sb.addBodyElement(newBodyName);

        // Create the orderID element for confirmation
        QName newOrderIDName = new QName("orderId");
        SOAPElement newOrderNo = confirm.addChildElement(newOrderIDName);
        newOrderNo.addTextNode(sentID);

        // Create ship-date element
        QName shipDateName = new QName("ship-date");
        SOAPElement shipDate = confirm.addChildElement(shipDateName);

        // Create the shipping date
        Date today = new Date();
        long msPerDay = 1000 * 60 * 60 * 24;
        long msTarget = today.getTime();
        long msSum = msTarget + (msPerDay * 2);
        Date result = new Date();
        result.setTime(msSum);
        String sd = result.toString();
        shipDate.addTextNode(sd);

        confirmation.saveChanges();

    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return confirmation;
}

Coffee Break Server

The Coffee Break server uses JavaServer Faces technology to build its user interface. The JSP pages use JavaServer Faces UI component tags to represent widgets, such as text fields and tables. All the JSP pages use preludes and codas to achieve a common look and feel among the HTML pages, and many of the JSTL custom tags discussed in Chapter 7, JavaServer Pages Standard Tag Library.

The Coffee Break server implementation is organized along the Model-View-Controller design pattern. A FacesServlet instance (included with the JavaServer Faces API) acts as the controller. It examines the request URL, creates and initializes model JavaBeans components, and dispatches requests to view JSP pages. The JavaBeans components contain the business logic for the application; they call the web services and perform computations on the data returned from the services. The JSP pages format the data stored in the JavaBeans components. The mapping between JavaBeans components and pages is summarized in Table 36–1.

Table 36–1 Model and View Components

Function 

JSP Page 

JavaBeans Component 

Update order data 

orderForm

ShoppingCart

Update delivery and billing data 

checkoutForm

CheckoutFormBean

Display order confirmation 

checkoutAck

OrderConfirmations

JSP Pages

The JSP pages are as follows:

The orderForm Page

orderForm displays the current contents of the shopping cart. The first time the page is requested, the quantities of all the coffees are 0 (zero). Each time the customer changes the coffee amounts and clicks the Update button, the request is posted back to orderForm.

The CoffeeBreakBean bean component updates the values in the shopping cart, which are then redisplayed by orderForm. When the order is complete, the customer proceeds to the checkoutForm page by clicking the Checkout button.

The table of coffees displayed on the orderForm is rendered using one of the JavaServer Faces component tags, dataTable. Here is part of the dataTable tag from orderForm:

<h:dataTable id="table"
    columnClasses="list-column-center,list-column-right,
        list-column-center, list-column-right"
    headerClass="list-header" rowClasses="list-row"
    footerClass="list-column-right"
    styleClass="list-background-grid"
    value="#{CoffeeBreakBean.cart.items}" var="sci">
    <f:facet name="header">
        <h:outputText  value="#{CBMessages.OrderForm}"/>
    </f:facet>
    <h:column>
        <f:facet name="header">
            <h:outputText  value="Coffee"/>
        </f:facet>
        <h:outputText id="coffeeName"
            value="#{sci.item.coffeeName}"/>
    </h:column>
    ...
</h:dataTable>

When this tag is processed, a UIData component and a Table renderer are created on the server side. The UIData component supports a data binding to a collection of data objects. The Table renderer takes care of generating the HTML markup. The UIData component iterates through the list of coffees, and the Table renderer renders each row in the table.

This example is a classic use case for a UIData component because the number of coffees might not be known to the application developer or the page author at the time the application is developed. Also, the UIData component can dynamically adjust the number of rows in the table to accommodate the underlying data.

For more information on UIData, please see Using Data-Bound Table Components.

The checkoutForm Page

checkoutForm is used to collect delivery and billing information from the customer. When the Submit button is clicked, an ActionEvent is generated. This event is first handled by the submit method of the checkoutFormBean. This method acts as a listener for the event because the tag corresponding to the submit button references the submit method with its action attribute:

<h:commandButton value="#{CBMessages.Submit}"
    action="#{checkoutFormBean.submit}"/>

The submit method submits the suborders to each supplier and stores the result in the request-scoped OrderConfirmations bean.

The checkoutForm page has standard validators on several components and a custom validator on the email component. Here is the tag corresponding to the firstName component, which holds the customer’s first name:

<h:inputText id="firstName" value="#{checkoutFormBean.firstName}"
    size="15" maxlength="20" required="true"/>

With the required attribute set to true, the JavaServer Faces implementation will check whether the user entered something in the First Name field.

The email component has a custom validator registered on it. Here is the tag corresponding to the email component:

<h:inputText id="email" value="#{checkoutFormBean.email}"
    size="25" maxlength="125" validator="#{checkoutFormBean.validateEmail}"/>

The validator attribute refers to the validateEmail method on the CheckoutFormBean class. This method ensures that the value the user enters in the email field contains an @ character.

If the validation does not succeed, the checkoutForm is re-rendered, with error notifications in each invalid field. If the validation succeeds, checkoutFormBean submits suborders to each supplier and stores the result in the request-scoped OrderConfirmations JavaBeans component and control is passed to the checkoutAck page.

The checkoutAck Page

checkoutAck simply displays the contents of the OrderConfirmations JavaBeans component, which is a list of the suborders constituting an order and the ship dates of each suborder. This page also uses a UIData component. Again, the number of coffees the customer ordered is not known before runtime. The UIData component dynamically adds rows to accommodate the order.

The checkoutAck.jsp page also makes use of a custom converter that converts the shipping date into an XMLGregorianCalendar type:

<h:outputText id="coffeeName"
    value="#{oc.confirmationBean.shippingDate}">
    <f:converter converterId="XMLGregorianCalendarConverter" /
</h:outputText>

The custom converter is implemented by XMLGregorianCalendarConverter.java.

JavaBeans Components

The JavaBeans components are as follows:

The RetailPriceList JavaBeans Component

RetailPriceList is a list of retail price items. A retail price item contains a coffee name, a wholesale price per pound, a retail price per pound, and a supplier. This data is used for two purposes: it contains the price list presented to the end user and is used by CheckoutFormBean when it constructs the suborders dispatched to coffee suppliers.

RetailPriceList first calls the URLHelper.getEndpointURL method to determine the JAX-WS service endpoint. It then queries the JAX-WS service for a coffee price list. Finally it queries the SAAJ service for a price list. The two price lists are combined and a retail price per pound is determined by adding a markup of 35% to the wholesale prices.

The ShoppingCart JavaBeans Component

ShoppingCart is a list of shopping cart items. A ShoppingCartItem contains a retail price item, the number of pounds of that item, and the total price for that item.

The OrderConfirmations JavaBeans Component

OrderConfirmations is a list of order confirmation objects. An OrderConfirmation contains order and confirmation objects, as discussed in Service Implementation.

The CheckoutFormBean JavaBeans Component

CheckoutFormBean checks the completeness of information entered into checkoutForm. If the information is incomplete, the bean populates error messages, and redisplays checkoutForm with the error messages. If the information is complete, order requests are constructed from the shopping cart and the information supplied to checkoutForm, and these orders are sent to each supplier. As each confirmation is received, an order confirmation is created and added to OrderConfirmations.

Several of the tags on the checkoutForm page have their required attributes set to true. This will cause the implementation to check whether the user enters values in these fields. The tag corresponding to the email component registers a custom validator on the email component, as explained in The checkoutForm Page. The code that performs the validation is the validateEmail method:

public void validateEmail(FacesContext context,
        UIComponent toValidate, Object value) {
    String message = "";
    String email = (String) value;
    if (email.indexOf(’@’) == -1) {
        ((UIInput)toValidate).setValid(false);
        message = CoffeeBreakBean.loadErrorMessage(context,
            CoffeeBreakBean.CB_RESOURCE_BUNDLE_NAME, "EMailError");
        context.addMessage(toValidate.getClientId(context),
            new FacesMessage(message));
    }
}

The CoffeeBreakBean JavaBeans Component

CoffeeBreakBean acts as the backing bean to the JSP pages. See Backing Beans for more information on backing beans. CoffeeBreakBean creates the ShoppingCart object, which defines the model data for the components on the orderForm page that hold the data about each coffee. CoffeeBreakBean also loads the RetailPriceList object. In addition, it provides the methods that are invoked when the buttons on the orderForm and checkoutAck are clicked. For example, the checkout method is invoked when the Checkout button is clicked because the tag corresponding to the Checkout button refers to the checkout method by means of its action attribute:

<h:commandButton id="checkoutLink" value="#{CBMessages.Checkout}"
    action="#{CoffeeBreakBean.checkout}" />

The checkout method returns a String, which the JavaServer Faces page navigation system matches against a set of navigation rules to determine what page to access next. The navigation rules are defined in a separate XML file, described in Resource Configuration.

The RetailPriceListServlet Servlet

RetailPriceListServlet responds to requests to reload the price list via the URL /loadPriceList. It simply creates a new RetailPriceList and a new ShoppingCart.

Because this servlet would be used by administrators of the Coffee Break server, it is a protected web resource. To load the price list, a user must authenticate (using basic authentication), and the authenticated user must be in the admin role.

Resource Configuration

A JavaServer Faces application usually includes an XML file that configures resources for the application. These resources include JavaBeans components, navigation rules, and others.

Two of the resources configured for the JavaServer Faces version of the Coffee Break server are the CheckoutForm bean and navigation rules for the orderForm page:

<managed-bean>
    <managed-bean-name>checkoutFormBean</managed-bean-name>
    <managed-bean-class>
        com.sun.cb.CheckoutFormBean
    </managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>firstName</property-name>
            <value>Coffee</value>
        </managed-property>
        <managed-property>
            <property-name>lastName</property-name>
            <value>Lover</value>
        </managed-property>
        <managed-property>
            <property-name>email</property-name>
            <value>jane@home</value>
        </managed-property>
        ...
    </managed-bean>
<navigation-rule>
    <from-view-id>/orderForm.jsp</from-view-id>
    <navigation-case>
         <from-outcome>checkout</from-outcome>
        <to-view-id>/checkoutForm.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

As shown in the managed-bean element, the checkoutForm bean properties are initialized with the values for the user, Coffee Lover. In this way, the hyperlink tag from orderForm is not required to submit these values in the request parameters.

As shown in the navigation-rule element, when the String, checkout, is returned from a method referred to by a component’s action attribute, the checkoutForm page displays.

Building, Packaging, Deploying, and Running the Coffee Break Application

The source code for the Coffee Break application is located in the directory tut-install/javaeetutorial5/examples/coffeebreak/. Within the cb directory are subdirectories for each web application (cb, cb-saaj, and cb-jaxws) and a directory, cb-common, for classes shared by the web applications. Each subdirectory contains a build.xml file. The web application subdirectories in turn contain a src subdirectory for Java classes and configuration files, and a web subdirectory for web resources.

Setting the Port

The JAX-WS and SAAJ services in the Coffee Break application run at the port that you specified when you installed the Application Server. The tutorial examples assume that the Application Server runs on the default port, 8080. If you have changed the port, you must update the port number in the following file before building and running the examples:

tut-install/javaeetutorial5/examples/coffeebreak/cb-common/src/com/sun/cb/common/
CoffeeBreak.properties

Update the port in the following URLs:

Building, Packaging, and Deploying the JAX-WS Coffee Supplier Service

    To build the JAX-WS service and client library and to package and deploy the JAX-WS service using NetBeans IDE, follow these steps:

  1. In NetBeans IDE, choose Open Project from the File menu.

  2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/coffeebreak/.

  3. Select the cb-jaxws folder.

  4. Select the Open as Main Project and Open Required Projects check boxes.

  5. Click Open Project.

  6. Right-click the cb-jaxws project and choose Build.

  7. Right-click the cb-jaxws project and choose Undeploy and Deploy.

    To build the JAX-WS service and client library and to package and deploy the JAX-WS service using Ant, follow these steps:

  1. In a terminal window, go to tut-install/javaeetutorial5/examples/coffeebreak/cb-jaxws/.

  2. Run ant. This task calls the default target, which compiles the source files of the JAX-WS service.

  3. Make sure the Application Server is running.

  4. Deploy the JAX-WS service:


    ant deploy
    

Building, Packaging, and Deploying the SAAJ Coffee Supplier Service

    To build the SAAJ service and client library and to package and deploy the SAAJ service using NetBeans IDE, follow these steps:

  1. In NetBeans IDE, choose Open Project from the File menu.

  2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/coffeebreak/.

  3. Select the cb-saaj folder.

  4. Select the Open as Main Project and Open Required Projects check boxes.

  5. Click Open Project.

  6. Right-click the cb-saaj project and choose Build.

  7. Right-click the cb-saaj project and choose Undeploy and Deploy.

    To build the SAAJ service and client library and to package and deploy the SAAJ service using Ant, follow these steps:

  1. In a terminal window, go to tut-install/javaeetutorial5/examples/coffeebreak/cb-saaj/.

  2. Run ant. This task calls the default target, which creates the client library and compiles the SAAJ service classes.

  3. Make sure the Application Server is started.

  4. Deploy the SAAJ service:


    ant deploy
    

Building, Packaging, and Deploying the Coffee Break Server

    To build, package, and deploy the Coffee Break server using NetBeans IDE, follow these steps:

  1. In NetBeans IDE, choose Open Project from the File menu.

  2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/coffeebreak/.

  3. Select the cb folder.

  4. Select the Open as Main Project and Open Required Projects check boxes.

  5. Click Open Project.

  6. Right-click the cb project and choose Build.

  7. Right-click the cb project and choose Undeploy and Deploy.

    To build, package, and deploy the Coffee Break server using Ant, follow these steps:

  1. In a terminal window, go to tut-install/javaeetutorial5/examples/coffeebreak/cb/.

  2. Run ant. This task calls the default target, which compiles the server classes.

  3. Make sure the Application Server is started.

  4. Deploy the Coffee Break server:


    ant deploy
    

Running the Coffee Break Client

After you have installed all the web applications, check that all the applications are running in the Admin Console. You should see cb, cb-saaj, and cb-jaxws in the list of applications.

You can run the Coffee Break client by opening this URL in a web browser:

http://localhost:8080/cbserver/

A page appears with the following message:


Redirecting to CoffeeBreak demo...click here otherwise.

In a few seconds, you should see a page like the one shown in Figure 36–2.

Figure 36–2 Order Form

Screen shot of Order Form page

After you have gone through the application screens, you will get an order confirmation that looks like the one shown in Figure 36–3.

Figure 36–3 Order Confirmation

Screen shot of Order Confirmation page

Removing the Coffee Break Application

    To remove the Coffee Break application, perform the following steps:

  1. Undeploy the JAX-WS service, SAAJ service, and the Coffee Break server using the Admin Console or by running ant undeploy in their respective directories.

  2. Stop the Application Server.

If you want to remove the build and dist directories, run ant clean in each directory, including tut-install/javaeetutorial5/examples/coffeebreak/cb-common/.

Chapter 37 The Duke’s Bank Application

This chapter describes the Duke’s Bank application, an online banking application. It covers the following topics:

Overview of the Duke’s Bank Application

Duke’s Bank has two clients: an application client used by administrators to manage customers and accounts, and a web client used by customers to access account histories and perform transactions. The web client is built using JavaServer Faces technology (see Chapter 10, JavaServer Faces Technology). The clients access the customer, account, and transaction information maintained in a database through enterprise beans. The Duke’s Bank application demonstrates the way that many of the component technologies presented in this tutorial (enterprise beans, application clients, and web components) are applied to provide a simple but functional application.

Figure 37–1 gives a high-level view of how the components of the Duke's Bank application interact.

Figure 37–1 Duke's Bank Application

A diagram showing the interaction of Duke's Bank's clients,
web components, enterprise beans, and entities.

Enterprise Beans

Let’s take a closer look at the access paths between the clients, enterprise beans, and database tables. The end-user clients (web and application clients) access only the session beans. Within the enterprise bean tier, the session beans use Java Persistence entities. On the back end of the application, the entities access the database tables that store the entity states.


Note –

The source code for these enterprise beans is in the tut-install/javaeetutorial5/examples/dukesbank/src/com/sun/ebank/ejb/ directory.


Session Beans

The Duke’s Bank application has three session beans: AccountControllerBean, CustomerControllerBean, and TxControllerBean. (Tx stands for a business transaction, such as transferring funds.) These session beans provide a client’s view of the application’s business logic. Hidden from the clients are the server-side routines that implement the business logic, access databases, manage relationships, and perform error checking.

The AccountControllerBean Session Bean

The business methods of the AccountControllerBean session bean perform tasks that fall into the following categories: creating and removing entities, managing the account-customer relationship, and getting the account information.

The following methods create and remove entities:

These methods of the AccountControllerBean session bean call the create and remove methods of the Account entity. The createAccount and removeAccount methods throw application exceptions to indicate invalid method arguments. The createAccount method throws an IllegalAccountTypeException if the type argument is neither Checking, Savings, Credit, nor Money Market. The createAccount method also looks up the specified customer exists by invoking the EntityManager.find method. If the result of this verification is null, the createAccount method throws a CustomerNotFoundException.

The following methods manage the account-customer relationship:

The Account and Customer entities have a many-to-many relationship. A bank account can be jointly held by more than one customer, and a customer can have multiple accounts.

In the Duke’s Bank application, the addCustomerToAccount and removeCustomerFromAccount methods of the AccountControllerBean session bean manage the account-customer relationship. The addCustomerToAccount method, for example, starts by verifying that the customer exists. To create the relationship, the addCustomerToAccount method first looks up the Customer and Account entities using the EntityManager.find method, then it calls the Account.addCustomer method to associate the customer with the account.

The following methods get the account information:

The AccountControllerBean session bean has two get methods. The getAccountsOfCustomer method returns all of the accounts of a given customer by invoking the getAccounts method of the Account entity. Instead of implementing a get method for every instance variable, the AccountControllerBean has a getDetails method that returns an object (AccountDetails) that encapsulates the entire state of an Account entity. Because it can invoke a single method to retrieve the entire state, the client avoids the overhead associated with multiple remote calls.

The CustomerControllerBean Session Bean

A client creates a Customer entity by invoking the createCustomer method of the CustomerControllerBean session bean. To remove a customer, the client calls the removeCustomer method, which invokes the EntityManager.remove method on the Customer instance.

The CustomerControllerBean session bean has two methods that return multiple customers: getCustomersOfAccount and getCustomersOfLastName. getCustomersOfAccount calls the getCustomers method of the Account entity. getCustomersOfLastName uses the Customer.FindByLastName named query to search the database for customers with a matching last name, which is a named parameter to the query.

The TxControllerBean Session Bean

The TxControllerBean session bean handles bank transactions. In addition to its get methods, getTxsOfAccount and getDetails, the TxControllerBean bean has several methods that change the balances of the bank accounts:

These methods access an Account entity to verify the account type and to set the new balance. The withdraw and deposit methods are for standard accounts, whereas the makeCharge and makePayment methods are for accounts that include a line of credit. If the type method argument does not match the account, these methods throw an IllegalAccountTypeException. If a withdrawal were to result in a negative balance, the withdraw method throws an InsufficientFundsException. If a credit charge attempts to exceed the account’s credit line, the makeCharge method throws an InsufficientCreditException.

The transferFunds method also checks the account type and new balance; if necessary, it throws the same exceptions as the withdraw and makeCharge methods. The transferFunds method subtracts from the balance of one Account instance and adds the same amount to another instance. Both of these steps must complete to ensure data integrity. If either step fails, the entire operation is rolled back and the balances remain unchanged. The transferFunds method, like all methods in session beans that use container-managed transaction demarcation, has an implicit Required transaction attribute. That is, you don’t need to explicitly decorate the method with a @TransactionAttribute annotation.

Java Persistence Entities

For each business entity represented in our simple bank, the Duke’s Bank application has a matching Java Persistence API entity:

The purpose of these entities is to provide an object view of these database tables: bank_account, bank_customer, and bank_tx. For each column in a table, the corresponding entity has an instance variable. Because they use the Java Persistence API, the entities contain no SQL statements that access the tables. The enterprise bean container manages all data in the underlying data source, including adding, updating, and deleting data from the database tables.

Unlike the session beans, the entities do not validate method parameters. The session beans check the parameters and throw the application exceptions, such as CustomerNotInAccountException and IllegalAccountTypeException. Consequently, if some other application were to include these entities, its session beans would also have to validate the method parameters. Validation code was not added to the entity’s methods, in order to keep the business logic separate from the entity data.

Helper Classes

The EJB JAR files include several helper classes that are used by the enterprise beans. The source code for these classes is in the following directory:

tut-install/javaeetutorial5/examples/dukesbank/dukesbank-ejb/src/java/com/sun/tutorial/
javaee/dukesbank/util/

Table 37–1 briefly describes the helper classes.

Table 37–1 Helper Classes for the Application’s Enterprise Beans

Class Name 

Description 

AccountDetails

Encapsulates the state of an Account instance. Returned by the getDetails method of AccountControllerBean.

CustomerDetails

Encapsulates the state of a Customer instance. Returned by the getDetails method of CustomerControllerBean.

Debug

Has simple methods for printing a debugging message from an enterprise bean. These messages appear on the standard output of the Application Server when it’s run with the --verbose option and in the server log.

DomainUtil

Contains validation methods: getAccountTypes, checkAccountType, and isCreditAccount.

TxDetails

Encapsulates the state of a Tx instance. Returned by the getDetails method of TxControllerBean.

Database Tables

A database table of the Duke’s Bank application can be categorized by its purpose: representing business entities.

Tables Representing Business Entities

Figure 37–2 shows the relationships between the database tables. The bank_customer and bank_account tables have a many-to-many relationship: A customer can have several bank accounts, and each account can be owned by more than one customer. This many-to-many relationship is implemented by the cross–reference table named bank_customer_account_xref. The tables named bank_account and bank_tx have a one-to-many relationship: A bank account can have many transactions, but each transaction refers to a single account.

Figure 37–2 Database Tables

A diagram showing the bank_customer, bank_account, bank_tx,
and bank_customer_account_xref database tables and their relationships.

Figure 37–2 uses several abbreviations. PK stands for primary key, the value that uniquely identifies a row in a table. FK is an abbreviation for foreign key, which is the primary key of the related table. Tx is short for transaction, such as a deposit or withdrawal.

Protecting the Enterprise Beans

In the Java EE platform, you protect an enterprise bean by specifying the security roles that can access its methods. In the Duke’s Bank application, you define two roles, bankCustomer and bankAdmin, because two categories of operations are defined by the enterprise beans.

A user in the bankAdmin role will be allowed to perform administrative functions: creating or removing an account, adding a customer to or removing a customer from an account, setting a credit line, and setting an initial balance. A user in the bankCustomer role will be allowed to deposit, withdraw, and transfer funds, make charges and payments, and list the account’s transactions. Notice that there is no overlap in functions that users in either role can perform.

The system restricts access to these functions to the appropriate role by setting method permissions on selected methods of the CustomerControllerBean, AccountControllerBean, and TxControllerBean enterprise beans. For example, by allowing only users in the bankAdmin role to access the createAccount method in the AccountControllerBean enterprise bean, you deny users in the bankCustomer role (or any other role) permission to create bank accounts.

Application Client

Sometimes, enterprise applications use a stand-alone client application for handling tasks such as system or application administration. For example, the Duke’s Bank application uses an application client to administer customers and accounts. This capability is useful in the event that the site becomes inaccessible for any reason or if a customer prefers to communicate things such as changes to account information by phone.

The application client shown in Figure 37–3 handles basic customer and account administration for the banking application through a Swing user interface. The bank administrator can perform any of the following functions on the respective tabs.

Figure 37–3 Application Client

Screenshot of the Duke's Bank application client for
the application admin.

Customer Info tab:

Account administration:

Error and informational messages appear in the bottom under Messages.

The Classes and Their Relationships

The source code for the application client is in the following directory:


tut-install/javaeetutorial5/examples/dukesbank/dukesbank-appclient/src/java/com/sun/tutorial/
javaee/dukesbank/client/

The application client is consists of a single class: BankAdmin.

BankAdmin Class

The BankAdmin class, which creates the user interface, is a Swing class that provides action methods that are called when certain events occur in the application, and methods that call the controller session beans. It was created using the NetBeans IDE Swing editor, Matisse.


Note –

Although BankAdmin was written using NetBeans IDE, you do not need to have NetBeans installed in order to run the application. If you want to alter the user interface, however, you do need to use NetBeans IDE.


The BankAdmin Constructor

The BankAdmin constructor creates the initial user interface, which consists of a menu bar, two tabs, and a message pane, by calling the initComponents method. The menu bar contains the standard File and Edit menus, the left tab is for viewing and updating customer information, the right tab is for viewing and updating account information, and the message pane contains a message area.

The initComponents method is automatically generated by NetBeans IDE. It creates all the user interface elements visible in BankAdmin.

Class Methods

The BankAdmin class provides methods that other objects call when they need to update the user interface. These methods are as follows:

The following methods interact with the controller session beans to create and update customer and account information:

The UI-elementMouseReleased methods are linked to the GUI controls in BankAdmin. They call the previous methods to enable/disable the GUI fields, and create/update accounts and customers.

Web Client

In the Duke’s Bank application, the web client is used by customers to access account information and perform operations on accounts. Table 37–2 lists the functions the client supports, the JSP pages the customer uses to perform the functions, and the backing beans ad other JavaBeans components that implement the functions.


Note –

The source code for the web client is in the tut-install/javaeetutorial5/examples/dukesbank/dukesbank-war/ directory.


Table 37–2 Web Client

Function 

JSP Pages 

JavaBeans Components 

Home page 

main.jsp

CustomerBean

Log on to or off of the application 

logon.jsp

logonError.jsp

logoff.jsp

CustomerBean

List accounts 

accountList.jsp

CustomerBean, AccountHistoryBean

List the history of an account 

accountHist.jsp

CustomerBean, AccountHistoryBean

Transfer funds between accounts 

transferFunds.jsp

transferAck.jsp

CustomerBean, TransferBean

Withdraw and deposit funds 

atm.jsp

atmAck.jsp

CustomerBean, ATMBean

Error handling 

error.jsp

none 

Figure 37–4 shows an account history screen.

Figure 37–4 Account History

Screenshot showing a page from the web client that displays
the account history.

Design Strategies

The main job of the JSP pages in the Duke’s Bank application is presentation. They use JavaServer Faces tags to represent UI components on the page, to bind the components to server-side data stored in backing beans, and wire the components to event-handling code. To maintain the separation of presentation and application behavior, most dynamic processing tasks are delegated to enterprise beans, custom tags, and JavaBeans components, including backing beans (see Backing Beans).

In the Duke’s Bank application, the JSP pages rely on backing beans and other JavaBeans components for interactions with the enterprise beans. In the Duke’s Bookstore application, discussed in Chapters Chapter 3, Getting Started with Web Applications to Including the Classes, Pages, and Other Resources, the BookDB JavaBeans component acts as a front end to a database.

In the Duke’s Bank application, CustomerBean acts as a facade to the enterprise beans. Through it, the backing beans can invoke methods on the enterprise beans. For example, TransferFundsBean can indirectly invoke the transferFunds method of the TxControllerBean enterprise bean by first calling getTxController on CustomerBean then calling transferFunds on the TxController interface.

The other backing beans have much richer functionality. ATMBean sets acknowledgment strings according to customer input, and AccountHistoryBean massages the data returned from the enterprise beans in order to present the view of the data required by the customer.

The web client uses a template mechanism implemented by custom tags (discussed in A Template Tag Library) to maintain a common look across all the JSP pages. The template mechanism consists of three components:

Finally, the web client uses logic tags from the JSTL core tag library to perform flow control and tags from the JSTL fmt tag library to localize messages and format currency.

Client Components

All the JavaBeans components used in the web client are instantiated by the managed bean facility (see Configuring a Bean) when they are encountered in the page, such as when an EL expression references the component. The managed bean facility is configured in the faces-config.xml file. The following managed-bean elements from the faces-config.xml file specify how AccountHistoryBean and CustomerBean are to be instantiated and stored in scope:

<managed-bean>
    <managed-bean-name>accountHistoryBean</managed-bean-name>
    <managed-bean-class>
        com.sun.tutorial.javaee.dukesbank.web.AccountHistoryBean
    </managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    ...
    <managed-property>
        <property-name>accountId</property-name>
        <value>#{param.accountId}</value>
    </managed-property>
    <managed-property>
    ...
</managed-bean>
<managed-bean>
    <managed-bean-name>customerBean</managed-bean-name>
    <managed-bean-class>
        com.sun.tutorial.javaee.dukesbank.web.CustomerBean
    </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

As shown by the preceding configurations, an AccountHistoryBean instance is saved into request scope under the name accountHistoryBean, and a CustomerBean instance is saved into session scope under the name customerBean. EL expressions use these names to reference the beans from a page. The managed bean configurations can also initialize bean properties with values. As shown in the preceding configuration, the accountId property of AccountHistoryBean is set to the expression #{param.accountId} when an instance of AccountHistoryBean is created. This expression references the accountId variable in the request parameter map. This is so that other pages in the application can pass the account ID to AccountHistoryBean and therefore make it available to the accountHist.jsp page.

Responsibility for managing the enterprise beans used by the web client rests with CustomerBean. It creates account and transaction controller enterprise beans and provides methods for retrieving the beans.

When instantiated, the CustomerBean component uses @EJB annotations to inject references to the enterprise beans. Because these enterprise beans apply to a particular customer or session, CustomerBean is stored in session.

public class CustomerBean {
        
        @EJB
        private AccountController accountController;

        @EJB
        private TxController txController;
        ...
}

CustomerBean also does the following:

Because CustomerBean is in session, it is a convenient place to keep account information so that the backing beans and their associated pages can pass this information between themselves.

The page fragment template/links.jsp generates the list of bank function links at the top of every page. Notice that the customer is retrieved from the userPrincipal object, which is set when the customer logs in (see Protecting the Web Client Resources). After the customer is set, the page can retrieve the collection of accounts from CustomerBean.

As shown by the following code from links.jsp, the ID of the first account in the collection of accounts is set into request scope. The setPropertyActionListener tag is nested inside the commandLink tag, which represents the hyperlink that launches the atm.jsp page. This setPropertyActionListener tag causes the account ID to be set in request scope when the hyperlink is clicked.

...
    <c:set var="accountId" scope="request"
             value="${customerBean.accounts[0].accountId}"/>
    <h:commandLink value="#{bundle.ATM}" action="atm">
        <f:setPropertyActionListener
                 target="#{requestScope.accountId}"
                 value="#{customerBean.accounts[0].accountId}"/>
    </h:commandLink>
...

Request Processing

When a user clicks on a button or a hyperlink, the application navigates to a new page or reloads the current page. Navigation to all pages listed in Table 37–2 is configured in the web/WEB-INF/faces-config.xml file using a set of navigation rules.

As described in Configuring Navigation Rules, the JavaServer Faces navigation mechanism matches a logical outcome String or an action method to one of the navigation rules to determine which page to open next. The button or hyperlink that the user clicks specifies the logical outcome String or action method with its action attribute.

Although it’s not necessary to do so, the web client of Duke’s Bank uses an Java SE Enum class to encapsulate all the possible logical outcomes for the application:

public enum Navigation  {
    main,
     accountHist,
     accountList,
     atm,
     atmAck,
     transferFunds,
     transferAck,
     error,
     logout;

    public Object action() {
        return this;
    }
}

If you are not familiar with enums, see http://java.sun.com/javase/6/docs/technotes/guides/language/enums.html.

A managed bean is needed to expose the enum to the expression language so that a page can access its logical outcomes. In this case, the Navigation enum class is accessed through the NavigationEnumBean:

public class NavigationEnumBean extends EnumManagedBean {
    public NavigationEnumBean() {
        super(Util.Navigation.class);
    }
}

NavigationEnumBean extends a special bean class that includes a method to return an enum constant, which represents a logical outcome:

public Enum getEnum(String enumName) {
    return Enum.valueOf(e, enumName);
}

The application also includes a custom EL resolver, EnumResolver, which resolves expressions that reference an instance of this bean class. You create a resolver if you want expressions to particular kinds of objects resolved in a special way that is not already supported by the EL mechanism. See Resolving Expressions for more information on EL resolvers.

The resolver calls the bean’s getEnum method from its getValue method to return the enum constant:

public Object getValue(ELContext elContext, Object base, Object property) {
    if ((base != null && property != null)
        && base instanceof EnumManagedBean) {
            elContext.setPropertyResolved(true);
            return
                 ((EnumManagedBean)base)
                    .getEnum(property.toString());
    }
    return null;
}

A tag’s action attribute references a particular constant of the enum to specify a logical outcome. The following commandLink tag appears on the links.jsp page:

<h:commandLink value="#{bundle.Logoff}"
    action="#{navigation.logout.action}"/>

The action attribute has the expression #{navigation.logout.action} to invoke the action method of the Navigation enum. This returns the enum constant, representing the logical outcome, logout.

The following piece of a navigation rule configuration in the faces-config.xml file corresponds to the action attribute expression of the preceding commandLink tag. It causes the logoff.jsp page to open if the logout logical outcome is returned.

<navigation-rule>
    ...
    <navigation-case>
        <description>
            Any action that returns "logout" should go to the
            logoff page and invalidate the session.
        </description>
        <from-action>logout</from-action>
        <to-view-id>/logoff.jsp</to-view-id>
</navigation-rule>

When a page in the application is rendered, it is constructed with the aid of a template mechanism. Every page includes the template.jsp page, which in turn includes certain subcomponents, such as banner.jsp, into the page depending on which page is being rendered. The screendefinitions.jspf page, included in template.jsp, determines which page to render based on the current view ID, which identifies the UI component tree that represents the page to be rendered. The screendefinitions.jspf page accesses the view ID with this expression from its definition tag:

<tt:definition name="bank"
         screen="${facesContext.viewRoot.viewId}">

Based on the view ID, the templating mechanism will include specific components into the page.

Protecting the Web Client Resources

In the JavaEE platform, you protect a web resource from anonymous access by specifying which security roles can access the resource. The web container guarantees that only certain users acting in those roles can access the resource. For the web container to enforce the security constraint, the application must specify a means for users to identify themselves, and the web container must support mapping a role to a user.

In the Duke’s Bank web client, you restrict all the URLs listed in Table 37–2 to the security role bankCustomer. The application requires users to identify themselves by means of the form-based login mechanism. When a customer tries to access a web client URL and has not been authenticated, the web container displays the JSP page logon.jsp. This page contains an HTML form that requires a customer to enter an identifier and password. This form is rendered by a JavaServer Faces custom component. A custom tag represents the component on the page. In the following piece of logon.jsp, the <db:formBasedLogin> tag represents the custom component:

<f:view>
...
<h:outputText value="#{bundle.Logon}"/>
 <h:outputText value="#{bundle.Submit}"/>.</h3>
<br><br>
<db:formBasedLogin  />
</f:view>

Note that there is no h:form tag. This is because the custom component renders the form tag along with the complete HTML form that customers use to log in:

<form action="j_security_check" method=post>
<table>
<tr>
    <td align="center" >
    <table border="0">
    <tr>
    <td><b><fmt:message key="CustomerId"/></b></td>
    <td>
        <input type="text" size="15" name="j_username">
     </td>
    </tr>
    <tr>
    <td><b><fmt:message key="Password"/></b></td>
    <td>
         <input type="password" size="15" name="j_password">
    </td>
    ...
</form>

    Note that the action invoked by the form, j_security_check, is specified by the Java Servlet specification, as are the request parameters j_username and j_password. The web container retrieves this information, maps it to a security role, and verifies that the role matches that specified in the security constraint. In order for the web container to check the validity of the authentication information and perform the mapping, you must perform these two steps when you deploy the application:

  1. Add the customer’s group, ID, and password to the default realm of the container using the Admin Console.

  2. Map the bankCustomer role to the customer or the customer’s group in the deployment descriptor.

After the customer has been authenticated, the identifier provided by the customer is used as a key to identify the customer’s accounts. The identifier is retrieved from the FacesContext object by the CustomerBean constructor, which saves it into the customerId property:

customerId = Long.parseLong(FacesContext.getCurrentInstance()
    .getExternalContext().getUserPrincipal().getName());

Building, Packaging, Deploying, and Running the Duke's Bank Application

To build the Duke’s Bank application, you must have installed the tutorial bundle as described in . When you install the bundle, the Duke’s Bank application files are located in the tut-install/javaeetutorial5/examples/dukesbank/ directory. This directory contains the configuration files for creating the EAR, dukesbank.ear. The EAR consists of the following three modules:

After you build the source code, all the sub-modules will be built into their respective module packages, and the resulting EAR file will reside in the tut-install/javaeetutorial5/examples/dukesbank/dist/ directory.

Setting Up the Servers

Before you can package, deploy, and run the example, you must first set up the Java DB database server with customer and account data, and you must add some resources to the Application Server.

Starting the Application Server

Before you can start this tutorial, the Application Server must be running. For information on starting the Application Server, see Starting and Stopping the Application Server.

Creating the Bank Database in NetBeans IDE

To create the database tables used in Duke’s Bank, follow the instructions in Creating the Database Tables in NetBeans IDE.

Creating the Bank Database Using Ant

    In Duke’s Bank, the database tables will be created and populated before deploying the application. This happens automatically when you run the deploy task. You can manually reset the database to its original state by following these steps:

  1. In a terminal window or command prompt, go to the tut-install/javaeetutorial5/examples/dukesbank/ directory.

  2. Execute the following command:


    ant create-tables
    

This task executes the SQL commands contained in tut-install/javaeetutorial5/examples/common/sql/javadb/tutorial.sql. The SQL commands delete any existing tables, create new tables, and insert the initial data in the tables.

Adding Users and Groups to the File Realm

To enable the Application Server to determine which users can access enterprise bean methods and resources in the web client, add users and groups to the server’s file security realm using the Admin Console following the procedures described in Managing Users and Groups on the Application Server. Add the users and groups listed in Table 37–3.

Table 37–3 Duke’s Bank Users and Groups

User 

Password 

Group 

200

javaee

bankCustomer

bankadmin

javaee

bankAdmin

Building, Packaging, and Deploying Duke’s Bank Using NetBeans IDE

    Follow these instructions to build, package, deploy, and run the Duke’s Bank example application to your Application Server instance using NetBeans IDE.

  1. In NetBeans IDE, select File->Open Project.

  2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/.

  3. Select the dukesbank folder.

  4. Select the Open as Main Project and Open Required Projects check boxes.

  5. Click Open Project.

  6. In the Projects tab, right-click the dukesbank project and select Undeploy and Deploy.

    You will see the output in the Output tab.

Building, Packaging, and Deploying Duke’s Bank Using Ant

To compile and package the enterprise beans, application client, and web client into dukesbank.ear, go to the tut-install/javaeetutorial5/examples/dukesbank/ directory of the tutorial distribution and execute the command:


ant

Run the following command to deploy dukesbank.ear:


ant deploy

This task calls the create-tables task to initialize the database tables.

Running the Duke's Bank Application Client Using NetBeans IDE

    To run the application client in NetBeans IDE, follow these steps:

  1. In NetBeans IDE, select the dukesbank project in the Projects pane.

  2. Right-click dukesbank and select Run.

  3. At the login prompts, type bankadmin for the user name and javaee for the password. You should see the application client, as shown in Figure 37–5.

    You can now create customers and accounts using the application client.

Running the Duke's Bank Application Client Using Ant

    To run the application client, follow these steps:

  1. In a terminal window, go to tut-install/javaeetutorial5/examples/dukesbank/.

  2. Enter the following command:


    ant run
    
  3. At the login prompts, type bankadmin for the user name and javaee for the password. You should see the application client, as shown in Figure 37–5.

    You can now create customers and accounts using the application client.

Figure 37–5 BankAdmin Application Client

A screenshot of the Duke's Bank application client.

Running the Duke's Bank Web Client

    To run the web client, follow these steps:

  1. Open the bank URL, http://localhost:8080/bank/main.faces, in a web browser.

  2. The application displays the login page. Enter 200 for the customer ID and javaee for the password. Click Submit.

  3. Select an application function: Account List, Transfer Funds, ATM, or Logoff. When you have a list of accounts, you can get an account history by selecting an account link.


    Note –

    The first time you select a new page, particularly a complicated page such as an account history, it takes some time to display because the Application Server must translate the page into a servlet class and compile and load the class.


If you select Account List, you will see the screen shown in Figure 37–6.

Figure 37–6 Account List

Screenshot of the account list in a page of the Duke's
Bank web client.