FAQ
History
PreviousHomeNext Search
Feedback
Divider

Coffee Break Server

The Coffee Break Server uses servlets, JSP pages, and JavaBeans components to dynamically construct HTML pages for consumption by a Web browser client. The JSP pages use the template tag library discussed in A Template Tag Library to achieve a common look and feel among the HTML pages, and many of the JSTL custom tags discussed in Chapter 6 to minimize the use of scripting.

The Coffee Break Server implementation is organized along the Model-View-Controller design pattern. The Dispatcher servlet is 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 12-1.

Table 12-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

To browse the code for the Coffee Break server in the IDE, mount the filesystem
<INSTALL>/j2eetutorial/examples/cb/cbservice.

Service-Oriented JavaBeans Components

The Coffee Break server uses the following JavaBeans components to represent data returned from the JAX-RPC and JAXM Web services:

The components are contained in the cbservice package, which is found in the directory <INSTALL>/j2eetutorial/examples/cb/WEB-INF/classes/cbservice

JAX-RPC Client

The JAX-RPC client is generated directly from the Web service and is located in <INSTALL>/j2eetutorial/examples/cb/jaxrpc/clientutil. Since the JAX-RPC client returns JavaBeans components defined in the clientutil package under the jaxrpc directory and the Coffee Break server class that accesses those components (see CheckoutFormBean) uses types defined in the cbservice package, CheckoutFormBean converts clientutil types to equivalent types defined in the cbservice package.

JAXM Client

The Coffee Break server sends requests to its JAXM distributor. Because the request-response form of JAXM messaging is being used, the client applications use 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 <INSTALL>/j2eetutorial/examples/cb/WEB-INF/classes/cbservice/JAXMPriceListRequest and <INSTALL>/j2eetutorial/examples/cb/WEB-INF/classes/cbservice/JAXMOrderRequest.

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 JAXMPriceListRequest, 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 so that it can create the SOAPMessage object msg.

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

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

The next step is to access the message's SOAPEnvelope object, which will be used to create a Name object for each new element that is created. It is also used to access the SOAPBody object, to which the message's content will be added.

SOAPPart part = msg.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPBody body = envelope.getBody(); 

The file price-list.dtd specifies that the top-most 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 Name object to identify it, which is created by the Envelope method createName. 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 = envelope.createName("request-prices",
      "RequestPrices", "http://sonata.coffeebreak.com");
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.

Name requestName = envelope.createName("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 JAXM 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 JAXM coffee supplier. 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 JAXM distributor's price list. (PriceListServlet is discussed in Returning the Price List.) The application server knows to execute PriceListServlet because the web.xml file at <INSTALL>/j2eetutorial/examples/cb/jaxm/service/WEB-INF maps the given endpoint 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. Notice that the SOAPEnvelope object is not accessed separately because it is not needed for creating Name objects, as it was in the previous section.

Vector list = new Vector();
SOAPBody responseBody = response.getSOAPPart().
                    getEnvelope().getBody(); 

The next step is to retrieve the SOAPBodyElement object. The method getChildElements returns an Iterator object that contains all of 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 JAXM coffee distributor 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 last 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 priceItems = new ArrayList(); 
for (int i = 0; i < list.size(); i = i + 2) {
    new PriceItemBean(); 
    pib.setCoffeeName(list.elementAt(i).toString());
    pib.setPricePerPound(new 
      BigDecimal(list.elementAt(i + 1).toString()));
    priceItems.add(pib);
}
Date today = new Date();
Date endDate = DateHelper.addDays(today, 30);
plb = new PriceListBean();
plb.setStartDate(today);
plb.setEndDate(endDate);
plb.setPriceItems(priceItems); 

Ordering Coffee

The other kind of message that the Coffee Break server can send to the JAXM distributor is an order for coffee. This is done in the placeOrder method of JAXMOrderRequest, which follows the DTD coffee-order.dtd.

Creating the Order

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

SOAPConnectionFactory scf =
                  SOAPConnectionFactory.newInstance();
SOAPConnection con = scf.createConnection();
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage msg = mf.createMessage();

SOAPPart part = msg.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPBody body = envelope.getBody(); 

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.

Name bodyName = envelope.createName("coffee-order", "PO", 
               "http://sonata.coffeebreak.com"); 
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.

Name orderIDName = envelope.createName("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.

Name childName = envelope.createName("customer");
SOAPElement customer = order.addChildElement(childName);

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

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

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

childName = envelope.createName("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 = envelope.createName("address");
SOAPElement address = order.addChildElement(childName); 
childName = envelope.createName("street");
SOAPElement street = address.addChildElement(childName);
street.addTextNode(orderBean.getAddress().getStreet());

childName = envelope.createName("city");   
SOAPElement city = address.addChildElement(childName); 
city.addTextNode(orderBean.getAddress().getCity());

childName = envelope.createName("state");
SOAPElement state = address.addChildElement(childName);
state.addTextNode(orderBean.getAddress().getState());

childName = envelope.createName("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.

for (Iterator it = orderBean.getLineItems().iterator();
                              it.hasNext(); ; ) {
  LineItemBean lib = (LineItemBean)it.next();

  childName = envelope.createName("line-item");
  SOAPElement lineItem = 
      order.addChildElement(childName);

  childName = envelope.createName("coffeeName");
  SOAPElement coffeeName = 
      lineItem.addChildElement(childName);
  coffeeName.addTextNode(lib.getCoffeeName());

  childName = envelope.createName("pounds");
  SOAPElement pounds = 
      lineItem.addChildElement(childName);
  pounds.addTextNode(lib.getPounds().toString());

  childName = envelope.createName("price");
  SOAPElement price = 
      lineItem.addChildElement(childName);
  price.addTextNode(lib.getPrice().toString());

}
  //total
  childName = envelope.createName("total");
  SOAPElement total = 
      order.addChildElement(childName);
  total.addTextNode(orderBean.getTotal().toString()); 
} 

With the order complete, the application sends the message and closes the connection.

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

Because the web.xml file maps the given endpoint 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.getSOAPPart().getEnvelope().getBody();
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();
Date date = df.parse(shippingDate);
Calendar cal = new GregorianCalendar();
cal.setTime(date);
cb = new ConfirmationBean();
cb.setOrderId(id);
cb.setShippingDate(cal);  

JSP Pages

orderForm

orderForm displays the current contents of the shopping cart. The first time the page is requested, the quantities of all the coffees are 0. Each time the customer changes the coffees amounts and clicks the Update button, the request is posted back to orderForm. The Dispatcher servlet 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 link.

checkoutForm

checkoutForm is used to collect delivery and billing information for the customer. When the Submit button is clicked, the request is posted to the checkoutAck page. However, the request is first handled by the Dispatcher, which invokes the validate method of checkoutFormBean If the validation does not succeed, the requested page is reset to checkoutForm, with error notifications in each invalid field. If the validation succeeds, checkoutFormBean submits suborders to each distributor and stores the result in the request-scoped OrderConfirmations JavaBeans component and control is passed to checkoutAck.

checkoutAck

checkoutAck simply displays the contents of the OrderConfirmations JavaBeans component, which is a list of the suborders comprising an order and the ship dates of each suborder.

JavaBeans Components

RetailPriceList

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 distributor. 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 distributors.

It first performs a JAXR lookup to determine the JAX-RPC service endpoints. It then queries each JAX-RPC service for a coffee price list. Finally it queries the JAXM service for a price list. The two price lists are combined and a retail price per pound is determined by adding a 35% markup to the wholesale prices.

Discovering the JAX-RPC Service

Instantiated by RetailPriceList, JAXRQueryByName connects to the registry server and searches for coffee distributors registered with the name JAXRPCCoffeeDistributor in the executeQuery method. The method returns a collection of organizations which contain services. Each service is accessible via a service binding or URI. RetailPriceList makes a JAX-RPC call to each URI.

ShoppingCartItem

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

OrderConfirmation

OrderConfirmations is a list of order confirmation objects. An order confirmation contains order and confirmation objects.

CheckoutFormBean

CheckoutFormBean checks the completeness of information entered into checkoutForm. If the information is incomplete, the bean populates error messages and Dispatcher 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 are sent to each distributor. As each confirmation is received, an order confirmation is created and added to OrderConfirmations. Note that since the JAX-RPC client returns JavaBeans components defined in the clientutil package, CheckoutFormBean converts types returned from the JAX-RPC client to equivalent types defined in the cbservice package.

if (allOk) {
  String orderId = CCNumber;
  cbconfirmation = new ConfirmationBean();
  cbconfirmation.setOrderId(orderId);    
  AddressBean cbaddress = new AddressBean();
  cbaddress.setStreet(street); 
  cbaddress.setCity(city);
  cbaddress.setState(state);
  cbaddress.setZip(zip);
  CustomerBean cbcustomer= new CustomerBean();
  cbcustomer.setFirstName(firstName);
  cbcustomer.setLastName(lastName); 
  cbcustomer.setPhoneNumber("(" + areaCode+ ") " +
    + phoneNumber);
  cbcustomer.setEmailAddress(email);

  for(Iterator d = rpl.getDistributors().iterator();
    d.hasNext(); ) {
    String distributor = (String)d.next();
    System.out.println(distributor);
    ArrayList lis = new ArrayList();
    BigDecimal price = new BigDecimal("0.00");
    BigDecimal total = new BigDecimal("0.00");
    for(Iterator c = cart.getItems().iterator(); 
      c.hasNext(); ) {
      ShoppingCartItem sci = (ShoppingCartItem) c.next();
      if ((sci.getItem().getDistributor()).
          equals(distributor) &&
          sci.getPounds().floatValue() > 0) {
        price = sci.getItem().
          getWholesalePricePerPound().
          multiply(sci.getPounds());  
        total = total.add(price);
        LineItemBean li = new LineItemBean();
          li.setCoffeeName(sci.getItem().getCoffeeName());
          li.setPounds(sci.getPounds(),);
          li.setPrice(sci.getItem().
        getWholesalePricePerPound());
        lis.add(li);
      }
    }

      if (!lis.isEmpty()) {
        OrderBean cborder = new OrderBean();
        cborder.setId(orderId);
        cborder.setCustomer(cbcustomer);
        cborder.setLineItems(lis);
        cborder.setTotal(total);
        cborder.setAddress(cbaddress);
        String cbpropsName = "cbservice.CoffeeServices";

        ResourceBundle cbpropBundle =
          ResourceBundle.getBundle(cbpropsName);

        String JAXMOrderURL = cbpropBundle.
          getString("JAXMOrder.url");

        if (distributor.equals(JAXMOrderURL)) {
        JAXMOrderRequest or = 
          new JAXMOrderRequest(JAXMOrderURL);
          cbconfirmation = or.placeOrder(cborder);
        } else {
          clientutil.OrderGenClient.
            CustomerBean customer = 
            new clientutil.OrderGenClient.
            CustomerBean();
          customer.setFirstName(firstName);
          customer.setLastName(lastName); 
          customer.setPhoneNumber("(" + areaCode+ ") " +
            phoneNumber);
          customer.setEmailAddress(email); 
          clientutil.OrderGenClient.
            AddressBean address = 
            new clientutil.OrderGenClient.
            AddressBean();
          address.setStreet(street);
          address.setCity(city);
          address.setState(state);
          address.setZip(zip);
          clientutil.OrderGenClient.
            OrderBean order = 
            new clientutil.OrderGenClient.
            OrderBean();  
          ArrayList newlis = new ArrayList();
          for(Iterator c = newlis.iterator();c.hasNext();) {
            LineItemBean li = (LineItemBean) c.next();
            clientutil.OrderGenClient.
            LineItemBean lib = 
              new clientutil.OrderGenClient.
              LineItemBean();
            lib.setCoffeeName(li.getCoffeeName());
            lib.setPounds(li.getPounds());
            lib.setPrice(li.getPrice());
            newlis.add(lib);
          }            
          order.setId(orderId);
          order.setCustomer(customer);
          order.setLineItems(newlis);
          order.setTotal(total);
          order.setAddress(address);
          OrderCaller ocaller = 
            new OrderCaller(distributor);
          clientutil.OrderGenClient.
          ConfirmationBean confirmation =
            = ocaller.placeOrder(order);
          cbconfirmation.setShippingDate(
            confirmation.getShippingDate());
        }
        OrderConfirmation oc = new 
          OrderConfirmation(cborder, cbconfirmation);
      ocs.add(oc);
    }
  }
} 

RetailPriceListServlet

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

Since this servlet would be used by administrators of the Coffee Break Server, it is a protected Web resource. In order to load the price list, a user must authenticate (using basic authentication) and the authenticated user must be in the admin role.

To view the security settings for cbservice:

  1. In the IDE, mount the filesystem
    <INSTALL>/j2eetutorial/examples/cb/cbservice.
  2. Expand the WEB-INF directory.
  3. Select the web.xml file.
  4. Select the Security property sheet. You will notice that the login configuration is specified to be BASIC, a security role admin is defined, and a security constraint allows the admin role to access the resources in the WRCollection. If you edit WRCollection, you will notice that it contains the URL /loadPriceList.
Divider
FAQ
History
PreviousHomeNext Search
Feedback
Divider

All of the material in The J2EE Tutorial for the Sun ONE Platform is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.