Download FAQ History |  | API Search Feedback

> ## SAAJ Distributor Service

> The SAAJ 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 messaging they will do. They have agreed to do request-response messaging using the SAAJ API (the `javax.xml.soap` package).

> The Coffee Break server sends two kinds of messages:

> - Requests for current wholesale coffee prices
> - Customer orders for coffee

> The SAAJ coffee supplier responds with two kinds of messages:

> - Current price lists
> - Order confirmations

> 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 SAAJ distributor are specified by the following DTDs:

> - `request-prices.dtd`
> - `price-list.dtd`
> - `coffee-order.dtd`
> - `confirm.dtd`

> These DTDs may be found at

>     <INSTALL>/jwstutorial12/examples/cb/saaj/dtds

> The `dtds` directory also contains a sample of what the XML documents specified in the DTDs might look like. The corresponding XML files for each of the DTDs are as follows:

> - `request-prices.xml`
> - `price-list.xml`
> - `coffee-order.xml`
> - `confirm.xml`

> 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 the following directory:

>     <INSTALL>/jwstutorial12/examples/cb/saaj/src/com/sun/cb

> ### SAAJ Client

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

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

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

> ##### Sending the Request

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

> The `getPriceList` method begins by creating the connection that will be used to send the request. Then it gets the default `MessageFactory` object 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. The `SOAPEnvelope` object 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 SAAJ coffee supplier. The message being sent is the `SOAPMessage` object `msg`, to which the elements created in the previous code snippets were added. The endpoint is the URI for the SAAJ coffee supplier, `http://localhost:8080/saaj-coffee-supplier/getPriceList`. The `SOAPConnection` object `con` is used to send the message, and because it is no longer needed, it is closed.

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

> When the `call` method is executed, Tomcat executes the servlet `PriceListServlet`. This servlet creates and returns a `SOAPMessage` object whose content is the SAAJ distributor's price list. (`PriceListServlet` is discussed in Returning the Price List.) Tomcat knows to execute `PriceListServlet` because the `web.xml` file at `<``INSTALL``>/jwstutorial12/examples/cb/saaj/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 SAAJ 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);
>     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 server can send to the SAAJ 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 to the endpoint `http://localhost:8080/saaj-coffee-supplier/orderCoffee` and closes the connection.

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

> Because the `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);
>     Calendar cal = new GregorianCalendar();
>     cal.setTime(date);
>     cb = new ConfirmationBean(id, cal);

> ### SAAJ Service

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

> #### Returning the Price List

> This section takes you through the servlet `PriceListServlet`. This servlet creates the message 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.

>     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. This `init` method initializes the servlet with the configuration information that Tomcat passed to it.

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

> The next method defined in `PriceListServlet` is `doPost`, which does the real work of the servlet by calling the `onMessage` method. (The `onMessage` method is discussed later in this section.) 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 = fac.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( "SAAJ 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()) {
>             MimeHeader header = (MimeHeader)it.next();

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

> The method `onMessage` is the application code for responding to the message sent by `PriceListRequest` and internalized into `msg`. It uses the static `MessageFactory` object `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);

>             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 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 Java(TM) Web Services Tutorial is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.