Archive for Spring

How to create your own GoogleWhack

Two short updates on WS-DuckTyping.

  • I will be doing a talk on WS-DuckTyping at SpringOne 2007. As a little joke, I used the word Anatidaeic in the abstract. Until two weeks ago this word had 0 hits on Google. Currently, it has one hit, therefore making it a true GoogleWhack. Of course, writing this post will make an end to that soon.
  • I wrote a post on XPath support in Spring Web Services over on the Interface21 team blog. Check it out!

Comments off

WS-DuckTyping

One of the most popular features of languages such as Smalltalk, ObjectiveC, and Ruby is Duck typing. To quote Wikipedia:

[…] duck typing is a form of dynamic typing in which a variable’s value itself implicitly determines what the variable can do. This implies that an object is interchangeable with any other object that implements the same interface, regardless of whether the objects have a related inheritance hierarchy.[…]

The term is a reference to the duck test — “If it walks like a duck and quacks like a duck, it must be a duck”.

So what does this have to do with Web services? Well, duck typing is a great way to create graceful and interoperable Web services. Web services are about exchanging information, and as long as the information walks like a duck…

Here are my three tips to implement WS-Duck Typing:

Don’t validate incoming messages!

Not only is XSD-based validation slow, it also requires strict schema conformation from the other party, thus creating a strictly typed service. Such a service breaks Postel’s Law: be conservative in what you do; be liberal in what you accept from others.

If you really want to do validation, do it on server-side outgoing messages only. After all, you should adhere to your own schema. Also, Schematron is the exception to the rule, since it not based on grammars, but on finding tree patterns in the parsed document. Which brings us to:

Use XPath!

XPath is an excellent way to extract information from an XML document. Code written with XML API like DOM, SAX, or StAX is typically quite fragile when it comes to element ordering, nesting, or unexpected elements. And XML marshalling isn’t much better: some of these API’s throw exceptions in these cases.

Not with XPath. When using XPath, you don’t care whether the <lastName> element is the first or the second child of <person>; the /person/lastname expression grabs it anyway. And if if you really don’t know where to find the last name, you can always resort to //lastname, which finds it anywhere in the document.

In the past, XPath has dismissed as being too slow. With modern XPath libraries which support XPath pre-compilation, this is less of an issue.

Don’t create stubs or skeletons!

This is perhaps the most controversial tip. If you create client-side stubs or server-side skeletons in a strongly-typed language like Java, you throw away any option of being liberal about the XML messages. Instead, you have create a strongly-typed API that is strongly-coupled to the contract, and that passes or expects parameters of a certain kind. If they are of any other kind, or if they are simply not there, your code will never be invoked. Even if you didn’t need the parameter in the first place.

If you treat Web services like XML messaging, rather than RPC, you could have handled the message gracefully: let’s see if I can find the first name under the person element, and if it’s not there, I’ll try and find in anywhere in the document. Still not there? Perhaps it’s an older message: I’ll just apply this stylesheet, and see if I can find the first name then. Et cetera, et cetera.

Hopefully, these tips will help you create flexible, interoperable Web services that gracefully handle XML messages.

Quack!

Comments (25)

Nobody cares about your stack trace!

Some Web service stacks offer an option to send any server-side stack trace over to the client, as part of a <faultDetail/> block. Axis 1 does it, and it seems like the JAX-WS reference also offers this feature. Presumably, this makes it easier to debug the service from the client side.

Let me explain why I think that there is a big difference between a SOAP Fault and an exception, and why I don’t offer this feature in Spring-WS:

Let’s say I call a public Web service to get a stock quote. Instead of the result, I might get a SOAP Fault as a reponse, which means something went wrong. In SOAP 1.1, a Fault contains four elements, the first two of which are required:

  1. a code (a qualified name)
  2. a string
  3. an actor, and
  4. a detail (which can contain any number of xml elements)
A Java or .NET Exception, on the other hand, has just two elements:
  1. a string message, and
  2. a stack trace
Notice the difference between the two? Of course, you can use the exception message as the fault string, and you can create some kind mapping between the exception class and the code, but that’s your mapping. There is no standard, interoperable way to write stack traces in faults, mostly because stack traces can only be found in Java, but not in C, for instance. And Web services are all about interoperability.

Back to the stock quote: imagine the Web service is not written in Java, but in C, which means we cannot send any stack trace as part of the fault. So what do we send in order to facilitate debugging on the client side? Do we attach the core dump ;) ?

There’s a simple lesson here: it only makes sense to use Web service technology where the client isn’t interested to the language of the server. If you really want to see the stack trace on the client-side, use Java RMI. If you want to invoke a method on a class written in another language, use CORBA or Hessian. If you want to do XML messaging, use SOAP.

Comments (7)

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)

Tutorial: Writing Contract-first Web Services

Over at the Spring-WS forum, people are starting to get convinced that doing contract-first Web service development is a good thing. After all, it’s all about the XML!

However, now that people have started working on their WSDLs and schema’s, a whole new range of questions come up. Mostly people are looking for advice or tutorials on writing schema’s and WSDLs. I could find any good ones, so I decided to write my own (I will leave it up to you whether this one is any good). The tutorial has three parts:

  1. the Messages
  2. the Schema
  3. the WSDL
In this post, I will use the excellent SOA methaphor provided by Dan North as a guiding scenario. You might want to read up on that. Basically, the Web service we’re going to define is a Human Resources service, to which you can send your holiday request.

Messages

The first thing to do is to forget about the programming language you use to implement the Web service. That’s right: forget about the curly braces, we’re doing angle brackets here!

Holiday

Next, we determine what the basic XML used in our service will look like. In our scenario, we have to deal with holiday request, so it makes sense to determine what a holiday looks like:

<Holiday>
  <StartDate>2006-07-03</StartDate>
  <EndDate>2006-07-07</EndDate>
</Holiday>

A holiday is just a start date and an end date. We decided to use the standard ISO 8601 date format for this, because we want to stick to the standards.

Note that there’s something missing? This isn’t 2000 anymore: namespaces are very fashionable these days. We might want to add one:

<Holiday xmlns="http://mycompany.com/holidays/schemas">
  <StartDate>2006-07-03</StartDate>
  <EndDate>2006-07-07</EndDate>
</Holiday>

Employee

We also have the notion of an employee in our scenario. Here’s what it looks like:

<Employee xmlns="http://mycompany.com/holidays/schemas">
  <Number>42</Number>
  <FirstName>Arjen</FirstName>
  <LastName>Poutsma</LastName>
</Employee>

We used the same namespace as before. If this employee could be used in other scenario’s, it might make sense to use a different namespace, such as "http://mycompany.com/employees/schemas".

HolidayRequest

We put both elements together in a HolidayRequest:

<HolidayRequest xmlns="http://mycompany.com/holidays/schemas">
  <Holiday>
    <StartDate>2006-07-03</StartDate>
    <EndDate>2006-07-07</EndDate>
  </Holiday>
  <Employee>
    <Number>42</Number>
    <FirstName>Arjen</FirstName>
    <LastName>Poutsma</LastName>
  </Employee>
</HolidayRequest>

The order of the two element doesn’t really matter: Employee could have been the first element just as well. As long as all the data is there; that’s most important.

In fact, the data is the only thing that is important: we are taking a data-driven approach here, we have not defined any behavior. As Ted Neward tells us in his excellent Effective Enterprise Java, Item 19: In general, you’ll want to prefer data-driven communications, particularly when navigating across component boundaries. Ted is a clever guy, we might want to adhere to his advice.

Schema

Now that we have decided what our data looks like, it makes sense to formalize this into a schema. Nowadays, you have four different ways of defining a grammar for XML:

DTDs are a thing of the past, let’s forget about those. Relax NG and Schematron are nice, and certainly easier than XSDs. Unfortunately, they are not so widely supported across platforms. So let’s stick with XSDs.

By far the easiest way to create the XSD is to infer it from sample documents. There is a wide variety of tools available which do this. Basically, these tools look at your sample XML documents, and generate a schema from it that validates them all. You will certainly have to polish up the resulting XSD, but it’s a great starting point.

Using the sample above, we end up with the following generated schema:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    elementFormDefault="qualified"
    targetNamespace="http://mycompany.com/holidays/schemas"
    xmlns:holidays="http://mycompany.com/holidays/schemas">
  <xs:element name="HolidayRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="holidays:Holiday"/>
        <xs:element ref="holidays:Employee"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Holiday">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="holidays:StartDate"/>
        <xs:element ref="holidays:EndDate"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="StartDate" type="xs:NMTOKEN"/>
  <xs:element name="EndDate" type="xs:NMTOKEN"/>
  <xs:element name="Employee">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="holidays:Number"/>
        <xs:element ref="holidays:FirstName"/>
        <xs:element ref="holidays:LastName"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Number" type="xs:integer"/>
  <xs:element name="FirstName" type="xs:NCName"/>
  <xs:element name="LastName" type="xs:NCName"/>
</xs:schema>

Quite a lot of XML! Let’s try and make it simpler. The first thing to notice is that everything is a root-level element. This means that your Web service should be able to accept all of these elements as data. This is not what we want, we only want to accept a HolidayRequest for now. You can do this by removing the wrapping element tags (thus keeping the types), and inlining the results:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:holidays="http://mycompany.com/holidays/schemas"
    elementFormDefault="qualified"
    targetNamespace="http://mycompany.com/holidays/schemas">
  <xs:element name="HolidayRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Holiday" type="holidays:HolidayType"/>
        <xs:element name="Employee" type="holidays:EmployeeType"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="HolidayType">
    <xs:sequence>
      <xs:element name="StartDate" type="xs:NMTOKEN"/>
      <xs:element name="EndDate" type="xs:NMTOKEN"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="EmployeeType">
    <xs:sequence>
      <xs:element name="Number" type="xs:integer"/>
      <xs:element name="FirstName" type="xs:NCName"/>
      <xs:element name="LastName" type="xs:NCName"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

But there is still something wrong here. With a schema like this, you can expect the following messages to validate:

<HolidayRequest xmlns="http://mycompany.com/holidays/schemas">
  <Holiday>
    <StartDate>this is not a date</StartDate>
    <EndDate>neither is this</EndDate>
  </Holiday>
    ...
</HolidayRequest>

Clearly, we want to make sure that the start and end date are really dates. XML Schema has an excellent built-in date type for this, let’s use that. We might as well change those intimidating NCNames. And finally, we change the sequence in HolidayRequest to all. This tells the XML parser that the order of Holiday and Employee is not significant. Our final XSD looks like this:

  
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:holidays="http://mycompany.com/holidays/schemas"
    elementFormDefault="qualified"
    targetNamespace="http://mycompany.com/holidays/schemas">
  <xs:element name="HolidayRequest">
    <xs:complexType>
      <xs:all>
        <xs:element name="Holiday" type="holidays:HolidayType"/>
        <xs:element name="Employee" type="holidays:EmployeeType"/>
      </xs:all>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="HolidayType">
    <xs:sequence>
      <xs:element name="StartDate" type="xs:date"/>
      <xs:element name="EndDate" type="xs:date"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="EmployeeType">
    <xs:sequence>
      <xs:element name="Number" type="xs:integer"/>
      <xs:element name="FirstName" type="xs:string"/>
      <xs:element name="LastName" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>  

Save this as holidays.xsd, and you’re done with the schema.

WSDL

That leaves us with the WSDL. As I’ve written before, the WSDL format doesn’t really lend itself to data-driven communications, because it forces a behavior-driven model. In my opinion, you most certainly don’t need a WSDL: just give me the XSD and an URL to point my client to, and I’m happy. Other people are less easily satisfied, however.

We start our WSDL with the standard preamble, and by importing our existing XSD. To separate the schema from the definition, we will use a separate namespace for the WSDL definitions: "http://mycompany.com/holidays/definitions".

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
</wsdl:definitions>

Next, we define our messages based on the schema we’ve written. We only have one message: one with the HolidayRequest we put in the schema:

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="RequestHolidayInput">
    <wsdl:part name="body" element="types:HolidayRequest" />
  </wsdl:message>
</wsdl:definitions>

Then we add the messages to a port type as operations:

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="RequestHolidayInput">
    <wsdl:part name="body" element="types:HolidayRequest" />
  </wsdl:message>
  <wsdl:portType name="HumanResourcesPortType">
    <wsdl:operation name="RequestHoliday">
      <wsdl:input message="tns:RequestHolidayInput" />
    </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

That finished the abstract part of the WSDL (the interface, as it were), and leaves the concrete part of the WSDL. The concrete part consists of a binding, which tells the client how to invoke the operations you’ve just defined; and a service, which tells it where to invoke it. Let’s add those to our WSDL.

Adding a concrete part is pretty standard stuff: just refer to the abstract part you defined previously, make sure you use document/literal for the <soap:binding> elements (anything else is not interoperable), pick a soapAction (in this case http://example.com/RequestHoliday, but any URI will do), and determine the location URL where you want request to come in (in this case http://mycompany.com/humanresources):

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="RequestHolidayInput">
    <wsdl:part name="body" element="types:HolidayRequest" />
  </wsdl:message>
  <wsdl:portType name="HumanResourcesPortType">
    <wsdl:operation name="RequestHoliday">
      <wsdl:input message="tns:RequestHolidayInput" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="HumanResourcesBinding" type="tns:HumanResourcesPortType">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="RequestHoliday">
      <soap:operation soapAction="http://example.com/RequestHoliday" />
      <wsdl:input>
        <soap:body use="literal" />
      </wsdl:input>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="HumanResourcesService">
    <wsdl:port name="HumanResourcesPort" binding="tns:HumanResourcesBinding">
      <soap:address location="http://mycompany.com/humanresources" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>  

And that’s our final WSDL, and also concludes this tutorial. The resulting schema and WSDL should be interoperable, and can be implemented with the SOAP stack of your choice. In a future post, I will show you how to implement this service with Spring-WS [See update below].

You can go back to your curly braces now.

Update 2006-06-11

Patrick suggested to use a <xsd:import> instead of a <wsdl:import>, which is indeed better. This also allows you to put the schema in a separate namespace. Thanks, Patrick!

Update 2007-03-26

A more recent version of this tutorial, including a second part which shows you how to implement this service in Spring-WS, is available here.

Comments (4)

« Previous entries · Next entries »