The JavaTM Web Services Tutorial
Home
TOC
Index
PREV TOP NEXT
Divider

JAXM Distributor Service

The JAXM distributor service is simply the arrangements that the distributor and the Coffee Break have made regarding their exchange of XML documents. These arrangements include what kinds of messages they will send, the form of those messages, and what kind of JAXM messaging they will do. If they had agreed to do one-way messaging, they would also have had to use messaging providers that talk to each other and had to use the same profile. In this scenario, the parties have agreed to use request-response messaging, so a messaging provider is not needed.

The Coffee Break server sends two kinds of messages:

The JAXM coffee supplier responds with two kinds of messages:

All of 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 server and the JAXM distributor are specified by the following DTDs:

These DTDs may be found at

<JWSDP_HOME>/docs/tutorial/examples/cb/jaxm/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 each of 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 JAXM API.

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

<JWSDP_HOME>/docs/tutorial/examples/cb/jaxm/src/com/sun/cb
 

JAXM Client

The Coffee Break server, which is the JAXM client in this scenario, 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 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 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(
 "http://localhost:8080/jaxm-coffee-supplier/getPriceList");
SOAPMessage response = con.call(msg, endpoint);
con.close();
 

When the call method is executed, Tomcat 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.) Tomcat knows to execute PriceListServlet because the web.xml file at <JWSDP>/docs/tutorial/examples/cb/jaxm/web/ 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) {
  priceItems.add(
      new PriceItemBean(list.elementAt(i).toString(), 
      new BigDecimal(list.elementAt(i + 1).toString())));
  System.out.print(list.elementAt(i) + "        ");
  System.out.println(list.elementAt(i + 1));
}

Date today = new Date();
Date endDate = DateHelper.addDays(today, 30);
PriceListBean plb = 
        new PriceListBean(today, endDate, 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 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 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(
  "http://localhost:8080/jaxm-coffee-supplier/orderCoffee");
SOAPMessage reply = con.call(msg, endpoint);
con.close();
 

Because the web.xml file maps the given endpoint to ConfirmationServlet, Tomcat 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();

SimpleDateFormat df = new 
        SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy");
Date date = df.parse(shippingDate);
ConfirmationBean cb = new ConfirmationBean(id, date); 
 

JAXM Service

The JAXM coffee distributor, the JAXM server in this scenario, provides the response part of the request-response paradigm. When JAXM 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 with 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. Then it declares the MessageFactory object msgFactory, which will be used to create a SOAPMessage object that has the headers and content of the original request message.

public class PriceListServlet extends HttpServlet { 
  static MessageFactory fac = null; 
  static { 
    try { 
      fac = MessageFactory.newInstance();
    } catch (Exception ex) { 
      ex.printStackTrace();
    } 
  };
 
  MessageFactory msgFactory;
 

Every servlet has an init method. This init method initializes the servlet with the configuration information that Tomcat passed to it. Then it simply initializes msgFactory with the default implementation of the MessageFactory class.

public void init(ServletConfig servletConfig) 
                  throws ServletException { 
  super.init(servletConfig);
  try { 
    // Initialize it to the default.
    msgFactory = MessageFactory.newInstance();
  } catch (SOAPException ex) { 
    throw new ServletException(
      "Unable to create message factory" + ex.getMessage()); 
  } 
} 
 

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.) Tomcat 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 = msgFactory.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. 
      OutputStream os = resp.getOutputStream(); 
      reply.writeTo(os); 
      os.flush(); 
    } else 
      resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
 
  } catch (Exception ex) { 
      throw new ServletException( "JAXM POST failed " +
                  ex.getMessage()); 
  } 
}
 

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

static MimeHeaders getHeaders(HttpServletRequest req) {

  Enumeration enum = req.getHeaderNames(); 
  MimeHeaders headers = new MimeHeaders();
 
  while (enum.hasMoreElements()) { 
    String headerName = (String)enum.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 Tomcat 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()) { 
    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 fac to create the SOAPMessage object message and then populates it with the distributor'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 distributor's price list. The onMessage method in ConfirmationServlet (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 = fac.createMessage();

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

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

    coffee Name coffeeN = envelope.createName("coffee");
    SOAPElement coffee = list.addChildElement(coffeeN);

    Name coffeeNm1 = envelope.createName("coffee-name");
    SOAPElement coffeeName =
                coffee.addChildElement(coffeeNm1);
    coffeeName.addTextNode("Arabica");

    Name priceName1 = envelope.createName("price"); 
    SOAPElement price1 = coffee.addChildElement(priceName1);
    price1.addTextNode("4.50");

    Name coffeeNm2 = envelope.createName("coffee-name"); 
    SOAPElement coffeeName2 =
                    coffee.addChildElement(coffeeNm2);
    coffeeName2.addTextNode("Espresso");

    Name priceName2 = envelope.createName("price"); 
    SOAPElement price2 = coffee.addChildElement(priceName2);
    price2.addTextNode("5.00");

    Name coffeeNm3 = envelope.createName("coffee-name"); 
    SOAPElement coffeeName3 =
                    coffee.addChildElement(coffeeNm3);
    coffeeName3.addTextNode("Dorada");

    Name priceName3 = envelope.createName("price"); 
    SOAPElement price3 = coffee.addChildElement(priceName3);
    price3.addTextNode("6.00");

    Name coffeeNm4 = envelope.createName("coffee-name"); 
    SOAPElement coffeeName4 =
                    coffee.addChildElement(coffeeNm4);
    coffeeName4.addTextNode("House Blend");

    Name priceName4 = envelope.createName("price"); 
    SOAPElement price4 = coffee.addChildElement(priceName4);
    price4.addTextNode("5.00");

    message.saveChanges();

  } catch(Exception e) { 
    e.printStackTrace(); 
  } 
  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 with 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 with the order ID and shipping date. The shipping date is calculated as today's date plus two days.

public SOAPMessage onMessage(SOAPMessage message) {

  SOAPMessage confirmation = null;

  try {

    //retrieve the orderID elementfrom the message received
    SOAPBody sentSB = message.getSOAPPart().
                      getEnvelope().getBody();
    Iterator sentIt = sentSB.getChildElements();
    SOAPBodyElement sentSBE =
                  (SOAPBodyElement)sentIt.next();
    Iterator sentIt2 = sentSBE.getChildElements();
    SOAPElement sentSE = (SOAPElement)sentIt2.next();

    //get the text for orderID to put in confirmation
    String sentID = sentSE.getValue();

    //create the confirmation message
    confirmation = fac.createMessage();
    SOAPPart sp = confirmation.getSOAPPart();
    SOAPEnvelope env = sp.getEnvelope();
    SOAPBody sb = env.getBody();
    Name newBodyName = env.createName("confirmation",
          "Confirm", "http://sonata.coffeebreak.com");
    SOAPBodyElement confirm = 
          sb.addBodyElement(newBodyName);

    //create the orderID element for confirmation
    Name newOrderIDName = env.createName("orderId");
    SOAPElement newOrderNo =
              confirm.addChildElement(newOrderIDName);
    newOrderNo.addTextNode(sentID);

     //create ship-date element
     Name shipDateName = env.createName("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;
}
 
Divider
Home
TOC
Index
PREV TOP NEXT
Divider

This tutorial contains information on the 1.0 version of the Java Web Services Developer Pack.

All of the material in The Java Web Services Tutorial is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.