Recipe13.5.Serving SOAP Documentation from WSDL


Recipe 13.5. Serving SOAP Documentation from WSDL

Problem

You are using SOAP and WSDL to build a service-based enterprise architecture. You want developers to be able to find complete and pertinent information on available services.

Solution

This solution constructs a WSDL-based service documentation server: a service that provides information about services.[4] This example will use a Perl-based CGI that invokes XSLT processing on a single WSDL file containing or including information about all services available in an enterprise. Here the CGI invokes the XSLT processor in a system call. This clumsy setup is good for prototyping but not production use. A better solution would use the Perl modules XML::LibXML and XML::LibXSLT. An even better architecture would use a more sophisticated server-side XSLT-enabled solution such as Cocoon. To focus on the XSLT and WSDL aspects of this example, and not the CGI architecture, we took a simplistic approach.

[4] A metaservice, if you will.

The site's main page is generated by a CGI that shows the user available services and ports. See the discussion for explanations of services and ports. It uses the following Perl CGI front end to Saxon:

#!c:/perl/bin/perl  print "Content-type: text/html\n\n" ; system "saxon StockServices.wsdl wsdlServiceList.xslt" ;

The transformation builds a form containing all available services, ports, bindings, and port types:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet       version="1.0"      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"     xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"     xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"     xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"     xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"     exclude-result-prefixes="wsdl soap http mime">       <xsl:output method="html"/>      <xsl:template match="/">       <html>       <head>         <title>Available Services at ACME Web Services, Inc.</title>         <xsl:comment>WSDL Documentation: Generated by wsdlServiceList.xslt         </xsl:comment>       </head>       <body>         <xsl:apply-templates/>        </body>     </html>       </xsl:template>       <xsl:template match="wsdl:definitions">     <h1>Available Services at ACME Web Services, Inc.</h1>     <br/>     <form name="QueryServiceForm" method="post" action="QueryService.pl">     <table>       <tbody>         <tr>           <td>Services</td>           <td>             <select name="service">               <option value="ALL">ALL</option>               <xsl:apply-templates select="wsdl:service"/>             </select>              </td>         </tr>          <tr>           <td>Ports</td>           <td>             <select name="port">               <option value="ALL">ALL</option>               <xsl:apply-templates select="wsdl:service/wsdl:port"/>             </select>              </td>         </tr>          <tr>           <td>Bindings</td>           <td>             <select name="binding">               <option value="ALL">ALL</option>               <xsl:apply-templates select="wsdl:binding"/>             </select>              </td>         </tr>          <tr>           <td>Port Types</td>           <td>             <select name="portType">               <option value="ALL">ALL</option>               <xsl:apply-templates select="wsdl:portType"/>             </select>              </td>         </tr>       </tbody>     </table>     <br/>     <button type="submit" name="submit">Query Services</button>      </form>   </xsl:template>     <xsl:template match="wsdl:service | wsdl:port | wsdl:binding | wsdl:portType">   <option value="{@name}"><xsl:value-of select="@name"/></option> </xsl:template>           </xsl:stylesheet>

Users can select a combination of service, port, bind, and port type on which they want more detailed information. When submitted, another small Perl CGI extracts the input and invokes another XSLT transform:

#!/perl/bin/perl     use warnings;     use strict; use CGI qw(:standard);     my $query = new CGI ;     my $service = $query->param('service'); my $port = $query->param('port'); my $binding = $query->param('binding'); my $portType = $query->param('portType');     print $query->header('text/html');     system "saxon StockServices.wsdl QueryService.xslt service=$service port=$port binding=$binding portType=$portType"

This transformation extracts the services that match the user's criteria and displays detailed information about the service. The first part of the stylesheet normalizes some parameters. It does so because an attribute reference in a WSDL file could contain a namespace prefix, but the @name attribute of the element it references does not. The same reasoning applies to key construction:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xslt [   <!ENTITY TNSPREFIX "'acme:'"> ]> <xsl:stylesheet       version="1.0"      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"      xmlns:xsd="http://www.w3.org/2000/10/XMLSchema"      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"      xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"      xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/">     <!--Query parameters --> <xsl:param name="service" select="'ALL'"/> <xsl:param name="port" select="'ALL'"/> <xsl:param name="binding" select="'ALL'"/> <xsl:param name="portType" select="'ALL'"/>     <!-- A technique (or a hack) to make variables empty   --> <!-- if the corrsponding parameter is ALL or otherwise --> <!-- the concatenation of &TNSPREFIX and the parameter  --> <!-- For example, number($service = 'ALL') * 99999 + 1 --> <!-- will be 1 if $service is not equal to 'ALL' but   --> <!-- 100000 if it is and hence beyond the length of the-->  <!-- string, which causes substring to return empty.   --> <!-- This is all to simplify cross-referencing.        --> <xsl:variable name="serviceRef"                select="substring(concat(&TNSPREFIX;,$service),                                 number($service = 'ALL') * 99999 + 1)"/> <xsl:variable name="portRef"               select="substring(concat(&TNSPREFIX;,$port),                                 number($port = 'ALL') * 99999 + 1)"/> <xsl:variable name="bindingRef"               select="substring(concat(&TNSPREFIX;,$binding),                                 number($binding = 'ALL') * 99999 + 1)"/> <xsl:variable name="portTypeRef"               select="substring(concat(&TNSPREFIX;,$portType),                                 number($portType = 'ALL') * 99999 + 1)"/>     <!-- These keys simplify and speed up querying -->               <xsl:key name="bindings_key"           match="wsdl:binding" use="concat(&TNSPREFIX;,@name)"/> <xsl:key name="portType_key"          match="wsdl:portType" use="concat(&TNSPREFIX;,@name)"/>     <xsl:output method="html"/>       <xsl:template match="/">       <html>       <head>         <title>ACME Web Services, Inc. Query Result</title>         <xsl:comment>WSDL Documentation: Generated by wsdlServiceList.xslt         </xsl:comment>       </head>       <body>         <xsl:apply-templates select="wsdl:definitions"/>        </body>     </html> </xsl:template>

Here you simplify the problem of determining whether the query found matching services by capturing the result in a variable and testing its normalized value. If it is empty, then you know the query failed, and it displays an appropriate message. The query is a little tricky because the service's portType can be determined only by making two hops from the service to its binding and then to the binding's port type:

<xsl:template match="wsdl:definitions">    <xsl:variable name="result">         <!-- Query services that match the query parameters             -->         <!-- The portType match is the only complicated part            -->         <!-- We need to traverse from the service's port to the binding -->         <!-- and then to the binding's type to do the match. Hence      -->         <!-- the nested key( ) calls                                     -->         <xsl:apply-templates               select="wsdl:service[                     (not($serviceRef)  or @name = $service) and                     (not($portRef)     or wsdl:port/@name = $port) and                     (not($bindingRef)  or wsdl:port/@binding = $bindingRef)                      and                     (not($portTypeRef) or key('portType_key',                                                key('bindings_key',                                                  wsdl:port/@binding)/@type)                                                     /@name = $portType)]"/>   </xsl:variable>   <xsl:choose>     <xsl:when test="normalize-space($result)">       <xsl:copy-of select="$result"/>     </xsl:when>     <xsl:otherwise>       <p><b>No Matching Services Found</b></p>     </xsl:otherwise>   </xsl:choose> </xsl:template>

The rest of the stylesheet is mostly logic that renders the WSDL as HTML table entries. A service may contain more than one port, so be sure to select ports based on the query parameters once more:

<xsl:template match="wsdl:service" mode="display"> <h1><xsl:value-of select="@name"/></h1> <p><xsl:value-of select="wsdl:documentation"/></p> <table border="1" cellpadding="5" cellspacing="0"  width="600">   <tbody>     <xsl:apply-templates           select="wsdl:port[(not($portRef) or @name = $port) and                            (not($bindingRef) or @binding = $bindingRef)]"           mode="display"/>   </tbody> </table> </xsl:template>

Use these keys to traverse (do a join) between port and binding, as well as between binding and port type:

<xsl:template match="wsdl:port" mode="display">     <tr>       <td style="font-weight:bold" colspan="2" align="center">Port</td>     </tr>     <tr>       <td colspan="2"><h3><xsl:value-of select="@name"/></h3></td>     </tr>     <tr>       <td style="font-weight:bold" width="50">Binding</td>       <td><xsl:value-of select="substring-after(@binding,':')"/></td>     </tr>     <tr>       <td style="font-weight:bold" width="50">Address</td>       <td><xsl:value-of select="soap:address/@location"/></td>     </tr>     <tr>       <th colspan="2" align="center">Operations</th>     </tr>     <xsl:apply-templates select="key('bindings_key',@binding)" mode="display"/> </xsl:template>     <xsl:template match="wsdl:binding" mode="display">   <xsl:apply-templates select="key('portType_key',@type)" mode="display">     <xsl:with-param name="operation" select="wsdl:operation/@name"/>   </xsl:apply-templates> </xsl:template>     <xsl:template match="wsdl:portType" mode="display">   <xsl:param name="operation"/>   <xsl:for-each select="wsdl:operation[@name = $operation]">     <tr>       <td colspan="2"><h3><xsl:value-of select="@name"/></h3></td>     </tr>     <xsl:if test="wsdl:input">       <tr>         <td style="font-weight:bold" width="50">Input</td>         <td><xsl:value-of select="substring-after(wsdl:input/@message,':')"/></td>       </tr>       <xsl:variable name="msgName"            select="substring-after(wsdl:input/@message,':')"/>       <xsl:apply-templates            select="/*/wsdl:message[@name = $msgName]" mode="display"/>     </xsl:if>     <xsl:if test="wsdl:output">       <tr>         <td style="font-weight:bold" width="50">Output</td>         <td><xsl:value-of select="substring-after(wsdl:output/@message,':')"/></td>       </tr>       <xsl:variable name="msgName"            select="substring-after(wsdl:output/@message,':')"/>       <xsl:apply-templates            select="/*/wsdl:message[@name = $msgName]" mode="display"/>    </xsl:if>   </xsl:for-each> </xsl:template>     <xsl:template match="wsdl:message" mode="display">   <xsl:variable name="dataType"        select="substring-after(wsdl:part[@name='body']/@element,':')"/>   <tr>     <td colspan="2">       <xsl:apply-templates             select="/*/wsdl:types/*/*[@name=$dataType]" mode="display">         <xsl:with-param name="initial-newline" select="false( )"/>       </xsl:apply-templates>     </td>   </tr> </xsl:template>

This code renders the messages' XSD schema as XML within HTML. The actual schema is probably the most informative piece of information you can display in order to document the data types associated with the service input and output messages:

<xsl:template match="*" mode="display">   <xsl:param name="initial-newline" select="true( )"/>      <xsl:if test="$initial-newline">     <xsl:call-template name="newline"/>   </xsl:if>   <!-- open tag -->   <a>&lt;</a>   <a><xsl:value-of select="name(.)" /> </a>      <!-- Output attributes -->   <xsl:for-each select="@*">     <a><xsl:text> </xsl:text><xsl:value-of select="name(.)" />  </a>     <a>=&quot;</a>     <xsl:value-of select="." />     <a>&quot;</a>   </xsl:for-each>      <xsl:choose>        <xsl:when test="child::node( )">         <!-- close start tag -->         <a>&gt;</a>         <xsl:apply-templates  mode="display"/>         <xsl:call-template name="newline"/>         <!-- closing tag -->         <a>&lt;</a>         <a>/<xsl:value-of select="name(.)" /></a>        </xsl:when>        <xsl:otherwise>             <a>/</a>        </xsl:otherwise>   </xsl:choose>   <a>&gt;</a> </xsl:template>     <!-- Add a newline and then indent based on depth within the schema --> <xsl:template name="newline">      <br/>      <xsl:for-each select="ancestor::xsd:*[not(self::xsd:schema)]">           <xsl:text>&#160;&#160;&#160;&#160;</xsl:text>      </xsl:for-each> </xsl:template>     </xsl:stylesheet>

Discussion

Chapter 10 spoke of a message repository that can capture metadata about messages exchanged in a client-server architecture. WSDL is an example of a kind of message-repository syntax for a web-service-based architecture. Here you can also use the same WSDL to generate code and documentation. This example focuses on serving up documentation.

WSDL describes web services in terms of associations between services and ports. In WSDL terms, a port is a single end point defined as a combination of a binding and a network address. A binding is a concrete protocol and data-format specification for a particular port type. A port type defines an abstract set of operations supported by one or more end points. An operation is an action that a service can perform, and it is defined in terms of a message with a corresponding data payload. The data payload is usually described in terms of an XSD schema. The following code is a partial example of a simple WSDL describing a set of services offered within the Acme Corporation:

<definitions name="StockServices" targetNamespace="http://acme.com/services.wsdl"  xmlns:acme="http://acme.com/services.wsdl" xmlns:xsd1="http://acme.com/stockquote. xsd" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas. xmlsoap.org/wsdl/">

WSDL uses XSD schema as the default method of specifying the message data types exchanged in a service architecture:

  <types>     <schema targetNamespace="http://acme.com/services.xsd"        xmlns="http://www.w3.org/2000/10/XMLSchema">       <element name="Ticker">         <complexType>           <all>             <element name="tickerSymbol" type="string"/>           </all>         </complexType>       </element>       <element name="TradePrice">         <complexType>           <all>             <element name="price" type="float"/>           </all>         </complexType>       </element>       <element name="CompanyName">         <complexType>           <all>             <element name="company" type="string"/>           </all>         </complexType>       </element>       <element name="ServicesInfo">         <complexType>           <sequence>             <element name="Services">               <complexType>                 <sequence>                   <element name="Service" type="string" minOccurs="0"                             maxOccurs="unbounded"/>                 </sequence>               </complexType>             </element>             <element name="Ports">               <complexType>                 <sequence>                   <element name="Port" type="string" minOccurs="0"                             maxOccurs="unbounded"/>                 </sequence>               </complexType>             </element>             <element name="Bindings">               <complexType>                 <sequence>                   <element name="Binding" type="string" minOccurs="0"                             maxOccurs="unbounded"/>                 </sequence>               </complexType>             </element>             <element name="PortTypes">               <complexType>                 <sequence>                   <element name="Port" type="string" minOccurs="0"                             maxOccurs="unbounded"/>                 </sequence>               </complexType>             </element>           </sequence>         </complexType>       </element>       <element name="ServicesQuery">         <complexType>           <all>             <element name="Service" type="string"/>             <element name="Port" type="string"/>             <element name="Binding" type="string"/>             <element name="PortType" type="string"/>           </all>         </complexType>       </element>       <element name="ServicesResponse">         <complexType>           <sequence>             <any/>           </sequence>         </complexType>       </element>     </schema>   </types>

Messages bind names to data types and are used to specify what is communicated within the service architecture:

  <message name="GetLastTradePriceInput">     <part name="body" element="xsd1:Ticker"/>   </message>   <message name="GetLastTradePriceOutput">     <part name="body" element="xsd1:TradePrice"/>   </message>   <message name="GetCompanyInput">     <part name="body" element="xsd1:Ticker"/>   </message>   <message name="GetCompanyOutput">     <part name="body" element="xsd1:CompanyName"/>   </message>   <message name="GetTickerInput">     <part name="body" element="xsd1:CompanyName"/>   </message>   <message name="GetTickerOutput">     <part name="body" element="xsd1:Ticker"/>   </message>   <message name="GetServicesInput"/>   <message name="GetServicesOutput">     <part name="body" element="xsd1:ServicesInfo"/>   </message>   <message name="QueryServicesInput">     <part name="body" element="xsd1:ServicesQuery"/>   </message>   <message name="QueryServicesOutput">     <part name="body" element="xsd1:ServicesRespose"/>   </message>

The port types specify which operations are available at a particular service end point (port). This section describes two such port types: one that gives stock quote information and another that describes a service discovery service. The service discovery service is the service you implement in this example, except that it would be used by a program other than a browser:

  <portType name="StockPortType">     <operation name="GetLastTradePrice">       <input message="acme:GetLastTradePriceInput"/>       <output message="acme:GetLastTradePriceOutput"/>     </operation>     <operation name="GetTickerFromCompany">       <input message="acme:GetTickerInput"/>       <output message="acme:GetCompanyOutput"/>     </operation>     <operation name="GetCompanyFromTicker">       <input message="acme:GetCompanyInput"/>       <output message="acme:GetTickerOutput"/>     </operation>   </portType>   <portType name="ServicePortType">     <operation name="GetServices">       <input message="acme:GetServicesInput"/>       <output message="acme:GetServicesOutput"/>     </operation>     <operation name="QueryServices">       <input message="acme:QueryServicesInput"/>       <output message="acme:QueryServicesOutput"/>     </operation>   </portType>

The bindings tie operations to specific protocols. Here you declare that operations are bound to the SOAP protocol:

  <binding name="StockQuoteSoapBinding" type="acme:StockPortType">     <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>     <operation name="GetLastTradePrice">       <soap:operation soapAction="http://acme.com/GetLastTradePrice"/>       <input>         <soap:body use="literal"/>       </input>       <output>         <soap:body use="literal"/>       </output>     </operation>   </binding>   <binding name="StockTickerSoapBinding" type="acme:StockPortType">     <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>     <operation name="GetTickerFromCompany">       <soap:operation soapAction="http://acme.com/GetTickerSymbol"/>       <input>         <soap:body use="literal"/>       </input>       <output>         <soap:body use="literal"/>       </output>     </operation>   </binding>   <binding name="StockNameSoapBinding" type="acme:StockPortType">     <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>     <operation name="GetCompanyFromTicker">       <soap:operation soapAction="http://acme.com/GetCompanyName"/>       <input>         <soap:body use="literal"/>       </input>       <output>         <soap:body use="literal"/>       </output>     </operation>   </binding>   <binding name="ServiceListSoapBinding" type="acme:ServicePortType">     <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>     <operation name="GetServices">       <soap:operation soapAction="http://acme.com/GetServices"/>       <input>         <soap:body use="literal"/>       </input>       <output>         <soap:body use="literal"/>       </output>     </operation>   </binding>

Two services are advertised here. One is related to stock quotes, and the other to service discovery. Notice how the services organize a set of ports to specify the functionality available though the service:

  <service name="StockInfoService">     <documentation>Provides information about stocks.</documentation>     <port name="StockQuotePort" binding="acme:StockQuoteSoapBinding">       <soap:address location="http://acme.com/stockquote"/>     </port>     <port name="StockTickerToNamePort" binding="acme:StockTickerSoapBinding">       <soap:address location="http://acme.com/tickertoname"/>     </port>     <port name="StockNameToTickerPort" binding="acme:StockNameSoapBinding">       <soap:address location="http://acme.com/nametoticker"/>     </port>   </service>   <service name="ServiceInfoService">     <documentation>Provides information about avaialable services.</documentation>     <port name="ServiceListPort" binding="acme:ServiceListSoapBinding">       <soap:address location="http://acme.com/stockquote"/>     </port>     <port name="ServiceQueryPort" binding="acme:ServiceQuerySoapBinding">       <soap:address location="http://acme.com/tickertoname"/>     </port>   </service>     </definitions>

Using your CGI-based server, you get a service-query screen. You can select the parameters of the query, as shown in Figure 13-6.

Figure 13-6. WSDL query screen


When you submit the query, you obtain the result shown in Figure 13-7.

Figure 13-7. WSDL result screen


See Also

The specifications for WSDL are available at http://www.w3.org/TR/wsdl. WSDL is not yet an official W3C recommendation; however, it is quickly gaining recognition and support in the industry. An official Web Services Description Working Group (http://www.w3.org/2002/ws/desc/) is working on what will eventually be a W3C-sanctioned recommendation.

See Recipe 14.16 for ways of embedding XSLT processing into Perl rather than forking off a separate process.

Uche Ogbuji writes about generating documentation and RDF from WSDL in his article "WSDL processing with XSLT" at http://www-106.ibm.com/developerworks/library/ws-trans/?dwzone=ws.




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net