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:

  • The WSDL and wsimport.

    The WSDL for the SOAP-based version of Amazon’s E-Commerce service is available at:

    http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl

    This WSDL is more than 1,500 lines in size, with most of these lines in the XML Schema. The wsimport utility can be applied to this WSDL in the usual way:

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

    The result is a directory/package named amazon filled with client-support classes generated from the WSDL. The part at the end, with custom.xml, is explained shortly.

  • Client-side API styles.

    The Amazon SOAP-based service follows best design practices and is, therefore, wrapped doc/lit. However, wsimport can generate different client APIs from one and the same Amazon WSDL. This point deserves elaboration. Consider a very simple operation in a SOAP-based service, which takes two arguments, text and a pattern, and returns the number of times that the pattern occurs in the text. For example, the text might be the novel War and Peace and the pattern might be the name of one of the heroines, Natasha. The operation is named getCount. There are different ways in which this operation might be implemented in Java. Perhaps the obvious implementation would have the declaration:

    public int getCount(String text, String pattern);

    This version takes two arguments, the text and the pattern, and returns the count as an int. Yet the client of a SOAP-based web service, following in the footsteps of DCE/RPC, can distinguish between in and out parameters—arguments passed into the service and ones passed out of this same service and back to the client. This possibility opens the way to a quite different version of getCount:

    public void getCount(String text, String pattern, Holder result);

    The return type for getCount is now void, which means that the count must be returned in some other way. The third parameter, of the special type Holder, embeds the desired count of pattern occurrences in the text. This programming style is uncommon in Java and, accordingly, might be judged inferior to the two-argument version of getCount that returns the count directly as an int. The point of interest is that wsimport can generate client-side artifacts in either style, and, perhaps surprisingly, the second style is the default for wsimport. In Java, the first style is:

    SOAPBinding.ParameterStyle.BARE

    and the second style is:

    SOAPBinding.ParameterStyle.WRAPPED

    The critical point is that these parameter styles refer to the wsimport artifacts generated from a service WSDL—the parameter styles do not refer to the structure of the service itself, which remains wrapped doc/lit. Java’s wsimport utility can present this service style, on the client side, in different ways, known as parameter styles in Java.

  • Authentication credentials in a SOAP-based client.

    A SOAP-based client against E-Commerce must send the same authentication credentials as a RESTful client: a registered user’s accessId and a hash value generated with the secretKey. In a REST-style client, these credentials are sent in the query string of a GET request. A SOAP-based client is different in that its requests are all POSTs, even if the intended web service operation is a read. In a SOAP-based exchange over HTTP, the request is a SOAP envelope that is the body of a POST request. Accordingly, a SOAP-based client must process the required credentials in a different way. In this section, the credential processing is partly the job of a SOAP handler, which is examined carefully in the next chapter.

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

package amazon;

import amazon.AWSECommerceService;
import amazon.AWSECommerceServicePortType;
import amazon.ItemSearchRequest;
import amazon.ItemSearchResponse;
import amazon.ItemSearch;
import amazon.Items;
import amazon.Item;
import amazon.AwsHandlerResolver;
import java.util.List;

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

        AWSECommerceService service = new AWSECommerceService();                  1
        service.setHandlerResolver(new AwsHandlerResolver(secretKey));            2
        AWSECommerceServicePortType port = service.getAWSECommerceServicePort();  3
        ItemSearchRequest request = new ItemSearchRequest();                      4
        request.setSearchIndex("Books");
        request.setKeywords("Austen");
        ItemSearch itemSearch= new ItemSearch();                                  5
        itemSearch.setAWSAccessKeyId(accessId);
        itemSearch.setAssociateTag("kalin");
        itemSearch.getRequest().add(request);
        ItemSearchResponse response = port.itemSearch(itemSearch);                6
        List<Items> itemsList = response.getItems();                              7
        int i = 1;
        for (Items next : itemsList)
           for (Item item : next.getItem())
               System.out.println(String.format("%2d: ", i++) +
                                  item.getItemAttributes().getTitle());
    }
}

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.

Asynchronous Clients Against SOAP-Based Services

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).

Example 4-17. A client that makes asynchronous requests against the RandService

import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;
import java.util.List;
import clientAsync.RandServiceService;
import clientAsync.RandService;
import clientAsync.NextNResponse;

public class RandClientAsync {
    public static void main(String[ ] args) {
        RandServiceService service = new RandServiceService();
        RandService port = service.getRandServicePort();
        port.nextNAsync(4, new MyHandler());                            1
        try {
            Thread.sleep(5000); // in production, do something useful!
        }
        catch(Exception e) { }
        System.out.println("\nmain is exiting...");
    }
    static class MyHandler implements AsyncHandler<NextNResponse> {     2
        public void handleResponse(Response<NextNResponse> future) {    3
            try {
                NextNResponse response = future.get();                  4
                List<Integer> nums = response.getReturn();              5
                for (Integer num : nums) System.out.println(num);       6
            }
            catch(Exception e) { System.err.println(e); }
        }
    }
}

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.

Get Java Web Services: Up and Running, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.