home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book HomeJava and XML, 2nd EditionSearch this book

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

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.

Example 13-5. The SOAP-ready document from Example 13-3

<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

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

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

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

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.



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.