Wednesday, 30 May 2007

What is the speed of an XML SAX Parser?

I recently found this FAQ (see the bottom of the page). This cracked me up:

What is the speed of an xml sax parser?

What do you mean, a validating or a non-validating parser?

Apologies for those of you who aren't Monty Python fan's!

[Hint: What is the air-speed velocity of an unladen swallow?]

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!

Wednesday, 23 May 2007

Danish SOA infrastructure

InfoQ have posted a presentation by Mikkel Hippe Brun, talking about the Danish national SOA project. I've been very involved with this initiative and I recommend watching this.

Tuesday, 22 May 2007

Getting started with WSRM and Axis2

This is a short HOWTO on getting your first WS-ReliableMessaging interaction going with Axis2 and Sandesha2. [Sandesha2 is an Open Source implementation of both the WSRM 1.0 and WSRM 1.1 specifications].

First, download the prereqs.

I'm proposing you do this with Axis2 1.2 - the latest release at the time of writing (May 2007).

Here is the download. You can use the standalone ZIP or the WAR file. I'm assuming you've already used Axis2 enough to know deploying services and creating clients.

The Sandesha2 1.2 release isn't yet available, so I'm posting a snapshot that you can use. As soon as it becomes available I'll point to the real thing.

Make the Version service Reliable
1) Take the version.aar file from the Axis2 distribution and edit the services.xml

Underneath the <services> tag add
<module ref="sandesha2">


You can either do it by hand, and update the AAR file (which is just a JAR file) or by go edit the build in <axis2_home>\samples\version.

I use 7-Zip file manager, which updates MAR and AAR files with any edits I make in-place which is great. I can just browse the AAR file, edit the XML, save it, and 7-Zip updates the AAR with my edits.

If you are too lazy to do either of those, I've posted the version.aar file with the updates here.

Add the right phases to Axis2
2) Edit your /conf/axis2.xml to add
<phase name="RMPhase"/>


Just between OperationIn/Out/InFault/OutFaultPhases and SoapMonitorPhase (4 places in all):

e.g.
<phase name="OperationInFaultPhase"/>
<phase name="RMPhase"/>
<phase name="soapmonitorPhase"/>

Deploy Sandesha2
3) Deploy sandesha2.mar into <AXIS2_HOME>/repository/modules

Check if its working
4) Start Axis2 and verify that the RM operations are listed when you
look at the version service via the browser
http://localhost:8080/axis2/services/

You should see extra operations listed under Version such as
  • Sandesha2OperationInOut
  • Sandesha2OperationOutIn
  • getVersion
  • Sandesha2OperationOutOnly
  • Sandesha2OperationInOnly
  • Sandesha2OperationDuplicate

Create a client
5) Build a client for the service using WSDL2Java:
\bin\wsdl2java.bat -uw -uri http://localhost:8080/axis2/services/Version?wsdl -o client

This will put the client code in .\client\ directory from where you ran the command.

WSDL2Java automatically creates an Ant build script, so you can test the build works using:
> ant

If you use Eclipse you can create a new project for this based on the Ant build.xml and you will get all the right libraries, etc. Just select File->New->Project, Java Project From Existing Ant Buildfile

6) Now go to .\client\src and add this Java class TestRM.java

import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HttpTransportPropertie.ProxyProperties;

import sample.axisversion.VersionVersionSOAP12Port_httpStub;

public class TestRM {
public static void main(String[] args) throws Exception {

// use a config context to ensure I have
// the right Axis2.xml and
Sandesha2 module
// Change the paths to fit your filesystem of course
ConfigurationContext cc = ConfigurationContextFactory.createConfigurationContextFrmFileSystem("c:/axis2-1.2/repository","c:/axis2-1.2/conf/axis2.xml");
// Use the generated Stub with the config Context
VersionVersionSOAP12Port_httpStub stub = new VersionVersionSOAP12Port_httpStub(cc,"http://localhost:8080/axis2/services/Version");

// WSRM prereqs Addressing
stub._getServiceClient().engageModule("addressing");

// Sandesha2 provides WSRM support
stub._getServiceClient().engageModule("sandesha2");

// go through a proxy so I can see what is happening
// dont forget to startup TCPMON - tcpmon 8001
ProxyProperties pp = new ProxyProperties();
pp.setProxyName("localhost"); pp.setProxyPort(8001);
stub._getServiceClient().getOptions().setProperty(HTTPConstants.PROXY,pp);

// This is the first message, so it will automatically kick off a CreateSequence
System.out.println(stub.getVersion());

// now mark the second message as the last
stub._getServiceClient().getOptions().setProperty("Sandesha2LastMessage","true");

// after the last message is sent, we should send a Terminate Sequence automatically
System.out.println(stub.getVersion());

// give enough time for it to work
Thread.sleep(1000);

// System exit because there will be Sandesha threads otherwise running
System.exit(0);
}
}

Let's just look at this code
Firstly, what is the overall flow? Simple! New up a stub, call stub.getVersion() twice and then exit. I've bolded the changes we make to be reliable.
So what have we added to make it work with RM?
  • Instead of using the default config, we explicitly want Axis2 to read the modified axis2.xml and also to use the repository containing Sandesha2.mar. So we use a custom ConfigurationContext.
  • Secondly, we want to see RM happening, so we set up a Proxy. For this we are going to use TCPMON.
  • Thirdly, we need to engage Addressing and Sandesha to ensure the modules do their thing.
  • We need to let Sandesha know when we are complete. There are a couple of ways to do this, but the very simplest is to mark the LastMessage. (This is based on WSRM1.0), so just before the second call to getVersion() we do this.
  • Finally, Apache Sandesha spawns threads, so we do a Thread sleep and then a System.exit(0) which ensures that the code exits.
7) Build everything (Running ant in client\ should work).

8) Remember to start TCPMON up. It needs to be running in PROXY mode on port 8001.
You can do this by typing
> tcpmon 8001

9) If you used Ant to build the file then you can call the class like this:
> java -Djava.ext.dirs=\axis2-1.2\lib;\code\client\build\lib\ TestRM

Obviously this only works if your Axis2 is in \axis2-1.2 and you built the client in the \code directory, but you get the idea and can switch it to work.

10) You should see something like:
May 21, 2007 5:45:21 PM org.apache.axis2.deployment.ModuleDeployer deploy
INFO: Deploying module: addressing-1.2
May 21, 2007 5:45:22 PM org.apache.axis2.deployment.ModuleDeployer deploy
INFO: Deploying module: sandesha2
May 21, 2007 5:45:22 PM org.apache.axis2.deployment.ModuleDeployer deploy
INFO: Deploying module: soapmonitor-1.2
May 21, 2007 5:45:22 PM org.apache.sandesha2.SandeshaModule init
WARNING: Could not load module policies. Using default values.
May 21, 2007 5:45:22 PM org.apache.axis2.deployment.ServiceDeployer deploy
INFO: Deploying Web service: version.aar
Hello I am Axis2 version service , My version is 1.2
Hello I am Axis2 version service , My version is 1.2

11) If you look at the TCPMON Trace you should see 4 message interactions:
1. CreateSequence/CSR
2. Request 1
3. Request 2/LastMessage
4. TerminateSequence.


Congratulations - you have just got RM working (I hope!!!).