Another Client Against the Amazon E-Commerce Service

The second client against the Amazon E-Commerce service does not deal explicitly with any XML but otherwise has the same functionality as the first client. The steps for setting up the second client are listed below, but the ZIP file with the sample code includes JAR Amazon2.jar that can be executed directly:

% java -jar Amazon2.jar <accessId> <secretKey>

Here are the steps for setting up the second Amazon client. These steps that would be copied for a Java client against any RESTful service that provides an XML Schema—and most services do provide a schema. For a depiction of the process and the role of Java’s xjc utility, see Figure 3-1.

Using the xjc utility to transform an XML Schema into Java classes

Figure 3-1. Using the xjc utility to transform an XML Schema into Java classes

  1. Download the XML Schema for the E-Commerce service. The URL is:

    http://webservices.amazon.com/AWSECommerceServices/AWSECommerceService.xsd

    The downloaded schema is about 55K in size. (This is the same schema used in the SOAP-based versions of the Amazon services.) Put the downloaded document in a local file such as amazon2/amazon.xsd. The local filename is arbitrary.

  2. The amazon.xsd needs some tweaking so that the Java JAX-B utilities can use this file without complaining. The downloaded XML Schema begins as follows:

    <?xml version="1.0" encoding="UTF-8"?>
    <xs:schema
      xmlns:xs="http://www.w3.org/2001/XMLSchema"                              1
      xmlns:tns="http://webservices.amazon.com/AWSECommerceService/2011-08-01"
      targetNamespace=
        "http://webservices.amazon.com/AWSECommerceService/2011-08-01"
      elementFormDefault="qualified">
      <xs:element name="Bin">                                                  2
        <xs:complexType>                                                       3
          <xs:sequence>                                                        4
          ...

    The problem lies with the namespace identifier xs, which occurs once to the right of the colon (line 1) in:

    xmlns:xs="http://www.w3.org/2001/XMLSchema"

    It also occurs on the second line and everywhere else to the left of the colon (lines 2, 3, and 4). The identifier xs should be changed globally to xsd; any reasonable text editor can make this change. The result should be:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsd:schema
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:tns="http://webservices.amazon.com/AWSECommerceService/2011-08-01"
      targetNamespace=
        "http://webservices.amazon.com/AWSECommerceService/2011-08-01"
      elementFormDefault="qualified">
      <xsd:element name="Bin">
        <xsd:complexType>
          <xsd:sequence>
          ...

    The xjc utility should not be this brittle, of course; Java itself generates schemas that use the namespace abbreviation xs.

  3. Execute the xjc utility, which ships with the core Java JDK, against the schema. In this example, the command is:

    % xjc -p restful2 amazon.xsd

    The -p flag stands for package. The xjc utility creates the package/subdirectory named restful2 and fills the subdirectory with, at present, 84 Java source files. The files have names such as CartAddRequest.java, ItemLookup.java, ItemSearch.java, LoyaltyPoints.java, and so on. These files, in compiled form, are the Java types that correspond to the XML Schema types in amazon.xsd.

  4. This step is optional but recommended for convenience. Copy the source files RestfulAmazon.java and RequestHelper.java from the first Amazon example into the restful2 subdirectory. The package name in both files needs to change from restful to restful2. The xjc-generated files could be kept in a separate package, of course.
  5. Compile the .java files in restful2.

The nearly 85 Amazon files in restful2 have JAX-B annotations such as @XmlType. These xjc-generated files automate the translation between an XML data type such as tns:ItemSearch and the corresponding Java type, in this case restful2.ItemSearch. The XML types are defined in the Amazon schema, and the corresponding Java types are the classes generated with the xjc utility. The process (see Figure 3-1) of generating Java artifacts is relatively uncomplicated.

Programming involves trade-offs, and the revised Amazon client illustrates one such trade-off. On the plus side, the revised RestfulAmazon client no longer needs explicitly to parse XML; hence, the various import directives that support DOM parsing can be removed because the getAuthor method no longer uses the imported types such as DocumentBuilder and NodeList. On the minus side, the xjc-generated classes bring a new API into play, and this API involves some Russian-doll nesting, as a look at the new code will show.

The revised client requires no changes to the RequestHelper except for the change in the package name. The revised RestfulAmazon client (see Example 3-12) is largely the same as the original; hence, unchanged code is marked with an ellipsis.

Example 3-12. The revised RestfulAmazon that uses JAX-B to avoid XML parsing

package restful2;

...
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Schema;
import javax.xml.XMLConstants;
import javax.xml.validation.Validator;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.JAXBException;

public class RestfulAmazon {
    ...
    public static void main(String[ ] args) {
       ...
    }
    private void lookupStuff(String accessKeyId, String secretKey) {
       ...
    }
    private String requestAmazon(String string_url) {
        ...
    }
    private String getAuthor(String xml) {
      String author = null;
      try {
        // Create an XML Schema object
        final String fileName = "amazon.xsd"; // downloaded XML Schema
        final String schemaUri = XMLConstants.W3C_XML_SCHEMA_NS_URI;
        SchemaFactory factory = SchemaFactory.newInstance(schemaUri);
        Schema schema = factory.newSchema(new StreamSource(fileName));          1
        // Create a JAX-B context for unmarshaling
        JAXBContext ctx = JAXBContext.newInstance(ItemLookupResponse.class);    2
        Unmarshaller um = ctx.createUnmarshaller();                             3
        um.setSchema(schema);                                                   4
        // Generate a Java ItemSearchResponse instance.
        ItemLookupResponse ilr = (ItemLookupResponse)
             um.unmarshal(new ByteArrayInputStream(xml.getBytes()));            5
        // Use the standard POJO idiom to extract the author.
        List<Items> itemsList = ilr.getItems(); // list of lists                6
        for (Items items : itemsList) {         // outer list                   7
          List<Item> list = items.getItem();  // inner list                     8
          for (Item item : list) {            // items in inner list
            ItemAttributes attributes = item.getItemAttributes();
            List<String> authors = attributes.getAuthor(); // could be several
            author = authors.get(0); // in this case, only one                  9
          }
        }
      }
      catch(JAXBException e ) { throw new RuntimeException(e); }
      catch(Exception e) { throw new RuntimeException(e); }
      return author;
    }
}

In the revised RestfulAmazon client, the getAuthor method is invoked with an XML document as the argument but the XML is not parsed. Instead, the method does the following:

The two clients against the Amazon RESTful service highlight a typical choice confronted in programming RESTful clients. The choice can be summarized as follows:

Is there a compelling answer to either question? For one-off client applications, working directly with the XML may be the way to go. Tools such as XPath make it relatively easy to extract information from XML documents, at least XML documents that are of a reasonable size. It is very hard to define reasonable size in this context, of course. The problem is that tools such as XPath require a DOM—a tree structure—in order to work. Building a tree from, say, an XML stream of gigabyte size may be prohibitively slow; searching the built tree may be the same. For client applications that regularly target a particular service such as Amazon E-Commerce, working with JAX-B artifacts means working in familiar Java idioms such as get/set methods. The benefit of using JAX-B is that the XML effectively disappears into the JAX-B infrastructure.