Chapter 1. Java Web Services Quickstart
What Are Web Services?
Although the term web service has various, imprecise, and evolving meanings, a glance at some features typical of web services will be enough to get us into coding a web service and a client, also known as a consumer or requester. As the name suggests, a web service is a kind of webified application, that is, an application typically delivered over HTTP (Hyper Text Transport Protocol). A web service is thus a distributed application whose components can be deployed and executed on distinct devices. For instance, a stock-picking web service might consist of several code components, each hosted on a separate business-grade server, and the web service might be consumed on PCs, handhelds, and other devices.
Web services can be divided roughly into two groups, SOAP-based and REST-style. The distinction is not sharp because, as a code example later illustrates, a SOAP-based service delivered over HTTP is a special case of a REST-style service. SOAP originally stood for Simple Object Access Protocol but, by serendipity, now may stand for Service Oriented Architecture (SOA) Protocol. Deconstructing SOA is nontrivial but one point is indisputable: whatever SOA may be, web services play a central role in the SOA approach to software design and development. (This is written with tongue only partly in cheek. SOAP is officially no longer an acronym, and SOAP and SOA can live apart from one another.) For now, SOAP is just an XML (EXtensible Markup Language) dialect in which documents are messages. In SOAP-based web services, the SOAP is mostly unseen infrastructure. For example, in a typical scenario, called the request/response message exchange pattern (MEP), the client’s underlying SOAP library sends a SOAP message as a service request, and the web service’s underlying SOAP library sends another SOAP message as the corresponding service response. The client and the web service source code may provide few hints, if any, about the underlying SOAP (see Figure 1-1).
REST stands for REpresentational State Transfer. Roy Fielding, one of the main authors of the HTTP specification, coined the acronym in his Ph.D. dissertation to describe an architectural style in the design of web services. SOAP has standards (under the World Wide Web Consortium [W3C]), toolkits, and bountiful software libraries. REST has no standards, few toolkits, and meager software libraries. The REST style is often seen as an antidote to the creeping complexity of SOAP-based web services. This book covers SOAP-based and REST-style web services, starting with the SOAP-based ones.
Except in test mode, the client of either a SOAP-based or REST-style service is rarely a web browser but rather an application without a graphical user interface. The client may be written in any language with the appropriate support libraries. Indeed, a major appeal of web services is language transparency: the service and its clients need not be written in the same language. Language transparency is the key to web service interoperability; that is, the ability of web services and requesters to interact seamlessly despite differences in programming languages, support libraries, and platforms. To underscore this appeal, clients against our Java web services will be written in various languages such as C#, Perl, and Ruby, and Java clients will consume services written in other languages, including languages unknown.
There is no magic in language transparency, of course. If a SOAP-based web service written in Java can have a Perl or a Ruby consumer, there must be an intermediary that handles the differences in data types between the service and the requester languages. XML technologies, which support structured document interchange and processing, act as the intermediary. For example, in a typical SOAP-based web service, a client transparently sends a SOAP document as a request to a web service, which transparently returns another SOAP document as a response. In a REST-style service, a client might send a standard HTTP request to a web service and receive an appropriate XML document as a response.
Several features distinguish web services from other distributed software systems. Here are three:
- Open infrastructure
Web services are deployed using industry-standard, vendor-independent protocols such as HTTP and XML, which are ubiquitous and well understood. Web services can piggyback on networking, data formatting, security, and other infrastructures already in place, which lowers entry costs and promotes interoperability among services.
- Language transparency
Web services and their clients can interoperate even if written in different programming languages. Languages such as C/C++, C#, Java, Perl, Python, Ruby, and others provide libraries, utilities, and even frameworks in support of web services.
- Modular design
Web services are meant to be modular in design so that new services can be generated through the integration and layering of existing services. Imagine, for example, an inventory-tracking service integrated with an online ordering service to yield a service that automatically orders the appropriate products in response to inventory levels.
What Good Are Web Services?
This obvious question has no simple, single answer. Nonetheless, the chief benefits and promises of web services are clear. Modern software systems are written in a variety of languages—a variety that seems likely to increase. These software systems will continue to be hosted on a variety of platforms. Institutions large and small have significant investment in legacy software systems whose functionality is useful and perhaps mission critical; and few of these institutions have the will and the resources, human or financial, to rewrite their legacy systems.
It is rare that a software system gets to run in splendid isolation. The typical software system must interoperate with others, which may reside on different hosts and be written in different languages. Interoperability is not just a long-term challenge but also a current requirement of production software.
Web services address these issues directly because such services are, first and foremost, language- and platform-neutral. If a legacy COBOL system is exposed through a web service, the system is thereby interoperable with service clients written in other programming languages.
Web services are inherently distributed systems that communicate mostly over HTTP but can communicate over other popular transports as well. The communication payloads of web services are structured text (that is, XML documents), which can be inspected, transformed, persisted, and otherwise processed with widely and even freely available tools. When efficiency demands it, however, web services also can deliver binary payloads. Finally, web services are a work in progress with real-world distributed systems as their test bed. For all of these reasons, web services are an essential tool in any modern programmer’s toolbox.
The examples that follow, in this chapter and the others, are meant to be simple enough to isolate critical features of web services but also realistic enough to illustrate the power and flexibility that such services bring to software development. Let the examples begin.
A First Example
The first example is a SOAP-based web service in Java and clients in Perl, Ruby, and Java. The Java-based web service consists of an interface and an implementation.
The Service Endpoint Interface and Service Implementation Bean
The first web service in Java, like almost all of the others in this book, can be compiled and deployed using core Java SE 6 (Java Standard Edition 6) or greater without any additional software. All of the libraries required to compile, execute, and consume web services are available in core Java 6, which supports JAX-WS (Java API for XML-Web Services). JAX-WS supports SOAP-based and REST-style services. JAX-WS is commonly shortened to JWS for Java Web Services. The current version of JAX-WS is 2.x, which is a bit confusing because version 1.x has a different label: JAX-RPC. JAX-WS preserves but also significantly extends the capabilities of JAX-RPC.
A SOAP-based web service could be implemented as a single Java class but, following best practices, there should be an interface that declares the methods, which are the web service operations, and an implementation, which defines the methods declared in the interface. The interface is called the SEI: Service Endpoint Interface. The implementation is called the SIB: Service Implementation Bean. The SIB can be either a POJO or a Stateless Session EJB (Enterprise Java Bean). Chapter 6, which deals with the GlassFish Application Server, shows how to implement a web service as an EJB. Until then, the SOAP-based web services will be implemented as POJOs, that is, as instances of regular Java classes. These web services will be published using library classes that come with core Java 6 and, a bit later, with standalone Tomcat and GlassFish.
Example 1-1 is the SEI for a web service that returns the current time as either a string or as the elapsed milliseconds from the Unix epoch, midnight January 1, 1970 GMT.
package ch01.ts; // time server import javax.jws.WebService; import javax.jws.WebMethod; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.Style; /** * The annotation @WebService signals that this is the * SEI (Service Endpoint Interface). @WebMethod signals * that each method is a service operation. * * The @SOAPBinding annotation impacts the under-the-hood * construction of the service contract, the WSDL * (Web Services Definition Language) document. Style.RPC * simplifies the contract and makes deployment easier. */ @WebService @SOAPBinding(style = Style.RPC) // more on this later public interface TimeServer { @WebMethod String getTimeAsString(); @WebMethod long getTimeAsElapsed(); }
Example 1-2 is the SIB, which implements the SEI.
package ch01.ts; import java.util.Date; import javax.jws.WebService; /** * The @WebService property endpointInterface links the * SIB (this class) to the SEI (ch01.ts.TimeServer). * Note that the method implementations are not annotated * as @WebMethods. */ @WebService(endpointInterface = "ch01.ts.TimeServer") public class TimeServerImpl implements TimeServer { public String getTimeAsString() { return new Date().toString(); } public long getTimeAsElapsed() { return new Date().getTime(); } }
The two files are compiled in the usual way from the current
working directory, which in this case is immediately above the
subdirectory ch01. The symbol
%
represents the command
prompt:
% javac ch01/ts/*.java
A Java Application to Publish the Web Service
Once the SEI and SIB have been compiled, the web service is ready to be published. In full production mode, a Java Application Server such as BEA WebLogic, GlassFish, JBoss, or WebSphere might be used; but in development and even light production mode, a simple Java application can be used. Example 1-3 is the publisher application for the TimeServer service.
package ch01.ts; import javax.xml.ws.Endpoint; /** * This application publishes the web service whose * SIB is ch01.ts.TimeServerImpl. For now, the * service is published at network address 127.0.0.1., * which is localhost, and at port number 9876, as this * port is likely available on any desktop machine. The * publication path is /ts, an arbitrary name. * * The Endpoint class has an overloaded publish method. * In this two-argument version, the first argument is the * publication URL as a string and the second argument is * an instance of the service SIB, in this case * ch01.ts.TimeServerImpl. * * The application runs indefinitely, awaiting service requests. * It needs to be terminated at the command prompt with control-C * or the equivalent. * * Once the applicatation is started, open a browser to the URL * * http://127.0.0.1:9876/ts?wsdl * * to view the service contract, the WSDL document. This is an * easy test to determine whether the service has deployed * successfully. If the test succeeds, a client then can be * executed against the service. */ public class TimeServerPublisher { public static void main(String[ ] args) { // 1st argument is the publication URL // 2nd argument is an SIB instance Endpoint.publish("http://127.0.0.1:9876/ts", new TimeServerImpl()); } }
Once compiled, the publisher can be executed in the usual way:
% java ch01.ts.TimeServerPublisher
How the Endpoint Publisher Handles Requests
Out of the box, the Endpoint
publisher handles one client
request at a time. This is fine for getting web services up and
running in development mode. However, if the processing of a given
request should hang, then all other client requests are effectively
blocked. An example at the end of this chapter shows how Endpoint
can handle requests concurrently
so that one hung request does not block the others.
Testing the Web Service with a Browser
We can test the deployed service by opening a browser and viewing the WSDL (Web Service Definition Language) document, which is an automatically generated service contract. (WSDL is pronounced “whiz dull.”) The browser is opened to a URL that has two parts. The first part is the URL published in the Java TimeServerPublisher application: http://127.0.0.1:9876/ts. Appended to this URL is the query string ?wsdl in upper-, lower-, or mixed case. The result is http://127.0.0.1:9876/ts?wsdl. Example 1-4 is the WSDL document that the browser displays.
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://ts.ch01/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://ts.ch01/" name="TimeServerImplService"> <types></types> <message name="getTimeAsString"></message> <message name="getTimeAsStringResponse"> <part name="return" type="xsd:string"></part> </message> <message name="getTimeAsElapsed"></message> <message name="getTimeAsElapsedResponse"> <part name="return" type="xsd:long"></part> </message><portType name="TimeServer">
<operation name="getTimeAsString" parameterOrder="">
<input message="tns:getTimeAsString"></input>
<output message="tns:getTimeAsStringResponse"></output>
</operation>
<operation name="getTimeAsElapsed" parameterOrder="">
<input message="tns:getTimeAsElapsed"></input>
<output message="tns:getTimeAsElapsedResponse"></output>
</operation>
</portType>
<binding name="TimeServerImplPortBinding" type="tns:TimeServer"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"> </soap:binding> <operation name="getTimeAsString"> <soap:operation soapAction=""></soap:operation> <input> <soap:body use="literal" namespace="http://ts.ch01/"></soap:body> </input> <output> <soap:body use="literal" namespace="http://ts.ch01/"></soap:body> </output> </operation> <operation name="getTimeAsElapsed"> <soap:operation soapAction=""></soap:operation> <input> <soap:body use="literal" namespace="http://ts.ch01/"></soap:body> </input> <output> <soap:body use="literal" namespace="http://ts.ch01/"></soap:body> </output> </operation> </binding><service name="TimeServerImplService">
<port name="TimeServerImplPort" binding="tns:TimeServerImplPortBinding">
<soap:address location="http://localhost:9876/ts"></soap:address>
</port>
</service>
</definitions>
Chapter 2 examines the WSDL in
detail and introduces Java utilities associated with the service
contract. For now, two sections of the WSDL (both shown in bold)
deserve a quick look. The portType
section, near the top, groups the operations that the web service
delivers, in this case the operations getTimeAsString
and getTimeAsElapsed
, which are the two Java
methods declared in the SEI and implemented in the SIB. The WSDL
portType
is like a Java interface
in that the portType
presents the
service operations abstractly but provides no implementation detail.
Each operation in the web service consists of an input
and an output
message, where
input means input for the web
service. At runtime, each message is a SOAP document. The other
WSDL section of interest is the last, the service
section, and in particular the
service location
, in this case the
URL http://localhost:9876/ts. The
URL is called the service endpoint and it informs
clients about where the service can be accessed.
The WSDL document is useful for both creating and executing
clients against a web service. Various languages have utilities for
generating client-support code from a WSDL. The core Java utility is
now called wsimport but the earlier
names wsdl2java and
java2wsdl were more descriptive. At runtime, a
client can consume the WSDL document associated with a web service in
order to get critical information about the data types associated with
the operations bundled in the service. For example, a client could
determine from our first WSDL that the operation getTimeAsElapsed
returns an integer and
expects no arguments.
The WSDL also can be accessed with various utilities such as curl. For example, the command:
% curl http://localhost:9876/ts?wsdl
also displays the WSDL.
A Perl and a Ruby Requester of the Web Service
To illustrate the language transparency of web services, the first client against the Java-based web service is not in Java but rather in Perl. The second client is in Ruby. Example 1-5 is the Perl client.
#!/usr/bin/perl -w use SOAP::Lite; my $url = 'http://127.0.0.1:9876/ts?wsdl'; my $service = SOAP::Lite->service($url); print "\nCurrent time is: ", $service->getTimeAsString(); print "\nElapsed milliseconds from the epoch: ", $service->getTimeAsElapsed();
On a sample run, the output was:
Current time is: Thu Oct 16 21:37:35 CDT 2008 Elapsed milliseconds from the epoch: 1224211055700
The Perl module SOAP::Lite
provides the under-the-hood functionality that allows the client
to issue the appropriate SOAP request and to process the resulting SOAP
response. The request URL, the same URL used to test the web service in
the browser, ends with a query string that asks for the WSDL document.
The Perl client gets the WSDL document from which the SOAP::Lite
library then generates the
appropriate service object (in Perl syntax, the scalar variable $service
). By consuming the WSDL document, the
SOAP::Lite
library gets the
information needed, in particular, the names of the web service
operations and the data types involved in these operations. Figure 1-2 depicts the architecture.
After the setup, the Perl client invokes the web service operations without any fuss. The SOAP messages remain unseen.
Example 1-6 is a Ruby client that is functionally equivalent to the Perl client.
#!/usr/bin/ruby # one Ruby package for SOAP-based services require 'soap/wsdlDriver' wsdl_url = 'http://127.0.0.1:9876/ts?wsdl' service = SOAP::WSDLDriverFactory.new(wsdl_url).create_rpc_driver # Save request/response messages in files named '...soapmsgs...' service.wiredump_file_base = 'soapmsgs' # Invoke service operations. result1 = service.getTimeAsString result2 = service.getTimeAsElapsed # Output results. puts "Current time is: #{result1}" puts "Elapsed milliseconds from the epoch: #{result2}"
The Hidden SOAP
In SOAP-based web services, a client typically makes a remote procedure call against the service by invoking one of the web service operations. As mentioned earlier, this back and forth between the client and service is the request/response message exchange pattern, and the SOAP messages exchanged in this pattern allow the web service and a consumer to be programmed in different languages. We now look more closely at what happens under the hood in our first example. The Perl client generates an HTTP request, which is itself a formatted message whose body is a SOAP message. Example 1-7 is the HTTP request from a sample run.
POST http://127.0.0.1:9876/ts HTTP/ 1.1 Accept: text/xml Accept: multipart/* Accept: application/soap User-Agent: SOAP::Lite/Perl/0.69 Content-Length: 434 Content-Type: text/xml; charset=utf-8 SOAPAction: "" <?xml version="1.0" encoding="UTF-8"?> <soap:Envelope soap:encodingStyle="http:// schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/soap/ envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://ts.ch01/" xmlns:xsd ="http://www.w3.org/2001/XMLSchema"> <soap:Body> <tns:getTimeAsString xsi:nil="true" /> </soap:Body> </soap:Envelope>
The HTTP request is a message with a structure of its own. In particular:
The HTTP start line comes first and specifies the request method, in this case the POST method, which is typical of requests for dynamic resources such as web services or other web application code (for example, a Java servlet) as opposed to requests for a static HTML page. In this case, a POST rather than a GET request is needed because only a POST request has a body, which encapsulates the SOAP message. Next comes the request URL followed by the HTTP version, in this case 1.1, that the requester understands. HTTP 1.1 is the current version.
Next come the HTTP headers, which are key/value pairs in which a colon (
:
) separates the key from the value. The order of the key/value pairs is arbitrary. The keyAccept
occurs three times, with a MIME (Multipurpose Internet Mail Extensions) type/subtype as the value:text/xml
,multipart/*
, andapplication/soap
. These three pairs signal that the requester is ready to accept an arbitrary XML response, a response with arbitrarily many attachments of any type (a SOAP message can have arbitrarily many attachments), and a SOAP document, respectively. The HTTP keySOAPAction
is often present in the HTTP header of a web service request and the key’s value may be the empty string, as in this case; but the value also might be the name of the requested web service operation.Two CRLF (Carriage Return Line Feed) characters, which correspond to two Java
\n
characters, separate the HTTP headers from the HTTP body, which is required for the POST verb but may be empty. In this case, the HTTP body contains the SOAP document, commonly called the SOAP envelope because the outermost or document element is namedEnvelope
. In this SOAP envelope, the SOAP body contains a single element whose local name isgetTimeAsString
, which is the name of the web service operation that the client wants to invoke. The SOAP request envelope is simple in this example because the requested operation takes no arguments.
On the web service side, the underlying Java libraries process the
HTTP request, extract the SOAP envelope, determine the identity of the
requested service operation, invoke the corresponding Java method
getTimeAsString
, and then generate
the appropriate SOAP message to carry the method’s return value back to
the client. Example 1-8 is the HTTP response from
the Java TimeServerImpl
service
request shown in Example 1-7.
HTTP/1.1 200 OK Content-Length: 323 Content-Type: text/xml; charset=utf-8 Client-Date: Mon, 28 Apr 2008 02:12:54 GMT Client-Peer: 127.0.0.1:9876 Client-Response-Num: 1 <?xml version="1.0" ?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soapenv:Body> <ans:getTimeAsStringResponse xmlns:ans="http://ts.ch01/"> <return>Mon Apr 28 14:12:54 CST 2008</return> </ans:getTimeAsStringResponse> </soapenv:Body> </soapenv:Envelope>
Once again the SOAP envelope is the body of an HTTP message, in
this case the HTTP response to the client. The HTTP start
line now contains the status code as the integer 200 and the
corresponding text OK
, which signal
that the client request was handled successfully. The SOAP envelope in
the HTTP response’s body contains the current time as a string between
the XML start and end tags named return
. The Perl SOAP library extracts the
SOAP envelope from the HTTP response and, because of information in the
WSDL document, expects the desired return value from the web service
operation to occur in the XML return
element.
A Java Requester of the Web Service
Example 1-9 is a Java client functionally equivalent to the Perl and Ruby clients shown in Examples 1-5 and 1-6, respectively.
package ch01.ts; import javax.xml.namespace.QName; import javax.xml.ws.Service; import java.net.URL; class TimeClient { public static void main(String args[ ]) throws Exception { URL url = new URL("http://localhost:9876/ts?wsdl"); // Qualified name of the service: // 1st arg is the service URI // 2nd is the service name published in the WSDL QName qname = new QName("http://ts.ch01/", "TimeServerImplService"); // Create, in effect, a factory for the service. Service service = Service.create(url, qname); // Extract the endpoint interface, the service "port". TimeServer eif = service.getPort(TimeServer.class); System.out.println(eif.getTimeAsString()); System.out.println(eif.getTimeAsElapsed()); } }
The Java client uses the same URL with a query string as do the
Perl and Ruby clients, but the Java client explicitly creates an XML
qualified name, which has the syntax namespace URI:local
name. A URI is a Uniform Resource Identifier and differs from the more
common URL in that a URL specifies a location,
whereas a URI need not specify a location. In short, a URI need
not be a URL. For now, it is enough to underscore that the Java class
java.xml.namespace.QName
represents
an XML-qualified name. In this example, the namespace URI is provided in
the WSDL, and the local name is the SIB class name TimeServerImpl
with the word Service
appended. The local name occurs in the
service
section, the last section of
the WSDL document.
Once the URL
and QName
objects have been constructed and the
Service.create
method has been
invoked, the statement of interest:
TimeServer eif = service.getPort(TimeServer.class);
executes. Recall that, in the WSDL document, the portType
section describes, in the style of an
interface, the operations included in the web service. The getPort
method returns a reference to a Java
object that can invoke the portType
operations. The eif
object reference
is of type ch01.ts.TimeServer
, which
is the SEI type. The Java client, like the Perl client, invokes the two
web service methods; and the Java libraries, like the Perl and Ruby
libraries, generate and process the SOAP messages exchanged
transparently to enable the successful method invocations.
Wire-Level Tracking of HTTP and SOAP Messages
Example 1-7 and Example 1-8 show an HTTP request message and an HTTP response message,
respectively. Each HTTP message encapsulates a SOAP envelope. These
message traces were done with the Perl client by changing the Perl
use
directive in Example 1-5:
use SOAP::Lite;
to:
use SOAP::Lite +trace;
The Ruby client in Example 1-6 contains a line:
service.wiredump_file_base = 'soapmsgs'
that causes the SOAP envelopes to be saved in files on the local disk. It is possible to capture the wire-level traffic directly in Java as well, as later examples illustrate. Various options are available for tracking SOAP and HTTP messages at the wire level. Here is a short introduction to some of them.
The tcpmon utility (available at https://tcpmon.dev.java.net)
is free and downloads as an executable JAR file. Its graphical user
interface (GUI) is easy to use. The utility requires only three
settings: the server’s name, which defaults to localhost
; the server’s port, which would be
set to 9876
for the TimeServer
example because this is the port at
which Endpoint
publishes the service;
and the local port, which defaults to 8080
and is the port at which
tcpmon listens. With tcpmon in
use, the TimeClient
would send its
requests to port 8080
instead of port
9876
. The tcpmon
utility intercepts HTTP traffic between the client and web service,
displaying the full messages in its GUI.
The Metro release has utility classes for tracking HTTP and SOAP traffic.
This approach does not require any change to the client or to the
service code; however, an additional package must be put on the
classpath and a system property must be set either at the command line
or in code. The required package is in the file jaxws_ri/jaxws-rt.jar. Assuming that the
environment variable METRO_HOME
points to the jaxws-ri
directory, here is the command that tracks HTTP and SOAP traffic between
the TimeClient
, which connects to the
service on port 9876, and the TimeServer
service. (Under Windows, $METRO_HOME
becomes %METRO_HOME%
.) The command is on three lines
for readability:
% java -cp ".":$METRO_HOME/lib/jaxws-rt.jar \ -Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true \ ch01.ts.TimeClient
The resulting dump shows all of the SOAP traffic but not all of the HTTP headers. Message tracking also can be done on the service side.
There are various other open source and commercial products available for tracking the SOAP traffic. Among the products that are worth a look at are SOAPscope, NetSniffer, and Wireshark. The tcpdump utility comes with most Unix-type systems, including Linux and OS X, and is available on Windows as WinDump. Besides being free, tcpdump is nonintrusive in that it requires no change to either a web service or a client. The tcpdump utility dumps message traffic to the standard output. The companion utility tcptrace (http://www.tcptrace.org) can be used to analyze the dump. The remainder of this section briefly covers tcpdump as a flexible and powerful trace utility.
Under Unix-type systems, the tcpdump utility typically must be executed as superuser. There are various flagged arguments that determine how the utility works. Here is a sample invocation:
% tcpdump -i lo -A -s 1024 -l 'dst host localhost and port 9876' | tee dump.log
The utility can capture packets on any network interface. A list
of such interfaces is available with the tcpdump -D
(under Windows, WinDump -D), which is equivalent to
the ifconfig -a command on Unix-like systems. In this example, the flag/value
pair -i lo
means
capture packets from the interface lo, where
lo is short for the localhost network interface on
many Unix-like systems. The flag -A
means that the captured packets should be presented in ASCII, which is
useful for web packets as these typically contain text. The -s 1024
flag sets the snap length, the number of bytes
that should be captured from each packet. The flag -l
forces the standard output to be line
buffered and easier to read; and, on the same theme, the construct
| tee dump.log
at the end pipes the
same output that shows up on the screen (the standard output) into a
local file named dump.log. Finally,
the expression:
'dst host localhost and port 9876'
acts as a filter, capturing only packets whose destination is
localhost on port 9876, the port on which TimeServerPublisher
of Example 1-3 publishes the TimeServer
service.
The tcpdump utility and the TimeServerPublisher
application can be started
in any order. Once both are running, the TimeClient
or one of the other clients can be
executed. With the sample use of tcpdump shown
above, the underlying network packets are saved in the file dump.log. The file does require some editing
to make it easily readable. In any case, the dump.log file captures the same SOAP
envelopes shown in Examples 1-7 and 1-8.
What’s Clear So Far?
The first example is a web
service with two operations, each of which delivers the current time but
in different representations: in one case as a human-readable string,
and in the other case as the elapsed milliseconds from the Unix epoch.
The two operations are implemented as independent, self-contained
methods. From the service requester’s perspective, either method may be
invoked independently of the other and one invocation of a service
method has no impact on any subsequent invocation of the same service
method. The two Java methods depend neither on one another nor on any
instance field to which both have access; indeed, the SIB class TimeServerImpl
has no fields at all. In short,
the two method invocations are stateless.
In the first example, neither method expects arguments. In general, web service operations may be parameterized so that appropriate information can be passed to the operation as part of the service request. Regardless of whether the web service operations are parameterized, they still should appear to the requester as independent and self-contained. This design principle will guide all of the samples that we consider, even ones that are richer than the first.
Key Features of the First Code Example
The TimeServerImpl
class
implements a web service with a distinctive message exchange pattern
(MEP)—request/response. The service allows a client to make a language-neutral remote procedure call,
invoking the methods getTimeAsString
and getTimeAsElapsed
. Other message
patterns are possible. Imagine, for example, a web service that tracks
new snow amounts for ski areas. Some participating clients, perhaps
snow-measuring electrical devices strategically placed around the ski
slopes, might use the one-way pattern by sending a snow amount from a
particular location but without
expecting a response from the service. The service might exhibit the
notification pattern by multicasting to subscribing clients (for
instance, travel bureaus) information about current snow conditions.
Finally, the service might periodically use the solicit/response
pattern to ask a subscribing client whether the client wishes to
continue receiving notifications. In summary, SOAP-based web services
support various patterns. The request/response pattern of RPC remains
the dominant one. The infrastructure needed to support this pattern in
particular is worth summarizing:
- Message transport
SOAP is designed to be transport-neutral, a design goal that complicates matters because SOAP messages cannot rely on protocol-specific information included in the transport infrastructure. For instance, SOAP delivered over HTTP should not differ from SOAP delivered over some other transport protocol such as SMTP (Simple Mail Transfer Protocol), FTP (File Transfer Protocol), or even JMS (Java Message Service). In practice, however, HTTP is the usual transport for SOAP-based services, a point underscored in the usual name: SOAP-based web services.
- Service contract
The service client requires information about the service’s operations in order to invoke them. In particular, the client needs information about the invocation syntax: the operation’s name, the order and types of the arguments passed to the operation, and the type of the returned value. The client also requires the service endpoint, typically the service URL. The WSDL document provides these pieces of information and others. Although a client could invoke a service without first accessing the WSDL, this would make things harder than they need to be.
- Type system
The key to language neutrality and, therefore, service/consumer interoperability is a shared type system so that the data types used in the client’s invocation coordinate with the types used in the service operation. Consider a simple example. Suppose that a Java web service has the operation:
boolean bytes_ok(byte[ ] some_bytes)
The
bytes_ok
operation performs some validation test on the bytes passed to the operation as an array argument, returning eithertrue
orfalse
. Now assume that a client written in C needs to invokebytes_ok
. C has no types namedboolean
andbyte
. C represents boolean values with integers, with nonzero astrue
and zero asfalse
; and the C typesigned char
corresponds to the Java typebyte
. A web service would be cumbersome to consume if clients had to map client-language types to service-language types. In SOAP-based web services, the XML Schema type system is the default type system that mediates between the client’s types and the service’s types. In the example above, the XML Schema typexsd:byte
is the type that mediates between the Csigned char
and the Javabyte
; and the XML Schema typexsd:boolean
is the mediating type for the C integers nonzero and zero and the Javaboolean
valuestrue
andfalse
. In the notationxsd:byte
, the prefixxsd
(XML Schema Definition) underscores that this is an XML Schema type becausexsd
is the usual extension for a file that contains an XML Schema definition; for instance, purchaseOrder.xsd.
Java’s SOAP API
A major appeal of SOAP-based web services is that the SOAP usually
remains hidden. Nonetheless, it may be useful to glance at Java’s
underlying support for generating and processing SOAP messages. Chapter 3, which introduces SOAP handlers, puts
the SOAP API to practical use. This section provides a first look at the
SOAP API through a simulation example. The application consists of one
class, DemoSoap
, but simulates
sending a SOAP message as a request and receiving another as a response.
Example 1-10 shows the full application.
package ch01.soap; import java.util.Date; import java.util.Iterator; import java.io.InputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPPart; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.Node; import javax.xml.soap.Name; public class DemoSoap { private static final String LocalName = "TimeRequest"; private static final String Namespace = "http://ch01/mysoap/"; private static final String NamespacePrefix = "ms"; private ByteArrayOutputStream out; private ByteArrayInputStream in; public static void main(String[ ] args) { new DemoSoap().request(); } private void request() { try { // Build a SOAP message to send to an output stream. SOAPMessage msg = create_soap_message(); // Inject the appropriate information into the message. // In this case, only the (optional) message header is used // and the body is empty. SOAPEnvelope env = msg.getSOAPPart().getEnvelope(); SOAPHeader hdr = env.getHeader(); // Add an element to the SOAP header. Name lookup_name = create_qname(msg); hdr.addHeaderElement(lookup_name).addTextNode("time_request"); // Simulate sending the SOAP message to a remote system by // writing it to a ByteArrayOutputStream. out = new ByteArrayOutputStream(); msg.writeTo(out); trace("The sent SOAP message:", msg); SOAPMessage response = process_request(); extract_contents_and_print(response); } catch(SOAPException e) { System.err.println(e); } catch(IOException e) { System.err.println(e); } } private SOAPMessage process_request() { process_incoming_soap(); coordinate_streams(); return create_soap_message(in); } private void process_incoming_soap() { try { // Copy output stream to input stream to simulate // coordinated streams over a network connection. coordinate_streams(); // Create the "received" SOAP message from the // input stream. SOAPMessage msg = create_soap_message(in); // Inspect the SOAP header for the keyword 'time_request' // and process the request if the keyword occurs. Name lookup_name = create_qname(msg); SOAPHeader header = msg.getSOAPHeader(); Iterator it = header.getChildElements(lookup_name); Node next = (Node) it.next(); String value = (next == null) ? "Error!" : next.getValue(); // If SOAP message contains request for the time, create a // new SOAP message with the current time in the body. if (value.toLowerCase().contains("time_request")) { // Extract the body and add the current time as an element. String now = new Date().toString(); SOAPBody body = msg.getSOAPBody(); body.addBodyElement(lookup_name).addTextNode(now); msg.saveChanges(); // Write to the output stream. msg.writeTo(out); trace("The received/processed SOAP message:", msg); } } catch(SOAPException e) { System.err.println(e); } catch(IOException e) { System.err.println(e); } } private void extract_contents_and_print(SOAPMessage msg) { try { SOAPBody body = msg.getSOAPBody(); Name lookup_name = create_qname(msg); Iterator it = body.getChildElements(lookup_name); Node next = (Node) it.next(); String value = (next == null) ? "Error!" : next.getValue(); System.out.println("\n\nReturned from server: " + value); } catch(SOAPException e) { System.err.println(e); } } private SOAPMessage create_soap_message() { SOAPMessage msg = null; try { MessageFactory mf = MessageFactory.newInstance(); msg = mf.createMessage(); } catch(SOAPException e) { System.err.println(e); } return msg; } private SOAPMessage create_soap_message(InputStream in) { SOAPMessage msg = null; try { MessageFactory mf = MessageFactory.newInstance(); msg = mf.createMessage(null, // ignore MIME headers in); // stream source } catch(SOAPException e) { System.err.println(e); } catch(IOException e) { System.err.println(e); } return msg; } private Name create_qname(SOAPMessage msg) { Name name = null; try { SOAPEnvelope env = msg.getSOAPPart().getEnvelope(); name = env.createName(LocalName, NamespacePrefix, Namespace); } catch(SOAPException e) { System.err.println(e); } return name; } private void trace(String s, SOAPMessage m) { System.out.println("\n"); System.out.println(s); try { m.writeTo(System.out); } catch(SOAPException e) { System.err.println(e); } catch(IOException e) { System.err.println(e); } } private void coordinate_streams() { in = new ByteArrayInputStream(out.toByteArray()); out.reset(); } }
Here is a summary of how the application runs, with emphasis on
the code involving SOAP messages. The DemoSoap
application’s request
method generates a SOAP message and
adds the string time_request
to the
SOAP envelope’s header. The code segment, with comments removed,
is:
SOAPMessage msg = create_soap_message(); SOAPEnvelope env = msg.getSOAPPart().getEnvelope(); SOAPHeader hdr = env.getHeader(); Name lookup_name = create_qname(msg); hdr.addHeaderElement(lookup_name).addTextNode("time_request");
There are two basic ways to create a SOAP message. The simple way is illustrated in this code segment:
MessageFactory mf = MessageFactory.newInstance(); SOAPMessage msg = mf.createMessage();
In the more complicated way, the MessageFactory
code is the same, but the
creation call becomes:
SOAPMessage msg = mf.createMessage(mime_headers, input_stream);
The first argument to createMessage
is a collection of the
transport-layer headers (for instance, the key/value pairs that make up
an HTTP header), and the second argument is an input stream that
provides the bytes to create the message (for instance, the input stream
encapsulated in a Java Socket
instance).
Once the SOAP message is created, the header is extracted from the
SOAP envelope and an XML text node is inserted with the value time_request
. The resulting SOAP message
is:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header> <ms:TimeRequest xmlns:ms="http://ch01/mysoap/"> time_request </ms:TimeRequest> </SOAP-ENV:Header> <SOAP-ENV:Body/> </SOAP-ENV:Envelope>
There is no need right now to examine every detail of this SOAP
message. Here is a summary of some key points. The SOAP body is always
required but, as in this case, the body may be empty. The SOAP header is optional but, in this case, the header
contains the text time_request
.
Message contents such as time_request
normally would be placed in the SOAP body and special processing
information (for instance, user authentication data) would be placed in
the header. The point here is to illustrate how the SOAP header and the
SOAP body can be manipulated.
The request
method writes the
SOAP message to a ByteArrayOutputStream
, which simulates sending
the message over a network connection to a receiver on a different host.
The request
method invokes the
process_request
method, which in turn
delegates the remaining tasks to other methods. The processing goes as
follows. The received SOAP message is created from a ByteArrayInputStream
, which simulates an input
stream on the receiver’s side; this stream contains the sent SOAP
message. The SOAP message now is created from the input stream:
SOAPMessage msg = null; try { MessageFactory mf = MessageFactory.newInstance(); msg = mf.createMessage(null, // ignore MIME headers in); // stream source (ByteArrayInputStream) }
and then the SOAP message is processed to extract the time_request
string. The extraction goes as
follows. First, the SOAP header is extracted from the SOAP message and
an iterator over the elements with the tag name:
<ms:TimeRequest xmlns:ms="http://ch01/mysoap/>
is created. In this example, there is one element with this tag
name and the element should contain the string time_request
. The lookup code is:
SOAPHeader header = msg.getSOAPHeader(); Iterator it = header.getChildElements(lookup_name); Node next = (Node) it.next(); String value = (next == null) ? "Error!" : next.getValue();
If the SOAP header contains the proper request string, the SOAP body is extracted from the incoming SOAP message and an element containing the current time as a string is added to the SOAP body. The revised SOAP message then is sent as a response. Here is the code segment with the comments removed:
if (value.toLowerCase().contains("time_request")) { String now = new Date().toString(); SOAPBody body = msg.getSOAPBody(); body.addBodyElement(lookup_name).addTextNode(now); msg.saveChanges(); msg.writeTo(out); trace("The received/processed SOAP message:", msg); }
The outgoing SOAP message on a sample run was:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header> <ms:TimeRequest xmlns:ms="http://ch01/mysoap/"> time_request </ms:TimeRequest> </SOAP-ENV:Header> <SOAP-ENV:Body> <ms:TimeRequest xmlns:ms="http://ch01/mysoap/"> Mon Oct 27 14:45:53 CDT 2008 </ms:TimeRequest> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
This example provides a first look at Java’s API. Later examples illustrate production-level use of the SOAP API.
An Example with Richer Data Types
The operations in the TimeServer
service take no arguments and
return simple types, a string and an integer. This section offers a
richer example whose details are clarified in the next chapter.
The Teams
web service in Example 1-11 differs from the TimeServer
service in several important
ways.
package ch01.team; import java.util.List; import javax.jws.WebService; import javax.jws.WebMethod; @WebService public class Teams { private TeamsUtility utils; public Teams() { utils = new TeamsUtility(); utils.make_test_teams(); } @WebMethod public Team getTeam(String name) { return utils.getTeam(name); } @WebMethod public List<Team> getTeams() { return utils.getTeams(); } }
For one, the Teams
service is
implemented as a single Java class rather than as a separate SEI and
SIB. This is done simply to illustrate the possibility. A more important
difference is in the return types of the two Teams
operations. The operation getTeam
is parameterized and returns an object
of the programmer-defined type Team
,
which is a list of Player
instances,
another programmer-defined type. The operation getTeams
returns a List<Team>
, that is, a Java Collection
.
The utility class TeamsUtility
generates the data. In a production environment, this utility might
retrieve a team or list of teams from a database. To keep this example
simple, the utility instead creates the teams and their players on the
fly. Here is part of the utility:
package ch01.team; import java.util.Set; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; public class TeamsUtility { private Map<String, Team> team_map; public TeamsUtility() { team_map = new HashMap<String, Team>(); } public Team getTeam(String name) { return team_map.get(name); } public List<Team> getTeams() { List<Team> list = new ArrayList<Team>(); Set<String> keys = team_map.keySet(); for (String key : keys) list.add(team_map.get(key)); return list; } public void make_test_teams() { List<Team> teams = new ArrayList<Team>(); ... Player chico = new Player("Leonard Marx", "Chico"); Player groucho = new Player("Julius Marx", "Groucho"); Player harpo = new Player("Adolph Marx", "Harpo"); List<Player> mb = new ArrayList<Player>(); mb.add(chico); mb.add(groucho); mb.add(harpo); Team marx_brothers = new Team("Marx Brothers", mb); teams.add(marx_brothers); store_teams(teams); } private void store_teams(List<Team> teams) { for (Team team : teams) team_map.put(team.getName(), team); } }
Publishing the Service and Writing a Client
Recall that the SEI for the TimeServer
service contains the
annotation:
@SOAPBinding(style = Style.RPC)
This annotation requires that the service use only very simple
types such as string and integer. By contrast, the Teams
service uses richer data types, which
means that Style.DOCUMENT
, the
default, should replace Style.RPC.
The document style does require more setup, which is given below but
not explained until the next chapter. Here, then, are the steps
required to get the web service deployed and a sample client written
quickly:
The source files are compiled in the usual way. From the working directory, which has ch01 as a subdirectory, the command is:
% javac ch01/team/*.java
In addition to the
@WebService
-annotatedTeams
class, the ch01/team directory contains theTeam
,Player
,TeamsUtility
, andTeamsPublisher
classes shown below all together:package ch01.team; public class Player { private String name; private String nickname; public Player() { } public Player(String name, String nickname) { setName(name); setNickname(nickname); } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setNickname(String nickname) { this.nickname = nickname; } public String getNickname() { return nickname; } } // end of Player.java package ch01.team; import java.util.List; public class Team { private List<Player> players; private String name; public Team() { } public Team(String name, List<Player> players) { setName(name); setPlayers(players); } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setPlayers(List<Player> players) { this.players = players; } public List<Player> getPlayers() { return players; } public void setRosterCount(int n) { } // no-op but needed for property public int getRosterCount() { return (players == null) ? 0 : players.size(); } } } // end of Team.java package ch01.team; import javax.xml.ws.Endpoint; class TeamsPublisher { public static void main(String[ ] args) { int port = 8888; String url = "http://localhost:" + port + "/teams"; System.out.println("Publishing Teams on port " + port); Endpoint.publish(url, new Teams()); } }
In the working directory, invoke the wsgen utility, which comes with core Java 6:
% wsgen -cp . ch01.team.Teams
This utility generates various artifacts; that is, Java types needed by the method
Endpoint.publish
to generate the service’s WSDL. Chapter 2 looks closely at these artifacts and how they contribute to the WSDL.Execute the
TeamsPublisher
application.In the working directory, invoke the wsimport utility, which likewise comes with core Java 6:
% wsimport -p teamsC -keep http://localhost:8888/teams?wsdl
This utility generates various classes in the subdirectory teamsC (the
-p
flag stands forpackage
). These classes make it easier to write a client against the service.
Step 4 expedites the coding of a client, which is shown here:
import teamsC.TeamsService; import teamsC.Teams; import teamsC.Team; import teamsC.Player; import java.util.List; class TeamClient { public static void main(String[ ] args) { TeamsService service = new TeamsService(); Teams port = service.getTeamsPort(); List<Team> teams = port.getTeams(); for (Team team : teams) { System.out.println("Team name: " + team.getName() + " (roster count: " + team.getRosterCount() + ")"); for (Player player : team.getPlayers()) System.out.println(" Player: " + player.getNickname()); } } }
When the client executes, the output is:
Team name: Abbott and Costello (roster count: 2) Player: Bud Player: Lou Team name: Marx Brothers (roster count: 3) Player: Chico Player: Groucho Player: Harpo Team name: Burns and Allen (roster count: 2) Player: George Player: Gracie
This example hints at what is possible in a commercial-grade,
SOAP-based web service. Programmer-defined types such as Player
and Team
, along with arbitrary collections of
these, can be arguments passed to or values returned from a web
service so long as certain guidelines are followed. One guideline
comes into play in this example. For the Team
and the Player
classes, the JavaBean properties are
of types String
or int
; and a List
, like any Java Collection
, has a toArray
method. In the end, a List<Team>
reduces to arrays of simple
types; in this case String
instances or int
values. The next
chapter covers the details, in particular how the
wsgen and the wsimport
utilities facilitate the development of JWS services and
clients.
Multithreading the Endpoint Publisher
In the examples so far, the Endpoint
publisher has been single-threaded
and, therefore, capable of handling only one client request at a time:
the published service completes the processing of one request before
beginning the processing of another request. If the processing of the
current request hangs, then no subsequent request can be processed
unless and until the hung request is processed to completion.
In production mode, the Endpoint
publisher would need to handle
concurrent requests so that several pending requests could be processed
at the same time. If the underlying computer system is, for example,
a symmetric multiprocessor (SMP), then separate CPUs could
process different requests concurrently. On a single-CPU machine, the
concurrency would occur through time sharing; that is, each request
would get a share of the available CPU cycles so that several requests
would be in some stage of processing at any given time. In Java,
concurrency is achieved through multithreading. At issue, then, is how
to make the Endpoint
publisher
multithreaded. The JWS framework supports Endpoint
multithreading without forcing the
programmer to work with difficult, error-prone constructs such as the
synchronized
block or the wait
and notify
method invocations.
An Endpoint
object has an
Executor
property defined with the
standard get
/set
methods. An Executor
is an object that executes Runnable
tasks; for example, standard Java
Thread
instances. (The Runnable
interface declares only one method
whose declaration is public void
run()
.) An Executor
object
is a nice alternative to Thread
instances, as the Executor
provides
high-level constructs for submitting and managing tasks that are to be
executed concurrently. The first step to making the Endpoint
publisher multithreaded is thus to
create an Executor
class such as the
following very basic one:
package ch01.ts; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; public class MyThreadPool extends ThreadPoolExecutor { private static final int pool_size = 10; private boolean is_paused; private ReentrantLock pause_lock = new ReentrantLock(); private Condition unpaused = pause_lock.newCondition(); public MyThreadPool(){ super(pool_size, // core pool size pool_size, // maximum pool size 0L, // keep-alive time for idle thread TimeUnit.SECONDS, // time unit for keep-alive setting new LinkedBlockingQueue<Runnable>(pool_size)); // work queue } // some overrides protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); pause_lock.lock(); try { while (is_paused) unpaused.await(); } catch (InterruptedException e) { t.interrupt(); } finally { pause_lock.unlock(); } } public void pause() { pause_lock.lock(); try { is_paused = true; } finally { pause_lock.unlock(); } } public void resume() { pause_lock.lock(); try { is_paused = false; unpaused.signalAll(); } finally { pause_lock.unlock(); } } }
The class MyThreadPool
creates
a pool of 10 threads, using a fixed-size queue to store the threads that
are created under the hood. If the pooled threads are all in use, then
the next task in line must wait until one of the busy threads becomes
available. All of these management details are handled automatically.
The MyThreadPool
class overrides a
few of the available methods to give the flavor.
A MyThreadPool
object can be
used to make a multithreaded Endpoint
publisher. Here is the revised publisher, which now consists of several
methods to divide the work:
package ch01.ts; import javax.xml.ws.Endpoint; class TimePublisherMT { // MT for multithreaded private Endpoint endpoint; public static void main(String[ ] args) { TimePublisherMT self = new TimePublisherMT(); self.create_endpoint(); self.configure_endpoint(); self.publish(); } private void create_endpoint() { endpoint = Endpoint.create(new TimeServerImpl()); } private void configure_endpoint() { endpoint.setExecutor(new MyThreadPool()); } private void publish() { int port = 8888; String url = "http://localhost:" + port + "/ts"; endpoint.publish(url); System.out.println("Publishing TimeServer on port " + port); } }
Once the ThreadPoolExecutor
has
been coded, all that remains is to set the Endpoint
publisher’s executor property to an
instance of the worker class. The details of thread management do not
intrude at all into the publisher.
The multithreaded Endpoint
publisher is suited for lightweight production, but this publisher is
not a service container in the true sense; that is,
a software application that readily can deploy many web services at the
same port. A web container such as Tomcat, which is the reference
implementation, is better suited to publish multiple web services.
Tomcat is introduced in later examples.
What’s Next?
A SOAP-based web service should provide, as a WSDL document, a
service contract for its potential clients. So far we have seen how a
Perl, a Ruby, and a Java client can request the WSDL at runtime for use
in the underlying SOAP libraries. Chapter 2 studies the WSDL more closely and
illustrates how it may be used to generate client-side artifacts such as
Java classes, which in turn ease the coding of web service clients. The
Java clients in Chapter 2 will not be
written from scratch, as is our first Java client. Instead such clients
will be written with the considerable aid of the
wsimport utility, as was the TeamClient
shown earlier. Chapter 2 also introduces JAX-B (Java API for
XML-Binding), a collection of Java packages that coordinate Java data
types and XML data types. The wsgen
utility generates JAX-B artifacts that play a key role in this
coordination; hence, wsgen also will get a closer
look.
Get Java Web Services: Up and Running 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.