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.
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.
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"
![]()
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"
>
![]()
<
xs:
complexType
>
![]()
<
xs:
sequence
>
![]()
...
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
.
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.
restful
to restful2
. The xjc-generated files could be kept in a
separate package, of course.
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
));
![]()
// Create a JAX-B context for unmarshaling
JAXBContext
ctx
=
JAXBContext
.
newInstance
(
ItemLookupResponse
.
class
);
![]()
Unmarshaller
um
=
ctx
.
createUnmarshaller
();
![]()
um
.
setSchema
(
schema
);
![]()
// Generate a Java ItemSearchResponse instance.
ItemLookupResponse
ilr
=
(
ItemLookupResponse
)
um
.
unmarshal
(
new
ByteArrayInputStream
(
xml
.
getBytes
()));
![]()
// Use the standard POJO idiom to extract the author.
List
<
Items
>
itemsList
=
ilr
.
getItems
();
// list of lists
![]()
for
(
Items
items
:
itemsList
)
{
// outer list
![]()
List
<
Item
>
list
=
items
.
getItem
();
// inner list
![]()
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
![]()
}
}
}
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:
JAXBContext
is used to get information about the class of interest, in this
case the xjc-generated class, ItemLookupResponse
(line 2). This class represents
the E-Commerce response from a lookup operation against the service.
JAXBContext
instance is used to create an unmarshaler (line 3), which
is initialized with the E-Commerce schema (lines 1 and 4).
ItemLookupResponse
class. At this point, there is
no need to parse any XML because the ItemLookupResponse
object can be used instead
to find desired information, in this case the author’s name.
In the current example, the RestfulAmazon
client looks up only one item, a book. In
general, however, a look-up request against the E-Commerce service might yield
many hits instead of just one. At the data structure level, the result is lists nested
inside lists; hence, code lines 6 through 8 work from the outermost to the innermost list.
Here, for a closer look, is the looping in isolation:
List
<
Items
>
itemsList
=
ilr
.
getItems
();
// list of lists
![]()
for
(
Items
items
:
itemsList
)
{
// outer list
List
<
Item
>
list
=
items
.
getItem
();
// inner list
for
(
Item
item
:
list
)
{
// items in inner list
![]()
ItemAttributes
attributes
=
item
.
getItemAttributes
();
![]()
List
<
String
>
authors
=
attributes
.
getAuthor
();
author
=
authors
.
get
(
0
);
// in this case, only one
![]()
}
}
The getItems
method encapsulated in the ItemLookupResponse
object returns a List<Items>
(line 1)
but each
element in this list is itself a list. In the second for
loop (line 2), individual
items are finally available; indeed, in this response there is exactly one
such Item
, whose ItemAttributes
(line 3) include the name of the author, J. K. Rowling. Her name
occurs as the first and only name in a list of authors (line 4) because, of course, even a single
book might have multiple authors.
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.