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 nowvoid
, which means that the count must be returned in some other way. The third parameter, of the special typeHolder
, embeds the desired count ofpattern
occurrences in thetext
. This programming style is uncommon in Java and, accordingly, might be judged inferior to the two-argument version ofgetCount
that returns the count directly as anint
. 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
();
service
.
setHandlerResolver
(
new
AwsHandlerResolver
(
secretKey
));
AWSECommerceServicePortType
port
=
service
.
getAWSECommerceServicePort
();
ItemSearchRequest
request
=
new
ItemSearchRequest
();
request
.
setSearchIndex
(
"Books"
);
request
.
setKeywords
(
"Austen"
);
ItemSearch
itemSearch
=
new
ItemSearch
();
itemSearch
.
setAWSAccessKeyId
(
accessId
);
itemSearch
.
setAssociateTag
(
"kalin"
);
itemSearch
.
getRequest
().
add
(
request
);
ItemSearchResponse
response
=
port
.
itemSearch
(
itemSearch
);
List
<
Items
>
itemsList
=
response
.
getItems
();
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
();
int
i
=
1
;
for
(
Items
next
:
itemsList
)
for
(
Item
item
:
next
.
getItem
())
System
.
out
.
println
(
String
.
format
(
"%2d: "
,
i
++)
+
item
.
getItemAttributes
().
getTitle
());
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
>
</
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
;
Holder
<
List
<
Items
>>
items
=
new
Holder
<
List
<
Items
>>();
port
.
itemSearch
(
search
.
getMarketplaceDomain
(),
search
.
getAWSAccessKeyId
(),
search
.
getAssociateTag
(),
search
.
getXMLEscaping
(),
search
.
getValidate
(),
search
.
getShared
(),
search
.
getRequest
(),
operationRequest
,
items
);
Items
retval
=
items
.
value
.
get
(
0
);
int
i
=
1
;
List
<
Item
>
item_list
=
retval
.
getItem
();
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
);
List
<
Items
>
itemsList
=
response
.
getItems
();
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
());
try
{
Thread
.
sleep
(
5000
);
// in production, do something useful!
}
catch
(
Exception
e
)
{
}
System
.
out
.
println
(
"\nmain is exiting..."
);
}
static
class
MyHandler
implements
AsyncHandler
<
NextNResponse
>
{
public
void
handleResponse
(
Response
<
NextNResponse
>
future
)
{
try
{
NextNResponse
response
=
future
.
get
();
List
<
Integer
>
nums
=
response
.
getReturn
();
for
(
Integer
num
:
nums
)
System
.
out
.
println
(
num
);
}
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
>
</
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.