Sunday, 27 May 2007

Creating doc-lit WSDLs that "unwrap" nicely

Introduction

This article is designed to help you craft WSDL 1.1 service descriptions that work well with the available tools to generate simple client interfaces. WSDL 1.1 defines three possible options you can use to define services:

  • rpc-encoded
  • rpc-literal
  • document-literal

The WS-I Basic Profile (BP) outlawed rpc-encoded style, leaving rpc-lit and doc-lit as the options. However, it is widely accepted that doc-lit is the most interoperable approach. The concept behind doc-lit is that you are transferring an XML document, and that you use XML Schema to define the document structure. However, you can still use this model object interactions. To do this, you need to use the "wrapped" style. You can read Anne Thomas Manes description of this approach here. This article gives a little more guidance on making this work with Microsoft .NET WCF and Apache Axis2.

This article refers to WSDL 1.1. I'd like to point out that WSDL 2.0 does a much better job of this. You can read the actual definition of the wrapped RPC style in the WSDL2.0 spec. I recommend reading these as they also capture some important rules for the 1.1 scenario too.

Motivation

Modern XML mapping tools such as Apache XMLBeans can map almost any XML into objects. However, XML structures don't always map nicely into Java or C# or VB. We want the code that we generate out of WSDL to be readable and simple. So if we started out with an operation:

   float getStockQuote(String symbol, Date date)

then we would like our service consumers to be able to generate similar code from our WSDL.


But if we are using document-literal style, then we have a single schema definition that "wraps" those parameters, so we are more likely to get:


getStockQuoteResponse getStockQuote  
(GetStockQuoteRequest request)

and then have to look inside the GetStockQuoteRequest object to find the data you need. Ok, it's not the end of the world, but on the other hand it would be nice to be able to avoid this, and still have interfaces that use the primitive types or simple objects. The wrapping and unwrapping approach is exactly what sorts this out, and we are going to examine how to make sure that the WCF tooling (svcutil.exe) and Axis2 tooling (WSDL2Java) will properly unwrap your doc-lit WSDL.


Of course if you start with C# or Java objects then the WSDLs that are generated by WCF or Axis2 will conform to these rules. Please note that in previous versions to Axis2 1.2 not all the subtleties of WCF were catered for, so if you are using Axis2 1.1.1 or before then this guide can also help you tweak the generated WSDL so that WCF svcutil will treat it correctly.


Microsoft WCF's svcutil.exe automatically unwraps code if it can, and adds various comments explaining why it couldn't in other cases. Axis2's WSDL2Java needs to be switched into "unwrapping" mode by using the command line parameter:

wsdl2java -uw 

Getting the schema right


The wrapper


The idea behind doc-lit wrapped is that the XML documents that you transfer match the operations and parameters of the objects you are calling. So there is a single top-level element: the "wrapper". It must be named the same as the operation you are calling. So if the operation is named getProfile you must have a <getProfile> element.


The contents of the wrapper


The wrapper contains the "parameters" of the call. In the schema you must have a <sequence> of elements each of which corresponds to one the parameters of the operation. For example, suppose we have an operation:


boolean addProfile(String nickname, Profile profile);


The input message is going to carry the two parameters - nickname and profile. Assuming we have a schema definition (tns:profile) that matches the Profile object, then the schema will look like this:

<element name="addProfile">
<complexType>
<sequence>
<element name="nickname"
nillable="true" minOccurs="0"
type="string" />
<element name="profile" nillable="true"
minOccurs="0" type="tns:profile" />
</sequence>
</complexType>
</element>

You must mark any "object-types" (strings, dates, or complexTypes) nillable="true" if you want the .NET svcutil tooling to treat this as a "wrapped" WSDL. I also recommend adding minOccurs="0". You can mark primitive types with nillable="true" but it isn't recommended, because typically you don't want these to map to nullable objects.


The same approach applies to responses. You must name the response element operationNameResponse - for example addProfileResponse - otherwise WCF's svcutil.exe will drop out of the unwrapping mode.


A few more schema hints


If you are using Axis2 1.1.1 or before, you must inline the schema. Typically its nice to have schema imported from another file using <xsd:import> or <xsd:include>. This has been fixed in Axis2 1.2 and beyond.


In addition, you also need to make sure your schema is marked:

elementFormDefault="qualified"

Otherwise .NET will switch out of unwrapping mode.


Getting the WSDL right


In addition to getting the schema right, the WSDL also has to be right. The most obvious things are of course that the service uses the doc-lit model:

  <soap:binding style="document" ....>

and

  <soap:body use="literal" />

In addition each message must have a single part, referencing one of your schema element wrappers, and the part must be named "parameters" else svcutil.exe will once agains switch out of the unwrapping mode.


Example:

<wsdl:message name="addProfileRequest">
<wsdl:part element="tns:addProfile"
name="parameters" />
</wsdl:message>

Example


Just to give you the idea, here are a couple of snippets of C# and Java showing the client code generated when unwrapping is recognized and when it isn't.


C# client without unwrapping

    // CODEGEN: Generating message contract since the wrapper name (getProfileRes) of message getProfileResponse does not match the default value (getProfile)
[System.ServiceModel.OperationContractAttribute(Action="http://www.fremantle.org/wsdls/ProfileServer/getProfile", ReplyAction="*")]
getProfileResponse getProfile(getProfileRequest request);

In this case svcutil jumped out of unwrap mode because the response wrapper was not named getProfileResponse.


C# client with unwrapping

    [System.ServiceModel.OperationContractAttribute(Action="http://www.fremantle.org/wsdls/ProfileServer/getProfile", ReplyAction="*")]
[return: System.ServiceModel.MessageParameterAttribute(Name="profile")] www.fremantle.org.ProfileServer.profile
getProfile(string nickname);

Notice how in this model there are no "wrapper" objects (getProfileRequest and getProfileResponse) but instead the interface uses the base objects and of course primitive types if they are used.


Java client without unwrapping

public  GetProfileResponse getProfile(GetProfile getProfile0) { ... }                    

Java client with unwrapping

public  Profile getProfile(String nickname2) { ... }

Summary


Here is a reminder of the things to check for:



  • SOAP binding style: document

  • SOAP body use: literal

  • Schema: elementFormDefault="qualified"

  • Each message has a single part, named "parameters"

  • Each message must refer to an element not a type

  • The input element must be named the same as the OperationName

  • The output element must be named OperationNameResponse

It may seem like this is a lot of bother to go to, just to fool some bit of code into doing the right thing! On the other hand, you may be creating this WSDL once, and you hope it will be used by many people and many different tools to create clients and use your service as much as possible. It might just be worth the effort to get it right. And unlike wrapping presents, you can wrap your WSDL just once and have lots of different people unwrap it over and over again!

2 comments:

specmurt said...

Paul,

Thank you for a good summary. I've been using this technique for ages but never managed to write summary.

Egor.

faith said...

Great post!! Does jax-wx tool offer a similar option as the -uw axis2 wsdl2java?