SOAP-Based Clients Against Amazon’s E-Commerce Service

Chapter 3 has two Java clients against the RESTful Amazon E-Commerce service. The first client parses the XML document from Amazon in order to extract the desired information, in this case the author of a specified Harry Potter book, J. K. Rowling. The second client uses JAX-B to deserialize the returned XML document into a Java object, whose get-methods are then used to extract the same information. This section introduces two more clients against the E-Commerce service; in this case the service is SOAP-based and, therefore, the clients are as well. The SOAP-based clients use a handler, Java code that has access to every outgoing and incoming SOAP message. In the case of Amazon, the handler’s job is to inject into the SOAP request the authentication information that Amazon requires, in particular a digest based on the secretKey used in both of the RESTful clients of Chapter 2. A message digest generated with the secretKey, rather than the secretKey itself, is sent from the client to the Amazon service; hence, the secretKey itself does not travel over the wire. SOAP handlers are the focus of the next chapter; for now, a handler is used but not analyzed.

The SOAP-based clients against Amazon’s E-Commerce service, like the other SOAP-based Java clients in this chapter, rely upon wsimport-generated classes as building blocks. There are some key points about the SOAP-based service and its clients:

In Chapter 3, the clients against the RESTful E-Commerce service did lookup operations. For contrast, the SOAP-based client does a search against the Amazon E-Commerce service. The AmazonClientBareStyle (see Example 4-15) is the first and friendliest SOAP-based client.

Example 4-15. A SOAP-based Amazon client in bare parameter style

The ZIP file with the sample code includes an executable JAR with the code from Example 4-15 and its dependencies. The JAR can be executed as follows:

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

The AmazonClientBareStyle highlights what SOAP-based services have to offer to their clients. The wsimport-generated classes include the AWSECommerceService with a no-argument constructor. This class represents, to the client, the E-Commerce service. The usual two-step occurs: in line 1 an AWSECommerceService instance is constructed and in line 3 the getAWSECommerceServicePort method is invoked. The object reference port can now be used, in line 6, to launch a search against the E-Commerce service, which results in an ItemSearchResponse. Line 2 in the setup hands over the user’s secretKey to the client-side handler, which uses the secretKey to generate a hash value as a message authentication code, which Amazon can then verify on the service side.

The remaining code, from line 7 on, resembles the code in the second RESTful client against the E-Commerce service. Here is a quick review of the SOAP-based code:

List<Items> itemsList = response.getItems();                              1
int i = 1;
for (Items next : itemsList)
   for (Item item : next.getItem())                                       2
      System.out.println(String.format("%2d: ", i++) +
                         item.getItemAttributes().getTitle());            3

The ItemSearchResponse from Amazon encapsulates a list of Items (line 1), each of whose members is itself a list. The nested for loop iterates (line 2) over the individual Item instances, printing the title of each book found (line 3). By the way, the search returns the default number of items found, 10; it is possible to ask for all of the items found. On a sample run, the output was:

 1: Persuasion (Dover Thrift Editions)
 2: Pride and Prejudice (The Cambridge Edition of the Works of Jane Austen)
 3: Emma (Dover Thrift Editions)
 4: Northanger Abbey (Dover Thrift Editions)
 5: Mansfield Park
 6: Love and Friendship
 7: Jane Austen: The Complete Collection (With Active Table of Contents)
 8: Lady Susan
 9: Jane Austen Collection: 18 Works, Pride and Prejudice, Love and Friendship,
         Emma, Persuasion, Northanger Abbey, Mansfield Park, Lady Susan & more!
10: The Jane Austen Collection: 28 Classic Works

Now is the time to clarify the custom.xml file used in the wsimport command against the Amazon WSDL. The filename custom.xml is arbitrary and, for review, here is the wsimport command:

% wsimport -p amazon -keep \
  http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl \
  -b custom.xml

The file custom.xml is:

<jaxws:bindings
    wsdlLocation =
     "http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdl"
    xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
  <jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>  1
</jaxws:bindings>

The key element in the file sets the enableWrapperStyle for the parameters to false (line 1). The result is the bare parameter style evident in the AmazonClientBareStyle code. The alternative to this style is the default one, the client-side wrapped style. The AmazonClientWrappedStyle (see Example 4-16) is a SOAP-based Amazon client in the default style.

Example 4-16. A SOAP-based Amazon client in wrapped parameter style

package amazon2;

import amazon2.AWSECommerceService;
import amazon2.AWSECommerceServicePortType;
import amazon2.ItemSearchRequest;
import amazon2.ItemSearch;
import amazon2.Items;
import amazon2.Item;
import amazon2.OperationRequest;
import amazon2.SearchResultsMap;
import amazon2.AwsHandlerResolver;

import javax.xml.ws.Holder;
import java.util.List;
import java.util.ArrayList;

class AmazonClientWrappedStyle {
    public static void main(String[ ] args) {
      if (args.length < 2) {
        System.err.println("java AmazonClientWrappedStyle <accessId> <secretKey>");
        return;
      }
      final String accessId = args[0];
      final String secretKey = args[1];

      AWSECommerceService service = new AWSECommerceService();
      service.setHandlerResolver(new AwsHandlerResolver(secretKey));
      AWSECommerceServicePortType port = service.getAWSECommerceServicePort();
      ItemSearchRequest request = new ItemSearchRequest();
      request.setSearchIndex("Books");
      request.setKeywords("Austen");
      ItemSearch search = new ItemSearch();
      search.getRequest().add(request);
      search.setAWSAccessKeyId(accessId);
      search.setAssociateTag("kalin");
      Holder<OperationRequest> operationRequest = null;      1
      Holder<List<Items>> items = new Holder<List<Items>>(); 2
      port.itemSearch(search.getMarketplaceDomain(),         3
                      search.getAWSAccessKeyId(),
                      search.getAssociateTag(),
                      search.getXMLEscaping(),
                      search.getValidate(),
                      search.getShared(),
                      search.getRequest(),
                      operationRequest,                      4
                      items);                                5
      Items retval = items.value.get(0);                     6
      int i = 1;
      List<Item> item_list = retval.getItem();               7
      for (Item item : item_list)
          System.out.println(String.format("%2d: ", i++) +
                             item.getItemAttributes().getTitle());
    }
}

The AmazonClientWrappedStyle code uses wsimport-generated classes created with the following command:

% wsimport -p amazon2 -keep \
  http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl

The WSDL is the same as in previous examples, but the style of the wsimport-classes changes from bare to wrapped, a change reflected in the AmazonClientWrappedStyle code. The change is evident at lines 1 and 2, which declare two object references of type Holder. As the name suggests, a Holder parameter is meant to hold some value returned from the E-Commerce service: the operationRequest holds metadata about the request, whereas items holds the book list that results from a successful search. This idiom is common in C or C++ but rare—and, therefore, clumsy—in Java. The Holder parameters are the last two (lines 4 and 5) of the nine parameters in the revised itemSearch (line 3). On a successful search, items refers to a value (line 6) from which a list of Items is extracted. This code, too, is awkward in Java. This list of Items has a getItem method (line 7), which yields a List<Item> from which the individual Item instances, each representing a Jane Austen book, can be extracted.

The AmazonClientWrappedStyle client is clearly the clumsier of the two clients against SOAP-based E-Commerce service, a service that has a single WSDL and whose response payloads to the two clients are identical in structure. The two clients differ markedly in their APIs, however. The bare style API would be familiar to most Java programmers, but the wrapped style, with its two Holder types, would seem a bit alien even to an experienced Java programmer. Nonetheless, the wrapped style remains the default in Java and in DotNet.

All of the SOAP-based clients examined so far make synchronous or blocking calls against a web service. For example, consider these two lines from the bare style client against the E-Commerce service:

ItemSearchResponse response = port.itemSearch(itemSearch); 1
List<Items> itemsList = response.getItems();               2

The call in line 1 to itemSearch blocks in the sense that line 2 does not execute until itemSearch returns a value, perhaps null. There are situations in which a client might need the invocation of itemSearch to return immediately so that other application logic could be performed in the meantime. In this case, a nonblocking or asynchronous call to itemSearch would be appropriate.

The RandClientAsync (see Example 4-17) is an asynchronous client against the RandService (see Example 4-1).

Although an asynchronous client also could be coded against the E-Commerce service, the far simpler RandService makes the client itself relatively straightforward; it is then easier to focus on the asynchronous part of the API. No changes are required in the RandService or its publication, under either Endpoint or a web server such as Tomcat. The wsimport command again takes a customization file, in this example customAsync.xml; the filename is arbitrary. The wsimport command is:

wsimport -p clientAsync -keep http://localhost:8888/rs?wsdl -b customAsync.xml

The customized binding file is:

<jaxws:bindings
    wsdlLocation="http://localhost:8888/rs?wsdl"
    xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
  <jaxws:enableAsyncMapping>true</jaxws:enableAsyncMapping> 1
</jaxws:bindings>

The customized binding sets the enableAsyncMapping to true (line 1). The wsimport utility generates the same classes as in the earlier examples: Next1, Next1Response, and so on. The request/response classes such as Next1 and Next1Response have additional methods, however, to handle the asynchronous calls, and these classes still have the methods that make synchronous calls.

The setup in the asynchronous client is the familiar two-step: first create a service instance and then invoke the getRandService method on this instance. The dramatic change is line 1, the asynchronous call, which now takes two arguments:

port.nextNAsync(4, new MyHandler());

Although the nextNAsync method does return a value, my code does not bother to assign this value to a variable. The reason is that the Java runtime passes the NextNResponse message from the RandService to the client’s event handler, an instance of MyHandler, which then extracts and prints the randomly generated integers from the service.

The call to nextNAsync, a method declared together with nextN in the wsimport-generated RandService interface, takes two arguments: the number of requested random numbers and an event handler, in this case a MyHandler instance. The handler class MyHandler must implement the AsyncHandler interface (line 2) by defining the handleResponse method (line 3). The handleResponse method follows the standard Java pattern for event handlers: the method has void as its return type and it expects one argument, an event triggered by a Response<NextNResponse> that arrives at the client.

When the client runs, the main thread executes the asynchronous call to nextNAsync, which returns immediately. To prevent the main thread from exiting main and thereby ending the application, the client invokes Thread.sleep. This is contrived, of course; in a production environment, the main thread presumably would go on to do meaningful work. In this example, the point is to illustrate the execution pattern. When the RandService returns the requested integers, the Java runtime starts a (daemon) thread to execute the handleResponse callback, which prints the requested integers. In the meantime, the main thread eventually wakes up and exits main, thereby terminating the client’s execution. On a sample run, the output was:

1616290443
-984786015
1002134912
311238217
main is exiting...

The daemon thread executing handleResponse prints the four integers, and the main thread prints the good-bye message.

Java and DotNet take different approaches toward generating, from a WSDL, support for asynchronous calls against a service. DotNet automatically generates methods for synchronous and asynchronous calls against the service; Java takes the more conservative approach of generating the asynchronous artifacts only if asked to do so with a customized binding such as the one used in this example. The key point is that Java API, like its DotNet counterpart, fully supports synchronous and asynchronous calls against SOAP-based services such as the RandService.