Archive for July, 2006

XSLT that transforms from XSD to WSDL

I don’t like writing WSDLs. I think they force a wrong model. Unfortunately, they are the standard. I do like contract-first Web service. I think the XML messages, and therefore the XSD, are most important. So, what I usually do when creating a WSDL

  1. start with the XML messages,
  2. create or infer a schema from that,
  3. copy all the defined elements in messages,
  4. create operations from the messages
  5. create a portType from the operations
  6. create a binding from the portType
  7. create a service from the binding
Step 2 requires some manual tuning, but steps 3-7 are pretty tedious, so I wondered if I couldn’t automate it in some way. Craig Walls also wondered about this.

I started out writing this in Java, but that got even more tedious. After some though, it seemed like perfect opportunity to use an XSLT stylesheet, since we have XML coming in (the schema), and XML coming out (the WSDL). Basically, the idea is to iterate over all elements that end with a certain suffix (’Request’ by default), and to create operations based on that. So if you have a GetHolidayRequest element in the schema, a GetHolidayRequestMessage is created, used by a GetHoliday operation.

Note that it is a XSLT 2.0 stylesheet, because I needed some of the new functionality (most importantly the new <xsl:namespace/> tag). You will need an XSLT 2.0 processor, like Saxon, to run it. As far as I know, Xalan doesn’t do XSLT 2.0 (yet). Also note that I’m no XSLT expert, so there are probably some improvements to be made. Please give them in the comments! The XSLT will eventually end up in Spring Web Services, so that you can dynamically create the WSDL based on your XSD.

Without further ado, here it is:

<!--
  ~ Copyright 2006 Arjen Poutsma.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <!-- Parameters -->
    <!-- Name for the WSDL definition, which is also uses as a prefix for portType, binding, and service -->
    <xsl:param name="name"/>
    <!-- Location where the service can be found -->
    <xsl:param name="location"/>
    <!-- Namespace of the schema -->
    <xsl:param name="schemaNamespace" select="/xsd:schema/@targetNamespace"/>
    <!-- Prefix for namespace of the schema -->
    <xsl:param name="schemaPrefix" select="'schema'"/>
    <!-- WSDL Namespace, by default the same as the schema namespace -->
    <xsl:param name="targetNamespace" select="$schemaNamespace"/>
    <!-- Prefix for WSDL -->
    <xsl:param name="prefix" select="'tns'"/>
    <xsl:param name="requestSuffix" select="'Request'"/>
    <xsl:param name="responseSuffix" select="'Response'"/>
    <xsl:param name="messageSuffix" select="'Message'"/>
    <xsl:param name="inputSuffix" select="'Input'"/>
    <xsl:param name="outputSuffix" select="'Output'"/>
    <xsl:param name="portTypeSuffix" select="'PortType'"/>
    <xsl:param name="bindingSuffix" select="'Binding'"/>
    <xsl:param name="serviceSuffix" select="'Service'"/>
    <xsl:param name="portSuffix" select="'Port'"/>
    <xsl:param name="soapActionPrefix" select="$targetNamespace"/>

    <xsl:variable name="elements" select="/xsd:schema/xsd:element"/>

    <!-- Elements that end with the request suffix, keyed under operation name -->
    <xsl:key name="requestElements" match="/xsd:schema/xsd:element[ends-with(@name, $requestSuffix)]"
             use="substring-before(@name,$requestSuffix)"/>
    <!-- Elements that end with the response suffix, keyed under operation name -->
    <xsl:key name="responseElements" match="/xsd:schema/xsd:element[ends-with(@name, $responseSuffix)]"
             use="substring-before(@name,$responseSuffix)"/>

    <!-- Global template -->
    <xsl:template match="/">
        <xsl:call-template name="definitions"/>
    </xsl:template>

    <!-- WSDL definitions -->
    <xsl:template name="definitions">
        <wsdl:definitions>
            <xsl:attribute name="name">
                <xsl:value-of select="$name"/>
            </xsl:attribute>
            <xsl:namespace name="{$schemaPrefix}" select="$schemaNamespace"/>
            <xsl:namespace name="{$prefix}" select="$targetNamespace"/>
            <xsl:attribute name="targetNamespace">
                <xsl:value-of select="$targetNamespace"/>
            </xsl:attribute>
            <xsl:call-template name="types"/>
            <xsl:for-each select="$elements">
                <xsl:call-template name="message">
                    <xsl:with-param name="elementName">
                        <xsl:value-of select="@name"/>
                    </xsl:with-param>
                </xsl:call-template>
            </xsl:for-each>
            <xsl:call-template name="portType"/>
        </wsdl:definitions>
    </xsl:template>

    <!-- WSDL types  -->
    <xsl:template name="types">
        <wsdl:types>
            <xsl:copy-of select="/xsd:schema"/>
        </wsdl:types>
    </xsl:template>

    <!-- WSDL Message -->
    <xsl:template name="message">
        <xsl:param name="elementName"/>
        <wsdl:message>
            <xsl:attribute name="name">
                <xsl:value-of select="concat($elementName,$messageSuffix)"/>
            </xsl:attribute>
            <wsdl:part name="body">
                <xsl:attribute name="element">
                    <xsl:value-of select="concat($schemaPrefix,':',$elementName)"/>
                </xsl:attribute>
            </wsdl:part>
        </wsdl:message>
    </xsl:template>

    <!-- WSDL Port type -->
    <xsl:template name="portType">
        <xsl:variable name="portTypeName" select="concat($name, $portTypeSuffix)"/>
        <wsdl:portType>
            <xsl:attribute name="name">
                <xsl:value-of select="$portTypeName"/>
            </xsl:attribute>
            <!-- Find all elements that end with $requestSuffix, and start an operation for that  -->
            <xsl:for-each select="/xsd:schema/xsd:element[ends-with(@name, $requestSuffix)]">
                <xsl:call-template name="portTypeOperation">
                    <xsl:with-param name="operationName">
                        <xsl:value-of select="substring-before(@name,$requestSuffix)"/>
                    </xsl:with-param>
                </xsl:call-template>
            </xsl:for-each>
        </wsdl:portType>
        <xsl:call-template name="binding">
            <xsl:with-param name="portTypeName" select="$portTypeName"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="portTypeOperation">
        <xsl:param name="operationName"/>
        <wsdl:operation>
            <xsl:attribute name="name">
                <xsl:value-of select="$operationName"/>
            </xsl:attribute>
            <xsl:for-each select="key('requestElements', $operationName)">
                <wsdl:input>
                    <xsl:attribute name="name">
                        <xsl:value-of select="concat($operationName, $inputSuffix)"/>
                    </xsl:attribute>
                    <xsl:attribute name="message">
                        <xsl:value-of select="concat($prefix,':',@name,$messageSuffix)"/>
                    </xsl:attribute>
                </wsdl:input>
            </xsl:for-each>
            <xsl:for-each select="key('responseElements', $operationName)">
                <wsdl:output>
                    <xsl:attribute name="name">
                        <xsl:value-of select="concat($operationName, $outputSuffix)"/>
                    </xsl:attribute>
                    <xsl:attribute name="message">
                        <xsl:value-of select="concat($prefix,':',@name,$messageSuffix)"/>
                    </xsl:attribute>
                </wsdl:output>
            </xsl:for-each>
        </wsdl:operation>
    </xsl:template>

    <xsl:template name="binding">
        <xsl:param name="portTypeName"/>
        <xsl:variable name="bindingName" select="concat($name, $bindingSuffix)"/>
        <wsdl:binding>
            <xsl:attribute name="name">
                <xsl:value-of select="$bindingName"/>
            </xsl:attribute>
            <xsl:attribute name="type">
                <xsl:value-of select="concat($prefix,':',$portTypeName)"/>
            </xsl:attribute>
            <wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
            <!-- Find all elements that end with $requestSuffix, and start an operation for that  -->
            <xsl:for-each select="/xsd:schema/xsd:element[ends-with(@name, $requestSuffix)]">
                <xsl:call-template name="bindingOperation">
                    <xsl:with-param name="operationName">
                        <xsl:value-of select="substring-before(@name,$requestSuffix)"/>
                    </xsl:with-param>
                </xsl:call-template>
            </xsl:for-each>
        </wsdl:binding>
        <xsl:call-template name="service">
            <xsl:with-param name="bindingName" select="$bindingName"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="bindingOperation">
        <xsl:param name="operationName"/>
        <wsdl:operation>
            <xsl:attribute name="name">
                <xsl:value-of select="$operationName"/>
            </xsl:attribute>
            <wsdlsoap:operation>
                <xsl:attribute name="soapAction">
                    <xsl:value-of select="concat($soapActionPrefix,'/',$operationName)"/>
                </xsl:attribute>
            </wsdlsoap:operation>
            <xsl:for-each select="key('requestElements', $operationName)">
                <wsdl:input>
                    <xsl:attribute name="name">
                        <xsl:value-of select="concat($operationName, $inputSuffix)"/>
                    </xsl:attribute>
                    <wsdlsoap:body use="literal"/>
                </wsdl:input>
            </xsl:for-each>
            <xsl:for-each select="key('responseElements', $operationName)">
                <wsdl:output>
                    <xsl:attribute name="name">
                        <xsl:value-of select="concat($operationName, $outputSuffix)"/>
                    </xsl:attribute>
                    <wsdlsoap:body use="literal"/>
                </wsdl:output>
            </xsl:for-each>
        </wsdl:operation>
    </xsl:template>

    <xsl:template name="service">
        <xsl:param name="bindingName"/>
        <wsdl:service>
            <xsl:attribute name="name">
                <xsl:value-of select="concat($name, $serviceSuffix)"/>
            </xsl:attribute>
            <wsdl:port>
                <xsl:attribute name="name">
                    <xsl:value-of select="concat($name, $portSuffix)"/>
                </xsl:attribute>
                <xsl:attribute name="binding">
                    <xsl:value-of select="concat($prefix,':', $bindingName)"/>
                </xsl:attribute>
                <wsdlsoap:address>
                    <xsl:attribute name="location">
                        <xsl:value-of select="$location"/>
                    </xsl:attribute>
                </wsdlsoap:address>
            </wsdl:port>
        </wsdl:service>
    </xsl:template>

</xsl:stylesheet>

Comments (4)

WS-Addressing needs a Phone Book

I’ve been looking into WS-Addressing lately, the W3C specification that aims to provide a transport-neutral mechanisms to address Web services. The basic idea is that an incoming SOAP message contain so-called Message Information Headers in the header, and that information is used to find an Endpoint Reference that matches it. The response is then sent back using that same information. It might not even go back to the same requester, if the header contains a ReplyTo or FaultTo.

A very useful specification, right?

Well, it depends on which one you mean. Turns out there are multiple versions available. There is the March 2003 version, the March 2004 version, and the August 2004 version. Then, control was handed over to the W3C, which split it into multiple parts: a generic part, a SOAP part, and a WSDL. Perhaps they thought the original version was too easy to read. The W3C published two version, one in August 2005, and the final version in May 2006.

The funny thing is that all of these version differ not only in namespace, because that would be easy to solve, but also in minute other ways. For instance, some define RefererenceProperties as part of the EndpointReference, while other define RefererenceParameters. Some define both. The difference between these two is that one is used to identify the endpoint being conveyed, while the other are associated with the endpoint to facilitate a particular interaction. You can guess which is which.

The inexperienced developer, who is not used to WS-* specifications, might think that this is easily solved. Just implement the final, latest version! Everyone implements that one, right?

Wrong again. Nobody actually implements the final version yet, as far as I can determine. Here’s a little matrix which shows which version are implemented by which some popular SOAP stacks. One interesting thing to notice is that Microsoft doesn’t even support WSE 2.0 in WSE 3.0.

ActiveSOAP Axis 2 WSE 2.0 WSE 3.0 XFire
March 2003 X
March 2004 X
August 2004 X X X
August 2005 X X
May 2006 X
(This table was created by browsing source code, and reading documentation. I could be wrong. I hope I’m wrong. Please tell me the WS-Addressing world is better than this). (Update: Dims informs me that Axis 2.0 implements the May 2006 version. My mistake! It’s hard to figure out by looking at the code, since the August 2005 and May 2006 version actually have the same namespace: http://www.w3.org/2005/08/addressing).

There are two lessons to be learned from this, besides the regular “WS-* SUCKS” brouhaha. If anyone tells you they implement WS-Addressing, make sure they tell you which version. And if we every want to make this interoperability thing work, we’d better not write and implement five different versions before we get it right.

Comments (5)