HomeDigital EditionSys-Con RadioSearch Web Services Cd
B2B Beginning WS Business Process Management Case Studies Content Management Distributing Computing e-Business Electronic Data Interchange Enterprise Industry Insight Integration Interviews Java & Web Services .NET Portal Product Reviews Scalability & Performance Security SOAP Source Code UDDI Wireless WS Standards WS Tips & Techniques WSDL WS Editorials XML

Source Code for this article

If your company is like most, it is likely that your suppliers, vendors, distributors, and partners have exposed dozens of Web services for your use.

What do you do if you want to try several Web services before deciding whether to integrate them with your IT environment, and you don't have the time to write a client for each? And what is the best way to handle verbose results that make it difficult to extract the information that is meaningful to your company?

Although some companies let you download free tools to invoke Web services, they generally don't give you much understanding of how a request is generated and usually won't go beyond showing you a raw response. You may want to roll up your sleeves and explore the many standards and APIs such as SAAJ and XSLT that are available to see how you can invoke Web services and generate useful reports. During this process you can begin building a framework for integrating Web services into your environment.

In this article we'll describe how to develop a generic tool that allows you to select the WSDL file of any Web service, provide the required input when prompted, run the Web service to view the result, and construct a report containing only the data of interest to you. Our generic tool, which we will call WSClient for the purposes of this discussion, uses industry standards and readily available Java APIs such as WSDL4J, SAAJ, Castor, JDOM, and XSLT.

WSClient Overview
Our WSClient will allow us to select a WSDL file, regardless of whether it resides on our local machine, a private intranet, or the Internet. After loading and analyzing the file, WSClient will display the available operations - such as GetFundQuote, GetHistoricalQuote, and so on. For each defined operation, WSClient will display the structure of the corresponding input and output messages.

When we select an operation, our tool will create a dummy XML message that conforms to the operation schema. We can then fill in the input values and click the Execute button to run the Web service. WSClient will then send a SOAP message to invoke the Web service and display the response, which will be a valid result or a SOAP fault describing a failure.

Let's look at how we'll load the WSDL file, create sample XML input, invoke the Web service through SOAP, and generate a report from the response.

WSDL Parsing and Analysis
WSDL (Web Services Description Language) defines an XML grammar that is used to describe Web services. WSDL separates the abstract definitions of messages and endpoints from their concrete data formats and deployments.

WSDL describes Web services by providing the details of the communication requirements necessary for a client to invoke the Web service. Specifically, WSDL is an XML model that describes the messages that need to be exchanged between a client and a service provider. Additionally, WSDL describes how and where a Web service is invoked.

A WSDL document uses the elements shown in Table 1 for defining a Web service.

Each element will be used to supply the information we need to invoke a Web service. WSClient will use IBM's Web Services Description Language for Java Toolkit (WSDL4J) to programmatically analyze the structure of the WSDL and identify the operations available for consumption. WSDL4J is the open source reference implementation of the Java APIs for WSDL (JWSDL) being developed under the Java Community Process.

Our demonstration Web service is named "XigniteQuotes" and is available to the public from Xignite, Inc. The XigniteQuotes Web service returns a full month of historical quotes for any U.S. Equity. (You can view the WSDL for this Web service at www.xignite.com/xquotes.asmx?WSDL.) WSClient will be able to read this service description to get the details required to consume this Web service.

Finding the Services and Operations
Our tool defines a class named ComponentBuilder that uses WSDL4J, the Castor Schema Object Model, and JDOM to analyze and compose an in-memory model of the Web service. The ComponentBuilder class creates an instance of WSDL4J's WSDLReader interface to load the WSDL definition. The WSDLReader interface defines the necessary methods for us to turn any WSDL into an in-memory model of a service description. We pass to the readWSDL() method the URI of our WSDL document and receive an instance of the Definition interface.

The Definition interface defines the methods we need to begin analyzing the WSDL definition and makes it possible to programmatically discover the defined services, their operations, the data types, and the service's endpoint URI.

Now that we have an in-memory definition of the WSDL document, we ask for the services that are defined. The Definition interface's getServices() method returns a collection of Service instances that our tool will iterate. Each Service instance will contain a group of related ports, and each port is represented as an instance of the Port interface. From the Port instance we can discover the binding referred to by the defined port. A binding is represented as an instance of the Binding interface, which gives us information on the binding operations that are represented as instances of the BindingOperation interface (see Listing 1).

Finding the Parts
Using the BindingOperation we can find details about the concrete implementation of an abstract operation defined in WSDL. Our tool currently supports only SOAP operations. WSDL4J's support for extensions to WSDL include the ExtensibilityElement interface. WSDL4J defines the SOAPOperation as a type of ExtensibilityElement to give us the information we need about the concrete implementation. From the SOAPOperation instance we can determine the encoding style and the SOAP action URI if one is defined.

The defined operation is represented as an instance of the Operation interface that is obtained by calling the BindingOperation's getOperation() method. The message structures of the operation's input and output are accessible as Message objects. Message definitions are obtained by calling the getMessage() method defined on the Input and Output interfaces.

The Message interface defines a method named getParts() to retrieve the SOAP parts that have been defined for this message. A message part is represented as an instance of the Part interface. The Part interface defines methods for retrieving a part's name, element name, and data type name (see Listing 2).

We can use this information to get the associated schema for each message part. This allows us to construct the request message to be sent to the Web service and to process the response generated by the invocation.

Creating Sample XML Input
Now that we know our message parts, we can create sample XML to use as request data when invoking the Web service. The Definition interface defines a method called getTypes() that we call to get the instance of the Types interface. The WSDL4J Types interface represents the <types> element defined in a WSDL document. We read the schema defined by the types element into an in-memory model using the Castor API.

In our sample WSDL document our <types> element contains the schema shown in Listing 3.

For each complex message part, we will refer to this schema to build an XML message that will be passed as part of the SOAP message sent during the Web service invocation. If we expect that the response part will be of a complex type we can refer to the type defined in the schema to process the response message.

The WSClient's ComponentBuilder class defines a method named buildMessageText() that takes as its input a WSDL4J Message instance. From the Message object we can obtain the list of parts that define the message. The parts are iterated and sample input text is built for the message parts. For each part processed we check to see if there is a complex type defined for it in our Castor Schema Object Model.

For each message part, we generate an initial XML instance that can be used to invoke the Web service. For example, our demonstration Web service takes as the request message the element named "GetQuotesHistorical". Using the element defined in the schema above an appropriate input message would be:

<GetQuotesHistorical xmlns="http://www.xignite.com/services/">
<Symbol>SUNW</Symbol>
<Month>12</Month>
<Year>2002</Year>
</GetQuotesHistorical>

This generated message is then saved to our OperationInfo instance and is then used as the initial message to be sent when we invoke the service (see Listing 4).

SOAP Invocation
WSClient uses the Java SAAJ API to invoke the Web service. SAAJ provides a flexible, yet fairly straightforward, way to consume a Web service. During our WSDL analysis we captured the elements necessary to make a Web service invocation using SAAJ.

The sequence of consuming a Web service from our SAAJ client will be:

  1. Create a connection
  2. Create the message
  3. Add message content
  4. Send the message to the destination
  5. Process the response
Creating a Connection and Message
We create the connection by calling the SOAPConnectionFactory's newInstance() method. We then create a new SOAPMessage instance using SAAJ's MessageFactory class. The SOAPMessage instance created by the SAAJ factory comes initialized with a pre-defined SOAPPart that contains a SOAPEnvelope (see Listing 5).

In addition, the SOAPEnvelope comes prebuilt with an empty SOAPHeader and SOAPBody. The SOAPHeader is optional. Our example doesn't pass a SOAPHeader, so we will remove it from the envelope when building the request.

Adding Content
We add content to the SOAPBody using SOAPElement instances added as child elements. We add to the SOAPBody a SOAPElement that contains the name of the service we are calling. Parts defined for the input message are then added as child SOAPElements (see Listing 6).

Invoking the Web Service
The request content is obtained from the sample input message that was built during WSDL analysis. This initial value could be displayed and edited by the user using a GUI-based tool and then used to populate the request message.

Once we've added the request content to the SOAPBody we can invoke the service. The final steps are to create the SOAPAction header and a URLEndpoint that points to the service's target URL. We give the Connection instance our populated SOAPMessage and the URLEndpoint and invoke the Connection's call() method. The call() method returns the XML response as a SOAPMessage (see Listing 7).

Making Sense Out of the Response
Web service responses can be very verbose and may contain extraneous data that makes it difficult to find what you are looking for. Even though XML documents are very structured, they are best suited for machines. Most people find it far easier to view an HTML report. A portion of the response from the XigniteQuotes service is shown in Listing 8.

It is important to note that users do not have to write this script themselves. Using a generic XSLT script, the tool automatically generates the script for this particular Web service from the WSDL file. This generic script knows how to traverse a WSDL file and extract the structure of the response message. It is aware of the XML Schema elements such as ComplexType, Sequence, All, Group, minOccurs, maxOccurs, etc. Using this information it infers when to display items as text fields and when to create tables.

The reporting process can be summarized as follows:
1.  WSDL file for Web service + Generic XSLT Script = XSLT script for the Web service
2.  Web service response in XML + XSLT script for the Web service = HTML report

The generic script processes the entire response and presents it in an HTML format, which may turn out to be very verbose. Because of this the tool displays the schema of the response, so users can visually select the elements of interest. This will generate a smaller XSLT script that will extract only the pertinent information from the response and present it as a report.

The generic script can be extended using richer XSLT semantics and formatting objects. This will enable us to transform the response into any format, including HTML, PDF, Word, or another XML document (see Figure 1).

Conclusion
Although our sample client is written using the Java language and Java-based APIs, a similar application can be written using tools and APIs available in Microsoft's .NET environment. The fact that WSDL doesn't force a particular programming model is actually one of the beauties of Web services.

WSClient is a useful mechanism for trying Web services. As we have seen you can use it to invoke any Web service and generate easy-to-read reports based on the response.

Our tool also can be used as a first step towards integrating Web services. Users can create a named scenario by specifying a WSDL file, the input XML (which is configurable), and an optional XSLT script. Through an API, the tool can be programmatically instructed to run the scenario identified by its name and return the result. This localizes the complexity of dealing with Web services in the tool and encapsulates the complexity from the invoking program. If used properly, this tool can help you make the transition from tentative evaluation of Web services to their complete adoption.

Author Bio
Bikash Behera and Jim Winfield are senior software architects at Metaserver, Inc., a provider of business process integration software.

Building Your Own SOAP Client and Reporting Tool by Bikash Behera & Jim Winfield
WSJ Vol 03 Issue 10 - pg.52



Listing 1.

   public List buildComponents(String wsdlURI)
   {
      // The list of components that will be returned
      List serviceList = Collections.synchronizedList(new ArrayList());

      // Create the WSDL Reader object
      WSDLReader reader = wsdlFactory.newWSDLReader();

      try
      {
         // Read the WSDL and get the top-level Definition object
         Definition def = reader.readWSDL(null, wsdlURI);

         // Create a castor schema from the types element defined in WSDL
         // This method will return null if there are types defined in the WSDL
         wsdlTypes = createSchemaFromTypes(def);

         // Get the services defined in the document
         Map services = def.getServices();

         if(services != null)
         {
            // Create a component for each service defined
            Iterator svcIter = services.values().iterator();

            for(int i = 0; svcIter.hasNext(); i++)
            {
               // Create a new ServiceInfo component for each service found
               ServiceInfo serviceInfo = new ServiceInfo();

               // Populate the new component from the WSDL Definition read
               populateComponent(serviceInfo, (Service)svcIter.next());

               // Add the new component to the List to be returned
               serviceList.add(serviceInfo);
            }
         }
      }

      catch(Throwable t)
      {
         // Process the error/exception
         System.err.println(t.getMessage());
      }

      // return the List of services we created
      return serviceList;
   }
   /**
    * Populates a ServiceInfo instance from the specified Service definiition
    *
    * @param   component      The component to populate
    * @param   service        The Service to populate from
    *
* @return The populated component is returned representing the Service parameter
    */
private ServiceInfo populateComponent(ServiceInfo component, Service service)
   {
      // Get the qualified service name information
      QName qName = service.getQName();

      // Get the service's namespace URI
      String namespace = qName.getNamespaceURI();

      // Use the local part of the qualified name for the component's name
      String name = qName.getLocalPart();

      // Set the name
      component.setName(name);

      // Get the defined ports for this service
      Map ports = service.getPorts();

// Use the Ports to create methods for all request/response messages defined
      Iterator portIter = ports.values().iterator();

      while(portIter.hasNext())
      {
         // Get the next defined port
         Port port = (Port)portIter.next();

         // Get the Port's Binding
         Binding binding = port.getBinding();

         // Now we will create operations from the Binding information
         List operations = buildOperations(binding);

         // Process methods built from the binding information
         Iterator operIter = operations.iterator();

         while(operIter.hasNext())
         {
            OperationInfo operation = (OperationInfo)operIter.next();

// Set the namespace URI for the method using the service's namespace.
            operation.setNamespaceURI(namespace);

            // Find the SOAP target URL
			ExtensibilityElement addrElem = 
findExtensibilityElement(port.getExtensibilityElements(), "address");

            if(addrElem != null && addrElem instanceof SOAPAddress)
            {
               // Set the SOAP target URL
               SOAPAddress soapAddr = (SOAPAddress)addrElem;
               operation.setTargetURL(soapAddr.getLocationURI());
            }

            // Add the method to the component
            component.addOperation(operation);
         }
      }

      return component;
   }

Listing 2.

   // Get the operation bound to the binding operation
   Operation operation = bindingOperation.getOperation();
   
   // From the operation we can get the Input definition
   Input inDef = operation.getInput();
   
   // Get the message defintion for the Input
   Message inMessage = inDef.getMessage();
   
   // Get the parts defined for this message
   Map parts = inMessage.getParts();

Listing 3.

<types xmlns:s="http://www.w3.org/2001/XMLSchema"
 xmlns:s0="http://www.xignite.com/services/">  
    <s:schema elementFormDefault="qualified"
	 targetNamespace="http://www.xignite.com/services/">

      <s:element name="GetQuotesHistorical">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="Symbol" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name="Month" type="s:int" />
            <s:element minOccurs="1" maxOccurs="1" name="Year" type="s:int" />
          </s:sequence>
        </s:complexType>
      </s:element>

      <s:element name="GetQuotesHistoricalResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="GetQuotesHistoricalResult"
			 type="s0:ArrayOfQuote" />
          </s:sequence>
        </s:complexType>
      </s:element>

      <s:complexType name="ArrayOfQuote">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="unbounded" name="Quote" nillable="true"
		   type="s0:Quote" />
        </s:sequence>
      </s:complexType>

      <s:complexType name="Quote">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="1" name="Error" type="s:string" />
          <s:element minOccurs="0" maxOccurs="1" name="Symbol" type="s:string" />
          <s:element minOccurs="0" maxOccurs="1" name="Date" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="Open" type="s:double" />
          <s:element minOccurs="1" maxOccurs="1" name="High" type="s:double" />
          <s:element minOccurs="1" maxOccurs="1" name="Low" type="s:double" />
          <s:element minOccurs="1" maxOccurs="1" name="Last" type="s:double" />
 <s:element minOccurs="1" maxOccurs="1" name="LastAdjusted" type="s:double" />
 <s:element minOccurs="1" maxOccurs="1" name="PercentChange" type="s:double" />
          <s:element minOccurs="1" maxOccurs="1" name="Volume" type="s:double" />
          <s:element minOccurs="1" maxOccurs="1" name="Change" type="s:double" />
        </s:sequence>
      </s:complexType>

    </s:schema>
  </types>

Listing 4

   /**
    * Builds and adds parameters to the supplied method given a SOAP Message definition
	 (from WSDL)
    *
    * @param   method      The method that the parameters built will be added to
    * @param   msg         The SOAP Message definition that has parts to defined
	 parameters for
    */
   private String buildMessageText(OperationInfo operationInfo, Message msg)
   {
      // Build a JDOM representation of the message
      //Element rootElem = new Element(operationInfo.getTargetMethodName(),
	   operationInfo.getTargetObjectURI());
      StringBuffer sb = new StringBuffer();

      // Get the message parts
      List msgParts = msg.getOrderedParts(null);

      // Process each part
      Iterator iter = msgParts.iterator();

      while(iter.hasNext())
      {
         // Get each part
         Part part = (Part)iter.next();

         // Add content for each message part
         Element partElem = new Element(part.getElementName().getLocalPart());

         // Determine if this message part's type is complex
         XMLType xmlType = getXMLType(part);

         if(xmlType != null && xmlType.isComplexType())
         {
            // Build the message structure
            buildComplexPart((ComplexType)xmlType, partElem);
         }
         else
         {
            partElem.addContent("0");
         }

         sb.append(XMLSupport.outputString(partElem));
      }

      return sb.toString();
   }

Listing 5.

// All SAAJ connections are created by using a connection factory
SOAPConnectionFactory conFactory = SOAPConnectionFactory.newInstance();
SOAPConnection connection = conFactory.createConnection();

// All SAAJ SOAP messages are created by using a message factory
MessageFactory msgFactory = MessageFactory.newInstance();
SOAPMessage msg = msgFactory.createMessage();

Listing 6.

         // Get the SOAP part from the SOAP message object
         SOAPPart soapPart = msg.getSOAPPart();

         // The SOAP part object will automatically contain the SOAP envelope
         SOAPEnvelope envelope = soapPart.getEnvelope();

         if(operation.getStyle().equalsIgnoreCase("rpc"))
         {
            // Add namespace declarations to the envelope
            // usually only required for RPC/encoded
            envelope.addNamespaceDeclaration
			(XSI_NAMESPACE_PREFIX, XSI_NAMESPACE_URI);
			envelope.addNamespaceDeclaration
			(XSD_NAMESPACE_PREFIX, XSD_NAMESPACE_URI);
         }

         // The client does not yet support SOAP headers
         SOAPHeader header = envelope.getHeader();
         header.detachNode();

         // Get the SOAP body from the envelope and populate it
         SOAPBody body = envelope.getBody();

         // Create the default namespace for the SOAP body
         body.addNamespaceDeclaration("", operation.getNamespaceURI());

         // Add the service information
         String targetObjectURI = operation.getTargetObjectURI();

         if(targetObjectURI == null)
         {
            // The target object URI should not be null
            targetObjectURI = "";
         }

         // Add the service information
         Name svcInfo = envelope.createName(operation.getTargetMethodName(), "",
		 targetObjectURI);

         // Add the service information
         SOAPElement svcElem = body.addChildElement(svcInfo);

         if(operation.getStyle().equalsIgnoreCase("rpc"))
         {
            // Set the encoding style of the service element
            svcElem.setEncodingStyle(operation.getEncodingStyle());
         }

         // Add the message to the SOAP body
         svcElem.addTextNode(operation.getInputMessageText());
         // Check for a SOAPAction
         String soapActionURI = operation.getSoapActionURI();

         // Add the SOAPAction value as a MIME header
         MimeHeaders mimeHeaders = msg.getMimeHeaders();
         mimeHeaders.setHeader("SOAPAction", "\"" +
		 operation.getSoapActionURI() + "\"");

         // Save changes to the message we just populated
         msg.saveChanges();

Listing 7

         // Get ready for the invocation
         URLEndpoint endpoint = new URLEndpoint(operation.getTargetURL());

         // Make the call
         SOAPMessage response = connection.call(msg, endpoint);

         // Close the connection, we are done with it
         connection.close();

         // Get the content of the SOAP response
         Source responseContent = response.getSOAPPart().getContent();

         // Convert the SOAP response into a JDOM
         TransformerFactory tFact = TransformerFactory.newInstance();
         Transformer transformer = tFact.newTransformer();

         JDOMResult jdomResult = new JDOMResult();
         transformer.transform(responseContent, jdomResult);

         // Get the document created by the transform operation
         Document responseDoc = jdomResult.getDocument();

         // Send the response to the console
         String strResponse = XMLSupport.outputString(responseDoc);
         System.out.println("SOAP Response from: " + operation.getTargetMethodName() +
		 ": " + strResponse);

         // Set the response as the output message
         operation.setOutputMessageText(strResponse);

Listing 8.

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetQuotesHistoricalResponse xmlns="http://www.xignite.com/services/">
            <GetQuotesHistoricalResult>
                <Quote>
                    <Date>12/31/2002</Date>
                    <Open>3.07</Open>
                    <High>3.15</High>
                    <Low>3.04</Low>
                    <Last>3.11</Last>
                    <LastAdjusted>3.11</LastAdjusted>
                    <PercentChange>0.974</PercentChange>
                    <Volume>39579300</Volume>
                    <Change>0.03</Change>
                </Quote>
                <Quote>
                    <Date>12/30/2002</Date>
                    <Open>3.28</Open>
                    <High>3.31</High>
                    <Low>3.05</Low>
                    <Last>3.08</Last>
                    <LastAdjusted>3.08</LastAdjusted>
                    <PercentChange>-5.231</PercentChange>
                    <Volume>39969900</Volume>
                    <Change>-0.17</Change>
                </Quote>
                …
            </GetQuotesHistoricalResult>
        </GetQuotesHistoricalResponse>
    </soap:Body>
</soap:Envelope

With the help of an automatically generated XSLT script, we can transform the XML
 response into an HTML report.  

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:output method = "html"/>   
   …………
   <xsl:template match = "GetQuotesHistoricalResult">

      <html>      
         <head>
         </head>
         <body>
         
            <table width="550" border="2" align="center">            
               <tr valign="bottom">
                  <td width="150" align="center">Date</td>
                  <td width="150" align="center">Open</td>
                  <td width="150" align="center">High</td>
                  <td width="150" align="center">Low</td>
                  <td width="150" align="center">Last</td>
               </tr>
               
               <xsl:apply-templates/>                     
                 
            </table>                          
         </body>                  
      </html>

   </xsl:template>      
   
   <xsl:template  match="Quote">
      
      <tr>              
         <td align="left" width="150"><xsl:value-of select="Date"/></td>
         <td align="left" width="150"><xsl:value-of select="Open"/></td>
         <td align="left" width="150"><xsl:value-of select="High"/></td>
         <td align="left" width="150"><xsl:value-of select="Low"/></td>
         <td align="left" width="150"><xsl:value-of select="Last"/></td>                     
      </tr>                              
      
   </xsl:template>   
               
</xsl:stylesheet>

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.