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.soappackage).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
<INSTALL>/javaeetutorial5/examples/coffeebreak/cb-saaj/dtds/. Thedtdsdirectory 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:
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
SOAPConnectionmethodcallto send messages.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
PriceListRequestandOrderRequest.Sending the Request
This section covers the code for creating and sending the request for an updated price list. This is done in the
getPriceListmethod ofPriceListRequest, which follows the DTDprice-list.dtd.The
getPriceListmethod begins by creating the connection that will be used to send the request. Then it gets the defaultMessageFactoryobject to be used for creating theSOAPMessageobjectmsg.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
SOAPBodyobject, to which the message's content will be added.The file
price-list.dtdspecifies that the topmost element inside the body isrequest-pricesand that it contains the elementrequest. The text node added torequestis the text of the request being sent. Every new element that is added to the message must have aQNameobject to identify it. The following lines of code create the top-level element in theSOAPBodyobjectbody. The first element created in aSOAPBodyobject is always aSOAPBodyElementobject.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
requestto the elementrequest-prices(represented by theSOAPBodyElementrequestPrices). 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 methodsaveChangeson 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
SOAPMessageobjectmsg, 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. TheSOAPConnectionobjectconis used to send the message, and because it is no longer needed, it is closed.When the
callmethod is executed, the Application Server executes the servletPriceListServlet. This servlet creates and returns aSOAPMessageobject whose content is the SAAJ supplier's price list. (PriceListServletis discussed in Returning the Price List.) The Application Server knows to executePriceListServletbecause we map the given endpoint to that servlet.Extracting the Price List
This section demonstrates (1) retrieving the price list that is contained in
response, theSOAPMessageobject returned by the methodcall, and (2) returning the price list as aPriceListBean.The code creates an empty
Vectorobject that will hold thecoffee-nameandpriceelements that are extracted fromresponse. Then the code usesresponseto access itsSOAPBodyobject, which holds the message's content.The next step is to retrieve the
SOAPBodyElementobject. The methodgetChildElementsreturns anIteratorobject that contains all the child elements of the element on which it is called, so in the following lines of code,it1contains theSOAPBodyElementobjectbodyEl, which represents theprice-listelement.Iterator it1 = responseBody.getChildElements(); while (it1.hasNext()) { SOAPBodyElement bodyEl = (SOAPBodyElement)it1.next();The
Iteratorobjectit2holds the child elements ofbodyEl, which representcoffeeelements. Calling the methodnextonit2retrieves the first coffee element inbodyEl. As long asit2has another element, the methodnextwill return the nextcoffeeelement.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-nameandpriceelements contained init3. Then the messagegetValueretrieves the text (a coffee name or a price) that the SAAJ coffee supplier added to thecoffee-nameandpriceelements when it gave content toresponse. The final line in the following code fragment adds the coffee name or price to theVectorobjectlist. Note that because of the nestedwhileloops, for eachcoffeeelement that the code retrieves, both of its child elements (thecoffee-nameandpriceelements) 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 theArrayListpriceItems, and prints each pair on a separate line. Finally it constructs and returns aPriceListBean.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
placeOrdermethod ofOrderRequest, which follows the DTDcoffee-order.dtd.Creating the Order
As with the client code for requesting a price list, the
placeOrdermethod starts by creating aSOAPConnectionobject and aSOAPMessageobject and accessing the message'sSOAPBodyobject.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 iscoffee-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 toorderIDis extracted from theOrderBeanobject passed to theOrderRequest.placeOrdermethod.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 theCustomercomponent ofOrderBean.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
addresselement, added next, has child elements for the street, city, state, and zip code. This information is extracted from theAddresscomponent ofOrderBean.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-itemhas three child elements:coffeeName,pounds, andprice. This information is extracted from theLineItemslist contained inOrderBean.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/orderCoffeeand closes the connection.Because we map the given endpoint to
ConfirmationServlet, the Application Server executes that servlet (discussed in Returning the Order Confirmation) to create and return theSOAPMessageobjectreply.Retrieving the Order Confirmation
The rest of the
placeOrdermethod retrieves the information returned inreply. The client knows what elements are in it because they are specified inconfirm.dtd. After accessing theSOAPBodyobject, the code retrieves theconfirmationelement and gets the text of theorderIDandship-dateelements. Finally, it constructs and returns aConfirmationBeanwith 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.HttpServletmethods:init,doPost, andonMessage. TheinitanddoPostmethods set up the response message, and theonMessagemethod 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 methodcall, invoked inPriceListRequest.Any servlet extends a
javax.servletclass. Being part of a web application, this servlet extendsHttpServlet. It first creates a staticMessageFactoryobject that will be used later to create theSOAPMessageobject 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
initmethod. Thisinitmethod 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
PriceListServletisdoPost, which does the real work of the servlet by calling theonMessagemethod. (TheonMessagemethod is discussed later in this section.) The Application Server passes thedoPostmethod two arguments. The first argument, theHttpServletRequestobjectreq, holds the content of the message sent inPriceListRequest. ThedoPostmethod gets the content fromreqand puts it in theSOAPMessageobjectmsgso that it can pass it to theonMessagemethod. The second argument, theHttpServletResponseobjectresp, will hold the message generated by executing the methodonMessage.In the following code fragment,
doPostcalls the methodsgetHeadersandputHeaders, defined immediately afterdoPost, to read and write the headers inreq. It then gets the content ofreqas a stream and passes the headers and the input stream to the methodMessageFactory.createMessage. The result is that theSOAPMessageobjectmsgcontains the request for a price list. Note that in this case,msgdoes not have any headers because the message sent inPriceListRequestdid 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
SOAPMessageobjectreplyand populates it by calling the methodonMessage.If
replyhas anything in it, its contents are saved, the status ofrespis set toOK, and the headers and content ofreplyare written toresp. Ifreplyis empty, the status ofrespis 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
getHeadersandputHeadersare not standard methods in a servlet, asinit,doPost, andonMessageare. The methoddoPostcallsgetHeadersand passes it theHttpServletRequestobjectreqthat the Application Server passed to it. It returns aMimeHeadersobject populated with the headers fromreq.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
doPostmethod callsputHeadersand passes it theMimeHeadersobjectheaders, which was returned by the methodgetHeaders. The methodputHeaderswrites the headers inheaderstores, the second argument passed to it. The result is thatres, the response that the Application Server will return to the methodcall, 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
onMessageis the application code for responding to the message sent byPriceListRequestand internalized intomsg. It uses the staticMessageFactoryobjectmessageFactoryto create theSOAPMessageobjectmessageand then populates it with the supplier's current coffee prices.The method
doPostinvokesonMessageand passes itmsg. In this case,onMessagedoes not need to usemsgbecause it simply creates a message containing the supplier's price list. TheonMessagemethod inConfirmationServlet(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
ConfirmationServletcreates the confirmation message that is returned to thecallmethod that is invoked inOrderRequest. It is very similar to the code inPriceListServletexcept that instead of building a price list, itsonMessagemethod builds a confirmation containing the order number and shipping date.The
onMessagemethod for this servlet uses theSOAPMessageobject passed to it by thedoPostmethod to get the order number sent inOrderRequest. 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; }