Download
FAQ History |
API
Search Feedback |
SAAJ Coffee Supplier Service
In contrast to the JAX-RPC service, the SAAJ supplier service does not register in a publicly accessible registry. It simply 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
<
INSTALL
>/j2eetutorial14/examples/cb/saaj/dtds/
. Thedtds
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:
SAAJ Client
The Coffee Break servers, which are the SAAJ clients in this scenario, send requests to their SAAJ supplier. The SAAJ client application uses the
SOAPConnection
methodcall
to 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
PriceListRequest
andOrderRequest
.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 ofPriceListRequest
, which follows the DTDprice-list.dtd
.The
getPriceList
method begins by creating the connection that will be used to send the request. Then it gets the defaultMessageFactory
object so that it can create theSOAPMessage
objectmsg
.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 aName
object for each new element that is created. TheSOAPEnvelope
object is also used to access theSOAPBody
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 topmost element inside the body isrequest-prices
and that it contains the elementrequest
. The text node added torequest
is the text of the request being sent. Every new element that is added to the message must have aName
object to identify it, and this object is created by theEnvelope
methodcreateName
. The following lines of code create the top-level element in theSOAPBody
objectbody
. The first element created in aSOAPBody
object is always aSOAPBodyElement
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 elementrequest-prices
(represented by theSOAPBodyElement
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 methodsaveChanges
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 SAAJ coffee supplier. The message being sent is the
SOAPMessage
objectmsg
, 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
. TheSOAPConnection
objectcon
is used to send the message, and because it is no longer needed, it is closed.When the
call
method is executed, the Application Server executes the servletPriceListServlet
. This servlet creates and returns aSOAPMessage
object whose content is the SAAJ supplier's price list. (PriceListServlet
is discussed in Returning the Price List.) The Application Server knows to executePriceListServlet
because 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
, theSOAPMessage
object returned by the methodcall
, and (2) returning the price list as aPriceListBean
.The code creates an empty
Vector
object that will hold thecoffee-name
andprice
elements that are extracted fromresponse
. Then the code usesresponse
to access itsSOAPBody
object, which holds the message's content. Notice that theSOAPEnvelope
object is not accessed separately because it is not needed for creatingName
objects, as it was in the previous section.The next step is to retrieve the
SOAPBodyElement
object. The methodgetChildElements
returns anIterator
object that contains all the child elements of the element on which it is called, so in the following lines of code,it1
contains theSOAPBodyElement
objectbodyEl
, which represents theprice-list
element.Iterator it1 = responseBody.getChildElements(); while (it1.hasNext()) { SOAPBodyElement bodyEl = (SOAPBodyElement)it1.next();The
Iterator
objectit2
holds the child elements ofbodyEl
, which representcoffee
elements. Calling the methodnext
onit2
retrieves the first coffee element inbodyEl
. As long asit2
has another element, the methodnext
will return the nextcoffee
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
andprice
elements contained init3
. Then the messagegetValue
retrieves the text (a coffee name or a price) that the SAAJ coffee supplier added to thecoffee-name
andprice
elements when it gave content toresponse
. The final line in the following code fragment adds the coffee name or price to theVector
objectlist
. Note that because of the nestedwhile
loops, for eachcoffee
element that the code retrieves, both of its child elements (thecoffee-name
andprice
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 theArrayList
priceItems
, and prints each pair on a separate line. Finally it constructs and returns aPriceListBean
.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); Calendar todayCal = new GregorianCalendar(); todayCal.setTime(today); Calendar cal = new GregorianCalendar(); cal.setTime(endDate); plb = new PriceListBean(); plb.setStartDate(todayCal); plb.setPriceItems(priceItems); plb.setEndDate(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 ofOrderRequest
, which follows the DTDcoffee-order.dtd
.Creating the Order
As with the client code for requesting a price list, the
placeOrder
method starts by creating aSOAPConnection
object, creating aSOAPMessage
object, and accessing the message'sSOAPEnvelope
andSOAPBody
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 iscoffee-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 toorderID
is extracted from theOrderBean
object passed to theOrderRequest
.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 theCustomer
component ofOrderBean
.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 theAddress
component ofOrderBean
.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
, andprice
. This information is extracted from theLineItems
list contained inOrderBean
.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 to the endpoint
http://localhost:8080/saaj-coffee-supplier/orderCoffee
and 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 theSOAPMessage
objectreply
.Retrieving the Order Confirmation
The rest of the
placeOrder
method retrieves the information returned inreply
. The client knows what elements are in it because they are specified inconfirm.dtd
. After accessing theSOAPBody
object, the code retrieves theconfirmation
element and gets the text of theorderID
andship-date
elements. Finally, it constructs and returns aConfirmationBean
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); Calendar cal = new GregorianCalendar(); cal.setTime(date); cb = new ConfirmationBean(id, 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
, andonMessage
. Theinit
anddoPost
methods set up the response message, and theonMessage
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 methodcall
, invoked inPriceListRequest
.Any servlet extends a
javax.servlet
class. Being part of a Web application, this servlet extendsHttpServlet
. It first creates a staticMessageFactory
object that will be used later to create theSOAPMessage
object that is returned.public class PriceListServlet extends HttpServlet { static MessageFactory fac = null; static { try { fac = MessageFactory.newInstance(); } catch (Exception ex) { ex.printStackTrace(); } };Every servlet has an
init
method. Thisinit
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
isdoPost
, which does the real work of the servlet by calling theonMessage
method. (TheonMessage
method is discussed later in this section.) The Application Server passes thedoPost
method two arguments. The first argument, theHttpServletRequest
objectreq
, holds the content of the message sent inPriceListRequest
. ThedoPost
method gets the content fromreq
and puts it in theSOAPMessage
objectmsg
so that it can pass it to theonMessage
method. The second argument, theHttpServletResponse
objectresp
, will hold the message generated by executing the methodonMessage
.In the following code fragment,
doPost
calls the methodsgetHeaders
andputHeaders
, defined immediately afterdoPost
, to read and write the headers inreq
. It then gets the content ofreq
as a stream and passes the headers and the input stream to the methodMessageFactory.createMessage
. The result is that theSOAPMessage
objectmsg
contains the request for a price list. Note that in this case,msg
does not have any headers because the message sent inPriceListRequest
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 = fac.createMessage(headers, is);Next, the code declares the
SOAPMessage
objectreply
and populates it by calling the methodonMessage
.If
reply
has anything in it, its contents are saved, the status ofresp
is set toOK
, and the headers and content ofreply
are written toresp
. Ifreply
is empty, the status ofresp
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( "SAAJ POST failed: " + ex.getMessage()); } }The methods
getHeaders
andputHeaders
are not standard methods in a servlet, asinit
,doPost
, andonMessage
are. The methoddoPost
callsgetHeaders
and passes it theHttpServletRequest
objectreq
that the Application Server passed to it. It returns aMimeHeaders
object populated with the headers fromreq
.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 callsputHeaders
and passes it theMimeHeaders
objectheaders
, which was returned by the methodgetHeaders
. The methodputHeaders
writes the headers inheaders
tores
, 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
onMessage
is the application code for responding to the message sent byPriceListRequest
and internalized intomsg
. It uses the staticMessageFactory
objectfac
to create theSOAPMessage
objectmessage
and then populates it with the supplier's current coffee prices.The method
doPost
invokesonMessage
and passes itmsg
. In this case,onMessage
does not need to usemsg
because it simply creates a message containing the supplier's price list. TheonMessage
method 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 = 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); 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 thecall
method that is invoked inOrderRequest
. It is very similar to the code inPriceListServlet
except that instead of building a price list, itsonMessage
method builds a confirmation containing the order number and shipping date.The
onMessage
method for this servlet uses theSOAPMessage
object passed to it by thedoPost
method 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) { SOAPMessage confirmation = null; try { // Retrieve orderID from 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 orderID test 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; }
Download
FAQ History |
API
Search Feedback |
All of the material in The J2EE(TM) 1.4 Tutorial is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.