13.4. Putting It All Together
With that
fairly basic understanding of WSDL added to the UDDI discussion,
you're ready for a complete web services example. In this
section, I detail the process of writing a SOAP service (a messaging
one, this time), registering it with a UDDI registry, finding it
using UDDI, getting the WSDL descriptor, and then interacting with
the service via a client.
For the example, I add a little more complexity. Here's the
scenario. CDs-R-Us is a new company that wants to provide CDs to
distributors around the world. Since they are late to
market, they seek to gain business by providing a high-tech
interface, using web services to make interaction easy. They are
going to provide the ability to send XML messages requesting CDs
through SOAP. Their applications will fulfill these orders by looking
up the CD on their catalog server (running, of course, a heavy-duty
version of the CDCatalog service from last chapter), and then
returning an invoice. There are actually two SOAP transactions going
on: one from the client to CDs-R-Us, which is messaging-based, and
one internal to CDs-R-Us, which is RPC-based. Figure 13-3 shows the complete process flow. They also
want to register their messaging service with a UDDI registry so
potential clients can find them.
Figure 13-3. Process flow for the example application
13.4.1. A Messaging Service
Since we'll
be using CDCatalog from last chapter for the RPC
client, I can skip right to the new code, the messaging service. This
should receive an XML purchase order and make a request to the
catalog service on another machine on CDs-R-Us's local network;
in other words, the messaging service is also a SOAP-RPC client. This
is perfectly legal in the web services world and quite common. One
business receives information from another, and in turn starts an
interaction with another business. If this still
seems odd, ask your home builder how many subcontractors he employs,
and then ask each of them how many subcontractors
they employ; it would probably blow your mind!
First, let's define the purchase order (PO) format that
CDs-R-Us requires. The XML
Schema for the PO document is shown in Example 13-2.
Example 13-2. po.xsd XML Schema
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.cds-r-us.com"
targetNamespace="http://www.cds-r-us.com">
<xs:element name="purchaseOrder">
<xs:complexType>
<xs:sequence>
<xs:element ref="recipient" />
<xs:element ref="order" />
</xs:sequence>
<xs:attribute name="orderDate" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="recipient">
<xs:complexType>
<xs:sequence>
<xs:element ref="name" />
<xs:element ref="street" />
<xs:element ref="city" />
<xs:element ref="state" />
<xs:element ref="postalCode" />
</xs:sequence>
<xs:attribute name="country" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="name" type="xs:string"/>
<xs:element name="street" type="xs:string" />
<xs:element name="city" type="xs:string" />
<xs:element name="state" type="xs:string" />
<xs:element name="postalCode" type="xs:short" />
<xs:element name="order">
<xs:complexType>
<xs:sequence>
<xs:element ref="cd" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="cd">
<xs:complexType>
<xs:attribute name="artist" type="xs:string" />
<xs:attribute name="title" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:schema>
With that schema in place, a typical PO would look something like
Example 13-3.
Example 13-3. Example PO for CDs
<purchaseOrder orderDate="07.23.2001"
xmlns="http://www.cds-r-us.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.cds-r-us.com po.xsd"
>
<recipient country="USA">
<name>Dennis Scannell</name>
<street>175 Perry Lea Side Road</street>
<city>Waterbury</city>
<state>VT</state>
<postalCode>05676</postalCode>
</recipient>
<order>
<cd artist="Brooks Williams" title="Little Lion" />
<cd artist="David Wilcox" title="What You Whispered" />
</order>
</purchaseOrder>
The
service should accept an XML document like Example 13-3, figure out what information is relevant, and
then pass that information on to the CD catalog service through RPC.
Once it gets a response, it formulates some sort of invoice or
acknowledgment for the messaging client, and sends that message back.
I keep things simple for the purpose of this example, but you can
easily see where to add additional processing as we walk through the
code.
Writing a service that accepts XML messages is a bit different from
writing one that accepts RPC requests; with messaging, you need to
interact more directly with the request and response objects, and
your class needs to know about SOAP. Remember that in the RPC-style
processing, the class receiving requests didn't know a thing
about RPC or SOAP, and was therefore encapsulated fairly well. With a
messaging-style service, all methods that can be interacted with must
follow this convention:
public void methodName(SOAPEnvelope env, SOAPContext req, SOAPContext res)
throws java.io.IOException, javax.mail.MessagingException;
This should feel somewhat similar to how servlets work; you get a
request and response object to interact with, as well as the actual
SOAP envelope for the message sent across the wire. You can see the
expected IOException that may be thrown when
network and related errors occur; additionally, a
MessagingException (from the JavaMail package) can
result from problems with the SOAP message envelope.
Additionally, the method name must be
the same as the name of the root element of the message
content! This is easy to forget; in our
case, it means that the method receiving XML must be called
purchaseOrder, since that is the root element in
Example 13-3. With this knowledge, it's
possible to set up the skeleton for a message service. This skeleton
is shown in Example 13-4; in addition to putting in
the framework for receiving a SOAP message, it also has the logic to
make the appropriate call to the CDCatalog service
on another machine. I've left a comment as a placeholder for
the messaging code we'll look at in a moment.
Example 13-4. Skeleton for CDs-R-Us messaging service
package javaxml2;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import javax.mail.MessagingException;
// SOAP imports
import org.apache.soap.Constants;
import org.apache.soap.Envelope;
import org.apache.soap.Fault;
import org.apache.soap.SOAPException;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.encoding.soapenc.BeanSerializer;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
import org.apache.soap.rpc.SOAPContext;
import org.apache.soap.util.xml.QName;
public class OrderProcessor {
/** Mapping for CD class */
private SOAPMappingRegistry registry;
/** The serializer for the CD class */
private BeanSerializer serializer;
/** The RPC Call object */
private Call call;
/** Parameters for call */
private Vector params;
/** Response from RPC call */
private Response rpcResponse;
/** The URL to connect to */
private URL rpcServerURL;
public void initialize( ) {
// Set up internal URL for SOAP-RPC
try {
rpcServerURL =
new URL("http://localhost:8080/soap/servlet/rpcrouter");
} catch (MalformedURLException neverHappens) {
// ignored
}
// Set up a SOAP mapping to translate CD objects
registry = new SOAPMappingRegistry( );
serializer = new BeanSerializer( );
registry.mapTypes(Constants.NS_URI_SOAP_ENC,
new QName("urn:cd-catalog-demo", "cd"),
CD.class, serializer, serializer);
// Build a Call to the internal SOAP service
call = new Call( );
call.setSOAPMappingRegistry(registry);
call.setTargetObjectURI("urn:cd-catalog");
call.setMethodName("getCD");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
// Set up input
params = new Vector( );
}
public void purchaseOrder(Envelope env, SOAPContext req, SOAPContext res)
throws IOException, MessagingException {
// Set up SOAP environment
initialize( );
// Set up list of CDs successfully ordered
List orderedCDs = new LinkedList( );
// Set up hashtable of failed orders
Hashtable failedCDs = new Hashtable( );
// Parse incoming message and get list of CDS ordered
// Loop through each ordered CD from the PO request
String artist = "";
String title = "";
// Set up input
params.clear( );
params.addElement(new Parameter("title", String.class, title, null));
call.setParams(params);
try {
// Invoke the call
rpcResponse = call.invoke(rpcServerURL, "");
if (!rpcResponse.generatedFault( )) {
Parameter returnValue = rpcResponse.getReturnValue( );
CD cd = (CD)returnValue.getValue( );
// See if the CD is available
if (cd == null) {
failedCDs.put(title, "Requested CD is not available.");
continue;
}
// Verify it's by the right artist
if (cd.getArtist( ).equalsIgnoreCase(artist)) {
// Add this CD to the successful orders
orderedCDs.add(cd);
} else {
// Add this to the failed orders
failedCDs.put(title, "Incorrect artist for specified CD.");
}
} else {
Fault fault = rpcResponse.getFault( );
failedCDs.put(title, fault.getFaultString( ));
}
} catch (SOAPException e) {
failedCDs.put(title, "SOAP Exception: " + e.getMessage( ));
}
// At the end of the loop, return something useful to the client
}
}
NOTE:
In this example and in the rest of the chapter, I use the hostname
http://localhost:8080 to
represent a SOAP service running on your local machine. Most of you
will be testing the example locally, and this will help you avoid
putting in fictional hostnames and getting frustrated when things
don't work.
In a real environment you would expect the client to connect to a CDs-R-Us machine, like http://www.cds-r-us.com, and the messaging service to connect to an internal machine running the CD catalog, such as http://catalog.cds-r-us.com, perhaps behind an external firewall. Still, I'd rather your code work right away than try and put false hostnames in the example code. That's why everything uses the local machine as the hostname.
I briefly run through what is going on here, and then get to the
interesting aspect: the messaging interaction. First, the
initialize( ) method is used to set up an RPC call
for each client. This Call object is used and
reused, so no resources are wasted on a single client. At the same
time, each client gets their own Call object,
ensuring that synchronization and threading issues don't
surface. Next, some storage is set up: a List for
successful orders, and a Hashtable for failed
ones. The Hashtable has the title of the ordered
CD as the key, and error information as the value. Then, the SOAP
message from the client would be read (here's where I've
left a placeholder, for now). For each CD ordered, a looping process
begins. The CD title and artist are extracted from the message, and
an RPC call is invoked to obtain the requested CD object. Depending
on the result from the request to the CD catalog, the CD is added to
the list of successful or failed orders. At the end of the loop, a
message would be constructed and sent back to the client.
It's worth noting that the CDCatalog is a
simple version, and not complete in this context. A real CD catalog
service would probably check for a CD in its inventory, ensure copies
are available, remove one CD from the inventory, report its SKU, etc.
In this case, all the CD catalog service does is check for the
requested CD in its list of available CDs. Still, you get the
idea.
Now that this skeleton is in place, you are ready to interact with
the user's message. Let's take care of some additional
classes that will be used. Add the import statements shown
here:
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import javax.mail.MessagingException;
// DOM
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
// SOAP imports
import org.apache.soap.Constants;
import org.apache.soap.Envelope;
import org.apache.soap.Fault;
import org.apache.soap.SOAPException;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.encoding.soapenc.BeanSerializer;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
import org.apache.soap.rpc.SOAPContext;
import org.apache.soap.util.xml.QName;
The code needs to use DOM to work with the XML in the message sent by
the client; that message is what I want to look at first. You
remember the XML shown in Example 13-3, which is the
content of the message that the service expects to receive. However,
the message will be wrapped in some SOAP specifics, and ends up
looking like Example 13-5 before it's sent. The
extra information is used by SOAP to allow interpretation of the
message.
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<purchaseOrder orderDate="07.23.2001"
xmlns="urn:cd-order-service"
>
<recipient country="USA">
<name>Dennis Scannell</name>
<street>175 Perry Lea Side Road</street>
<city>Waterbury</city>
<state>VT</state>
<postalCode>05676</postalCode>
</recipient>
<order>
<cd artist="Brooks Williams" title="Little Lion" />
<cd artist="David Wilcox" title="What You Whispered" />
</order>
</purchaseOrder>
</s:Body>
</s:Envelope>
The actual message is in the body of the SOAP envelope. The
corollaries to these structures in Apache SOAP are
org.apache.soap.Envelope
and
org.apache.soap.Body.
To get the entries in the body, use
envelope.getBody().getBodyEntries(
)
, which returns a Vector.
The first (and only) item in this Vector in this
example turns out to be a DOM Element, which is
the Java equivalent for the XML purchaseOrder
element. That, of course, is exactly what we want. Once that element
is obtained, you can use normal DOM methods to walk the DOM tree and
get each ordered CD. Add the following code to your
purchaseOrder( ) method, which extracts and
iterates through each CD requested by the client:
public void purchaseOrder(Envelope env, SOAPContext req, SOAPContext res)
throws IOException, MessagingException {
// Set up SOAP environment
initialize( );
// Set up list of CDs successfully ordered
List orderedCDs = new LinkedList( );
// Set up hashtable of failed orders
Hashtable failedCDs = new Hashtable( );
// Get the purchaseOrder element - always the first body entry
Vector bodyEntries = env.getBody().getBodyEntries( );
Element purchaseOrder = (Element)bodyEntries.iterator().next( );
// In a real application, do something with the buyer information
// Get the CDs ordered
Element order =
(Element)purchaseOrder.getElementsByTagName("order").item(0);
NodeList cds = order.getElementsByTagName("cd");
// Loop through each ordered CD from the PO request
for (int i=0, len=cds.getLength( ); i<len; i++) {
Element cdElement = (Element)cds.item(i);
String artist = cdElement.getAttribute("artist");
String title = cdElement.getAttribute("title");
// Set up input for SOAP-RPC call, shown in Example 13-4
params.clear( );
params.addElement(new Parameter("title", String.class, title, null));
call.setParams(params);
try {
// Existing RPC code from code, shown in Example 13-4
} catch (SOAPException e) {
failedCDs.put(title, "SOAP Exception: " + e.getMessage( ));
}
}
// At the end of the loop, return something useful to the client
}
Once this code completes, the list of ordered CDs that were
successful is in the orderedCDs
List, and the failed orders are in the
failedCDs Hashtable. Since the
client already knows how to "speak"
XML (it sent an XML message), it makes sense to send an XML response.
Rather than constructing a response from scratch, formatting it for
SOAP, and manually responding, it's possible to use the
Envelope object the code just read from. Add in
the code shown here, which generates a response:
public void purchaseOrder(Envelope env, SOAPContext req, SOAPContext res)
throws IOException, MessagingException {
// Existing code for Messaging parsing, shown above
// Loop through each ordered CD from the PO request
for (int i=0, len=cds.getLength( ); i<len; i++) {
Element cdElement = (Element)cds.item(i);
String artist = cdElement.getAttribute("artist");
String title = cdElement.getAttribute("title");
// Set up input
params.clear( );
params.addElement(new Parameter("title", String.class, title, null));
call.setParams(params);
try {
// Existing RPC code from code, shown in Example 13-4
} catch (SOAPException e) {
failedCDs.put(title, "SOAP Exception: " + e.getMessage( ));
}
}
// At the end of the loop, return something useful to the client
Document doc = new org.apache.xerces.dom.DocumentImpl( );
Element response = doc.createElement("response");
Element orderedCDsElement = doc.createElement("orderedCDs");
Element failedCDsElement = doc.createElement("failedCDs");
response.appendChild(orderedCDsElement);
response.appendChild(failedCDsElement);
// Add the ordered CDs
for (Iterator i = orderedCDs.iterator(); i.hasNext( ); ) {
CD orderedCD = (CD)i.next( );
Element cdElement = doc.createElement("orderedCD");
cdElement.setAttribute("title", orderedCD.getTitle( ));
cdElement.setAttribute("artist", orderedCD.getArtist( ));
cdElement.setAttribute("label", orderedCD.getLabel( ));
orderedCDsElement.appendChild(cdElement);
}
// Add the failed CDs
Enumeration keys = failedCDs.keys( );
while (keys.hasMoreElements( )) {
String title = (String)keys.nextElement( );
String error = (String)failedCDs.get(title);
Element failedElement = doc.createElement("failedCD");
failedElement.setAttribute("title", title);
failedElement.appendChild(doc.createTextNode(error));
failedCDsElement.appendChild(failedElement);
}
// Set this as the content of the envelope
bodyEntries.clear( );
bodyEntries.add(response);
StringWriter writer = new StringWriter( );
env.marshall(writer, null);
// Send the envelope back to the client
res.setRootPart(writer.toString( ), "text/xml");
}
This builds up a new XML tree with the successful and failed orders
within it. It then sets the tree as the content of the
envelope's Body, replacing the original
request from the client. Next, the envelope has to be converted to a
textual format, which can be sent as the response using the
SOAPContext res object. SOAP
provides a means of doing this, through the marshall(
)
method. Supplying the method a
StringWriter means that the value "dropped
into" that writer can be extracted as a
String for use later. The second argument is an
instance of
org.apache.soap.util.xml.XMLJavaMappingRegistry.
An example is the SOAPMappingRegistry class, a
subclass of XMLJavaMappingRegistry used earlier
and in the last chapter; since no special types need to be mapped, a
null argument suffices.
Finally, the result of all this work and serialization is set as the
content of the response, through the setRootPart(
)
element. The second value of this method is the HTML-style content
type. Since the code sends back XML, the correct value is
"text/xml". Once the client gets that response, it can
figure out the content is in an XML format and decode it. Other than
setting this content on the SOAPContext response
object, you don't need to do anything else in order to
communicate back to the client. Once this method completes, the SOAP
server will automatically return that object to the client, along
with any information you've put within it. This is also a lot
like working with the HttpServletResponse object
in servlets, if you are familiar with that methodology.
At this point, you can compile the OrderProcessor
class, and deploy it to your SOAP server:
java org.apache.soap.server.ServiceManagerClient
http://localhost:8080/soap/servlet/rpcrouter deploy xml/OrderProcessorDD.xml
Once this is done, you're ready to register the service with a
UDDI registry.
13.4.2. Registering with UDDI
To begin the process of registering
your service, make sure it is publicly accessible. You can't
register a service that is only available on your local machine
(http://localhost:8080 and the
like). If you are in the testing or experimentation phase, read
through this section and file it away for later use. If you are ready
to actually register a service on a network somewhere, be sure you
know the hostname of the machine where it can be accessed. Then hop
online and visit http://www.uddi.org.
Once you're at the UDDI website, click the
"Register" link at the top right of the screen (see Figure 13-2 if you are lost at this point). Then select
the node to register your service within. Right now, registering in
one makes it accessible in all, so this is a fairly meaningless
decision. I chose IBM in my case, and click the "Go"
button. At this point, you'll need an account to access the IBM
registry. If you don't have one, it's free to sign up and
get one; click the "Register" button over on the left,
and follow the instructions there. Once you're registered,
login through the "Login" link on the left (see Figure 13-4). You also must provide an activation key,
supplied via email after registration on your first login.
Figure 13-4. Logging into the IBM UDDI registry
Once you make it through the registration and account activation
process, you're ready to publish your service. First select
whether you want to add a new business; this is generally a good
idea. I've added a new business, shown in Figure 13-5.
Figure 13-5. Adding a new business to the UDDI registry
You can then add services and attach them to your business, which
adds an additional layer of organization to your searches. You can
optionally add business contacts, locations, and more. Once
that's done, you are ready to add your service to the registry.
Select "Add a new service" and enter in the name of your
service; in this example, it would be
cd-order-service. You'll be given options to
enter the description, access point, and service location for the
service. I entered "This service allows ordering CDs via a
purchase order" as my description. For my access point, I
selected "http" and then
"newInstance.com/soap/servlet/rpcrouter" for the access
point. Do the same for your service, using your own hostname and URL.
You can then specify a service locator, which is a formal set of
standards for categorizing your service; I'm not going to get
into this here, but you can read up on it at the web site. Once
you're finished entering information, you should have something
that looks similar to Figure 13-6.
Figure 13-6. Results from adding a service
At this point things begin to get a little less efficent.
Unfortunately, there is no capability to upload a WSDL document
describing your service: that would allow a description, in technical
terms, to be made available with your service for those who may want
to be clients of the service. The only information allowed is the
service name and access point (or points, if it's available at
multiple locations). However, this does make it possible for anyone
with a UDDI registration and login to search for a service and a
related business. So you've registered your service, and
it's available for anyone else to search for.
13.4.3. Searching a UDDI Registry
The opposite side of this coin is looking
for a service; I'm moving from the service provider realm into
that of the client, the service consumer. If you want to use SOAP,
WSDL, and the rest, visit the UDDI web site's registry online
and login, then search for services (like the one you just
registered). This is simple: just click the "Find" link,
select the node to search within (again, I chose "IBM"),
and enter the name of the service to look for. You'll need to
login if you haven't already.
This is another area where web services is still evolving; searches
are pretty basic, and can only be done based on a service name. If I
name my service "Reading Library Service" and you enter
"book" as the search text, you'll never find my
service. You would need to enter "reading" or
"library" to get my service. Still, it is a good start.
Once you've registered your service, you can enter
"cd" as your search text. Specify that you want to find
services, as opposed to businesses or service types. Then, click the
"Find" button on the search screen. You should get
results similar to those shown in Figure 13-7 (which
includes the CD service I added, and may include other readers'
services once this book is out).
Figure 13-7. Search results with search text of "cd"
You can click on the service name and find the access point I entered
earlier, as well as any other information I supplied (such as whether
I selected a service type or category). Again, you might expect to be
able to download WSDL from this service description, but that's
not currently an option. At this point, you contact the business
which has a service you are interested in, determine available
methods to use from them, and set up any cost-based arragements
required to use their services.
The UDDI registries still have some work left; however, the
infrastructure is in place for a powerful means of registering and
searching for web services. Additionally, as WSDL becomes available
to upload (for service providers) and download (for service
consumers), personal interaction will not be required to use a
service. Despite the lack of human contact and immersion in LCD
screens, this means more services are used, which in turn causes more
services to be developed.
13.4.4. WSDL
Now I spend some time talking about how useful
WSDL documents are. I'll
tell you how to take one and run a simple tool, like the IBM WSTK
mentioned earlier in the chapter, and generate a Java client.
I'd like to describe how WSDL is a lifesaver, right now, today!
However, that's not yet the case; instead, I'll let you
know what is going on with Java and WSDL, so you'll be prepared
when all the pieces fall into place.
Expect to see an array of tools beginning to appear that allow you to
take a Java service, like the OrderProcessor or
CDCatalog classes, and generate WSDL for those
services. In fact, some of these tools are already available.
IBM's WSTK is one I've already mentioned, and there are
other packages from The Mind Electric
(
Glue, at
http://www.themindelectric.com),
Silverstream (http://www.silverstream.com), and SOAPWiz
(http://www.bju.edu/cps/faculty/sschaub/soapwiz/).
I tried these with varying levels of success. In simple cases like
CDCatalog, the tools could usually generate WSDL
(although IBM's toolkit choked on the method retuning a
Hashtable). This is because the methods expect as
input, and return as output, fairly primitive Java types like
String and CD, which is made up
of primitives.
The problems began when I tried to use these tools on the
OrderProcessor class. Since this class is
message-based, instead of RPC-based, it has some complex types as
input: Envelope and
SOAPContext. Because these are complex types,
which in turn are made up of complex types, WSDL generators tend to
get confused very fast, and generally end up choking and spewing out
stack traces. The tools still have some work to do in order to handle
message-based SOAP services or extremely complex RPC-based services.
The end result is twofold: first, it should get you excited about the
future. As tools emerge that can handle these more complex types, it
will be easy to generate WSDL from even complex SOAP services. Then,
those same tools can even generate clients that speak to these
services. Imagine searching the UDDI registry for a service,
downloading its WSDL descriptor, and using a tool to generate a
client that speaks to the service. With only a small amount of code
modification specific to your business needs, you're working
with the new service. The future is bright for web services (as well
as for a book on the subject, I imagine!).
The second result is that you are still going to have to pick up a
phone and talk to someone about their service, at least for the short
term. Once someone tells you the method signatures that you can
interact with, or sends it to you in email (we programmer-types tend
to be poor communicators in person), you are ready to code up a
client, as I now describe.
13.4.5. Writing a Client
Once you find the service you want, a
set of methods to use, and the messages you're allowed to send,
you're ready to write a client. Example 13-6
shows this client, ready to compile and use.
Example 13-6. The CDOrderer client
package javaxml2;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
// SAX and DOM imports
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
// SOAP imports
import org.apache.soap.Constants;
import org.apache.soap.Envelope;
import org.apache.soap.SOAPException;
import org.apache.soap.messaging.Message;
import org.apache.soap.transport.SOAPTransport;
import org.apache.soap.util.xml.XMLParserUtils;
public class CDOrderer {
public void order(URL serviceURL, String msgFilename)
throws IOException, SAXException, SOAPException {
// Parse the XML message
FileReader reader = new FileReader(msgFilename);
DocumentBuilder builder = XMLParserUtils.getXMLDocBuilder( );
Document doc = builder.parse(new InputSource(reader));
if (doc == null) {
throw new SOAPException(Constants.FAULT_CODE_CLIENT,
"Error parsing XML message.");
}
// Create the message envelope
Envelope msgEnvelope = Envelope.unmarshall(doc.getDocumentElement( ));
// Send the message
Message msg = new Message( );
msg.send(serviceURL, "urn:cd-order-service", msgEnvelope);
// Handle the response
SOAPTransport transport = msg.getSOAPTransport( );
BufferedReader resReader = transport.receive( );
String line;
while ((line = resReader.readLine( )) != null) {
System.out.println(line);
}
}
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: java javaxml2.CDOrderer " +
"[XML Message Filename]");
return;
}
try {
URL serviceURL =
new URL("http://localhost:8080/soap/servlet/messagerouter");
CDOrderer orderer = new CDOrderer( );
orderer.order(serviceURL, args[0]);
} catch (Exception e) {
e.printStackTrace( );
}
}
}
This is a very simple, trivial client. It reads in an XML message
supplied on the command line, and converts that to a SOAP
Envelope. The
org.apache.soap.messaging.Message object is used
to encapsulate the envelope and send it to the service URN and
hostname specified. The response is obtained through the
SOAPTransport from the Message,
and I simply echoed the resulting message to the screen. In your own
applications, you could write this to another XML file, extract it
and use DOM or JDOM to manipulate it, and then continue processing.
Instead of directly instantiating an instance of
org.apache.xerces.dom.DocumentImpl, I used the
JAXP
DocumentBuilder
class and SOAP's
XMLUtils
class to avoid a vendor-specific piece of code. This is a better
practice than that shown in the
OrderProcessor
class, where the Xerces class is directly referenced. I show both
just to give you a good idea of the difference; I recommend changing
the code in OrderProcessor to mimic the client
shown here.
Once you've ensured all the
required SOAP client classes are in your classpath, and compiled the
CDOrderer class, you are ready to try things out.
Ensure that the services at urn:cd-order-service
and urn:cd-catalog are deployed and available.
Additionally, you may want to add one or both of the CDs in the
po.xml document, from Example 13-2 and Example 13-3. I tried it
once with a single CD added to the catalog, to see both a successful
and failed order, and then with both added to see it succeed on both:
C:\javaxml2\build>java javaxml2.CDAdder
http://localhost:8080/soap/servlet/rpcrouter
"Little Lion" "Brooks Williams" "Signature Sounds"
Adding CD titled 'Little Lion' by 'Brooks Williams', on the label
Signature Sounds
Successful CD Addition.
Make sure you have the SOAP-suitable XML shown in Example 13-5 saved; I used the filename poMsg.xml in my xml directory. Finally, you're ready to
run the client:
bmclaugh@GANDALF
$ java javaxml2.CDOrderer c:\javaxml2\ch13\xml\poMsg.xml
<?xml version='1.0' encoding='UTF-8'?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<response>
<orderedCDs>
<orderedCD artist="Brooks Williams" label="Signature Sounds"
title="Little Lion"/>
</orderedCDs>
<failedCDs>
<failedCD title="What You Whispered">Requested CD is not available.</failedCD>
</failedCDs>
</response>
</s:Body>
</s:Envelope>
The program spits out the XML response from the server; I formatted
my response to make it more readable. You should get a similar output
(in this example, I've added the Brooks Williams CD but not the
David Wilcox CD to the catalog, as shown previously) in your tests.
At this point, you should be feeling pretty comfortable with writing
both SOAP servers and client. Additionally, you probably realize that
UDDI and WSDL are not that complex. Used with SOAP, they provide a
nice framework for web services and interbusiness communication. I
also recommend you take a look at some of the more advanced
properties of Apache SOAP, or the SOAP implementation you are using.
For example, Apache SOAP supports using SMTP (the Simple Mail
Transport Protocol) as a transport for RPC and messaging. I
don't cover this, because it's a more advanced SOAP
topic, and because the specification does not yet cover SMTP as a
transport. In other words, it's implementation-dependent, and I
avoid those cases as much as possible. In any case, getting to know
the ins and outs of your SOAP implementation only increases the
effectiveness of your web services.
13.4.6. Where Do I Go From Here?
If you're like me, you probably are ready for about three or
four more chapters of this stuff. A chapter on working with UDDI
registries programmatically, another chapter on working with WSDL,
some more examples. . . it would be fun. Of course, then this would
be a book on web services, not Java and XML. However, there are
several resources online you should check out to get to the next
level. First, try
http://www.webservices.org, which has a lot
of additional introductory material. Then check out IBM's site
on the subject, at http://www.ibm.com/developerworks/webservices;
if you are working with Microsoft clients (C#, Visual Basic, and COM
objects), you'll want to visit http://msdn.microsoft.com/soap. In other
words, use this chapter as a solid jumping off point, visit the web
services sites mentioned here, and look for upcoming books from
O'Reilly on SOAP.
 |  |  | 13.3. WSDL |  | 13.5. What's Next? |
Copyright © 2002 O'Reilly & Associates. All rights reserved.
|