Главная » XSLT » Генерация документации о SOAP из WSDL-документа

0

Задача

Вы создаете архитектуру предприятия на основе Web-сервисов с использова­нием SOAP и WSDL. Требуется, чтобы разработчики могли найти полную и по­лезную информацию об имеющихся сервисах.

Решение

В этом решении мы создадим на основе WSDL-описания сервер докумен­тации по Web-сервисам, то есть сервис, который предоставляет информацию о сервисах[3]. Мы напишем CGI-сценарий на языке Perl, который будет с помощью

XSLT обрабатывать один WSDL-файл. Этот файл содержит сам или включает информацию обо всех сервисах, работающих на предприятии. В данном случае CGI-сценарий вызывает процессор XSLT с помощью функции system. Такой способ еще годится для создания прототипа, но никак не для промышленного развертывания. Было бы лучше воспользоваться Perl-модулями XML::LibXML и XML::LibXSLT. А еще лучше прибегнуть к развитой серверной подсистеме на базе XSLT, например, Cocoon. Мы приняли упрощенный подход для того, чтобы сосредоточиться на тех аспектах, которые относятся к XSLT и WSDL, а не к ар­хитектуре CGI.

Главная страница сайта генерируется CGI-сценарием, которые показывает имеющиеся сервисы и порты. О том, что это такое, см. ниже. Мы следующим обра­зом вызываем процессор Saxon из Perl:

#!c:/perl/bin/perl

print "Content-type: text/html\n\n" ;

system "saxon StockServices.wsdl wsdlServiceList.xslt" ;

Следующее преобразование строит форму, в которой описаны сервисы, пор­ты, привязки и типы портов:

<?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>Cервисы в компании ACME Web Services, Inc.</title> <xsl:comment>Докyментация из WSDL: сгенерирована

wsdlServiceList.xslt </xsl:comment> </head> <body>

<xsl:apply-templates/> </body> </html>

<xsl:template match="wsdl:definitions">

<h1>Сервисы в компании ACME Web Services, Inc.</h1> <br/>

<form name="QueryServiceForm" method="post" action="QueryService.pl"> <table> <tbody> <tr>

<td>Сервисы</td> <td>

<select name="service">

<option value="ALL">BСЕ</option> <xsl:apply-templates select="wsdl:service"/> </select> </td> </tr> <tr>

<td>Порты</td> <td>

<select name="port">

<option value="ALL">BСЕ</option>

<xsl:apply-templates select="wsdl:service/wsdl:port"/> </select> </td> </tr> <tr>

<td>Привязки</td> <td>

<select name="binding">

<option value="ALL">BСЕ</option> <xsl:apply-templates select="wsdl:binding"/> </select>

</td> </tr> <tr>

<td>Tипы портов</td> <td>

<select name="portType">

<option value="ALL">BСЕ</option> <xsl:apply-templates select="wsdl:portType"/> </select> </td> </tr> </tbody> </table>

<br/>

<button type="submit" name="submit">Послать запpос</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>

Пользователь может выбрать любую комбинацию сервиса, порта, привязки и типа порта, о которой хочет получить подробную информацию. Для обработки запроса вызывается небольшой Perl-сценарий, который разбирает входные дан­ные и вызывает еще одно XSLT-преобразование:

#!/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"

Это преобразование находит сервисы, соответствующие заданному пользова­телем критерию, и отображает подробную информацию о каждом из них. В пер­вой части таблицы стилей некоторые параметры приводятся к каноническому виду. Делать это необходимо, потому что атрибуты @element в WSDL-файле мо­гут содержать префикс пространства имен, а атрибут @name его не содержит. То же самое относится и к конструкции key:

<?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/200 0/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/">

<!– Пapaметpы зaпpoсa –>

<xsl:param name="service" select="’ALL’"/> <xsl:param name="port" select="’ALL’"/> <xsl:param name="binding" select="’ALL’"/> <xsl:param name="portType" select="’ALL’"/>

<!– Способ (или тpюк), позволяющий пpисвoить –> <!– пеpеменнoй пустое значение, если соответствующий –> <!– пapaметp paвен ‘ВСЕ’, а в пpoтивнoм случае записать –> <!— в нее pезyльтaт конкатенации &TNSPREFIX и пapaметpa. –> <!— Haпpимеp, number($service = ‘ВСЕ’) * 99999 + 1 –> <!— paвнo 1, если $service не paвнo ‘ВСЕ’, и paвнo 100000, —> <!— если paвнo. Число 100000 заведомо больше длины CTpora, —> <!— поэтому substring веpнет пустую CTpo^. Все это нужно —> <!– только для того, чтобы yпpoстить фopмиpoвaние –> <!– пеpекpестных ссылок –> <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)"/>

<!– Эти ключи yпpoщaют и yскopяют поиск –> <xsl:keyname="bindings_key"

match="wsdl:binding" use="concat(&TNSPREFIX;,@name)"/> <xsl:keyname="portType_key"

match="wsdl:portType" use="concat(&TNSPREFIX;,@name)"/>

<xsl:output method="html"/>

<html> <head>

<title>ACME Web Services, Inc. Результат запpocа</title> <xsl:comment>Дoкyментация из WSDL: сгенерирована wsdlServiceList.xslt </xsl:comment> </head> <body>

<xsl:apply-templates select="wsdl:definitions"/> </body> </html> </xsl:template>

Далее, чтобы легче было понять, соответствует ли запросу хотя бы один сер­вис, мы запоминаем результат в переменной и проверяем ее нормализованное значение. Если оно пусто, значит подходящих сервисов не нашлось, и мы выводим соответствующее сообщение. Запрос довольно хитрый, поскольку для того чтобы узнать тип порта сервиса, нужно сначала перейти от сервиса к привязке, а потом получить тип порта для этой привязки:

<xsl:template match="wsdl:definitions"> <xsl:variable name="result">

<!– Найти сервисы, соответствующие параметрам запроса. –>

<!— Для этого необходимо перейти от порта сервиса к —>

<!— привязке, а затем к типу привязки. Отсюда и —>

<!– вложенные вызовы key(). –>

<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>

^хЬ^одходящих сервисов не найденo</b></p> </xsl:otherwise> </xsl:choose> </xsl:template>

Оставшаяся часть таблицы стилей – это преимущественно преобразование WSDL в HTML-таблицу. У сервиса может быть несколько портов, поэтому выб­рать порты, соответствующие параметрам запроса, придется еще раз:

<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>

Используем ключи для перехода от порта к привязке (операция соединения) и от привязки к типу порта:

<xsl:template match="wsdl:port" mode="display"> <tr>

<td style="font-weight:bold" colspan="2" align="center">Порт</td> </tr> <tr>

<td colspan="2"><h3><xsl:value-of select="@name"/></h3></td> </tr> <tr>

<td style="font-weight:bold" width="5 0">Привязка</td> <td><xsl:value-of select="substring-after(@binding,':’)"/></td> </tr> <tr>

<td style="font-weight:bold" width="5 0">Aдрес</td> <td><xsl:value-of select="soap:address/@location"/></td> </tr> <tr>

<th colspan="2" align="center">Операции</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="5 0">Bxод</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="5 0">Bыxод</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>

Следующий код выводит XSD-схему сообщений в виде XML внутри HTML. Сама схема – это, пожалуй, самая полезная информация для документирова­ния типов данных, ассоциированных с входными и выходными сообщениями сервиса:

<xsl:template match="*" mode="display">

<xsl:param name="initial-newline" select="true( )"/>

<xsl:if test="$initial-newline">

<xsl:call-template name="newline"/> </xsl:if>

<!– открывающий тег –> <a>&lt;</a>

<a><xsl:value-of select="name(.)" /> </a>

<!– Выводим атрибуты — > <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( )"> <!– закрыть тег — > <a>&gt;</a>

<xsl:apply-templates mode="display"/> <xsl:call-template name="newline"/> <!– закрывающий тег –> <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>

<!– Начать новую строку и сформировать отступ, вычисляемый по уровню –> <!– вложенности внутри схемы –>

<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>

Обсуждение

В главе 10 шла речь о репозитории сообщений, где можно сохранить метадан­ные о сообщениях, которыми обмениваются клиент и сервер. Язык WSDL дает пример синтаксиса для описания такого репозитория в применении к архитекту­ре на основе Web-сервисов. Один и тот же WSDL-файл можно использовать для генерации кода и документации. В данном рецепте мы занимались только выда­чей документации по запросу.

На языке WSDL Web-сервисы описываются в терминах ассоциаций между сер­висами и портами. В терминологии WSDL порт – это концевая точка, определен­ная как комбинация привязки и сетевого адреса. Привязкой называется конкрет­ный протокол и спецификация формата данных для конкретного типа порта. Тип порта определяет абстрактный набор операций, поддерживаемых одной или не­сколькими концевыми точками. Операция – это действие, выполняемое сервисом; определяется она в терминах сообщений с той или иной полезной нагрузкой. Полезная нагрузка описывается XSD-схемой. Ниже приведена часть простого WSDL-файла, в котором описывается набор сервисов, предлагаемых внутри ком­пании 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 по умолчанию применяются XSD-схемы для описания типов данных в сообщениях, которыми обмениваются клиент и сервер:

<types>

<schema targetNamespace="http://acme.com/services.xsd" xmlns="http://www.w3.org/200 0/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>

Элементы message связывают имена с типами данных и специфицируют, что именно передается в сообщении:

<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>

Тип порта определяет, какие операции доступны в конкретной концевой точ­ке сервиса (порту). В этом рецепте описываются порты двух типов: один предназ­начен для выдачи информации о курсах акций, а другой описывает службу обна­ружения сервисов. Последнюю мы как раз и реализовали.

<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>

Привязки связывают операции с протоколами. В данном случае мы объявля­ем, что все операции привязаны к протоколу SOAP:

<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>

Ниже объявлено два сервиса. Один относится к курсам акций, другой – к об­наружению сервисов. Обратите внимание, как организован набор портов, описы­вающих функциональность, доступную с помощью данного сервиса:

<service name="StockInfoService">

<documentation>Пpeдоставляeт информацию о курсах акций.</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>Пpeдоставляeт информацию об имеющихся сервисах. </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>

Наш CGI-сценарий выводит форму с запросом о сервисах. Вы можете задать параметры запроса, как показано на рис. 13.6.

В ответ на этот запрос будет выдан результат, показанный на рис. 13.7.

Рис. 13.6. Запрос к WSDL

 

Рис. 13.7. Ответ на запрос к WSDL

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии