Monday, January 3, 2011

Grails,CXF plugin and .NET

As promised here's a follow up on the WebService interoperability between Grails using Apache CXF plugin and .NET.

First let's create a simple example project and install the grails-cxf plugin:
grails create-app example
grails install-plugin cxf

Next let's create a service:
grails create-service example

This has created 2 files:
grails-app/services/example/ExampleService.groovy
and
test/unit/example/ExampleServiceTests.groovy

The first one is the one we need to modify so let's get to it:
package example

import javax.jws.*

@WebService(serviceName="ExampleService", name="Example")
class ExampleService {

static expose = [ 'cxfjax' ]

@WebMethod(operationName="sayHello")
String sayHello(@WebParam(name="name") String name) {
"Hello ${name}!"
}
}

As you can see I've added every possible bit of information to customize the generated WSDL. With all that in place we can get the description of this service (WSDL) at http://localhost:8080/example/services/example?wsdl. Let's use that to create a .NET client. This time from command-line:
SET PATH=%PATH%;"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin"
wsdl.exe http://localhost:8080/example/services/example?wsdl

In the first line we're expanding the system path so that all the tools are available from anywhere. In the second line we're generating a client for the web service we've created in Grails. Simple enough, right?

Let's create a simple console application and use this newly generated client:
Program.cs:
using System;

public class Progra {
public static void Main(String[] args) {
Console.WriteLine(new ExampleService().sayHello("John"));
}
}

Let's compile everything from command-line:
set PATH=%PATH%;C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319
csc *.cs

Again, no magic here. Once you've compiled everything run the application and bum! Everything works as expected :)
My guess is that CXF plugin didn't work just right out of the box because of some problems with default naming that CXF is using when auto-generating WSDL. With all the annotations in place everything seems to be working just fine.

I hope this will help someone. In case you'd like to fiddle with it yourself here's the example. in src/csharp you'll find the sources for the client along with a batch file to compile them.

Unfortunately when it comes to a more advanced scenario, like for example returning an array of domain objects from a service method the CXF plugin still fails to produce good enough WSDL to work with .NET. This is the case where XFire plugin shines best!

FYI: the same trick does not work with Axis2 plugin. The naming is all whacko. Things like this$dist$set$2 and urn:this$dist$get$2 hurt the WSDL code generator so much it spits nothing out but a load of warnings.

Have fun!

8 comments:

Christian said...

If you get the time, you might want to attempt this scenario again with the new 1.x cxf plugin. Support should be available for lists and maps now. I am curious how this works with .net anywho.

http://grails.org/plugin/cxf

Matthias Hryniszak said...

@Christian I tried it out with Mono 2.10 but no cigar. I couldn't create a service that returns an array (or List) of objects other than primitives.

Christian said...

I will give it a whirl myself and see if I can piece it together. Thanks for checking.

Christian said...

I did a test of my own and was able to get the plugin working great with list and map type responses. Please see the project:

https://github.com/ctoestreich/cxf-mono-test

Let me know if there was some other scenario that you were having trouble with.

Matthias Hryniszak said...

@Christian I ended up with the following output
1 :: 1 Binders full of women.
2 :: 1 Binder?! I hardly know her!

Unhandled Exception: System.Web.Services.Protocols.SoapException: Message part {http://test/}getMaps was not recognized. (Does it exist in service WSDL?)
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReceiveResponse (System.Net.WebResponse response, System.Web.Services.Protocols.SoapClientMessage message, System.Web.Services.Protocols.SoapExtension[] extensions) [0x00000] in <filename unknown>:0
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke (System.String method_name, System.Object[] parameters) [0x00000] in <filename unknown>:0
at RemoteReportServiceService.getMaps () [0x00000] in <filename unknown>:0
at (wrapper remoting-invoke-with-check) RemoteReportServiceService:getMaps ()
at cxf.TestService.testMaps () [0x00000] in <filename unknown>:0
at cxf.TestService.Main (System.String[] args) [0x00000] in <filename unknown>:0
[ERROR] FATAL UNHANDLED EXCEPTION: System.Web.Services.Protocols.SoapException: Message part {http://test/}getMaps was not recognized. (Does it exist in service WSDL?)
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReceiveResponse (System.Net.WebResponse response, System.Web.Services.Protocols.SoapClientMessage message, System.Web.Services.Protocols.SoapExtension[] extensions) [0x00000] in <filename unknown>:0
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke (System.String method_name, System.Object[] parameters) [0x00000] in <filename unknown>:0
at RemoteReportServiceService.getMaps () [0x00000] in <filename unknown>:0
at (wrapper remoting-invoke-with-check) RemoteReportServiceService:getMaps ()
at cxf.TestService.testMaps () [0x00000] in <filename unknown>:0
at cxf.TestService.Main (System.String[] args) [0x00000] in <filename unknown>:0


Any idea what that might be?

Matthias Hryniszak said...

Anyways, I think I'm going to check pure CXF in a Spring application to verify if the actual issue lies with CXF itself or with the CXF-Grails integration. I'll let you know what I found out. But first - the new James Bond awaits!

Christian said...

Oops I forgot to check in the other project's additional map method :). In Now.

I Given that you did get output from the list response, I would think that any issues are getting have to do with core cxf. It can be a bit tricky to get all the annotations correct for JAXB, service, etc. to handle your responses. I call out a few of the gotchas in the plugin source readme page.

Christian said...

Bond? Lucky. Unless... can I time travel to November 9, 2012 here in the US?! hmm.