Friday, August 31, 2007

Code first mayhem: generating WSDL from EJB interfaces

I'm working on a project that involves writing stubs for EJB services. Our approach is to write a simple EJB that delegates to a web service (stub "intelligence" lies in the web service).

Obviously, we want the web service to conform to the same API as the original EJB interface: same methods, same parameter lists, etc. So, I'm using the Artix java2wsdl tool to generate corresponding WSDL artifacts (and associated XSD) from the EJB interfaces. This tool is based on Apache CXF java2wsdl, which in turn is based on the reference implementation of JAX-B.

Now, you'd think that this should be a cinch: however, I ran into a number of difficulties and had to some up with some crafty workarounds. The bottom line is that generating contracts from raw code is trickier that you might expect. Read on for your pleasure...

First Problem: Naming Conflicts. The EJB interface uses types that create naming conflicts with wrapped-doc-literal artifacts.

Who would have thought it? The EJB interface used a wrapping style that conflicted with the naming conventions for wrapped-doc-literal. The EJBs had APIs that looked like this:

void sayHello(String s, SayHello params),

java2wsdl will try and create a schema definition for SayHello (the type in the parameter list). The tool will then try and create a wrapper XSD element for the sayHello message, also called SayHello. Ouch... naming collision here we come.

To get around this, I took advantage of the fact that SayHello (the parameter type) was actually in a different Java package: I just needed to map the Java package to a different schema namespace. This is tricky though: the only way to do this in JAX-B is to annotate the source: I only had access to the class files. I posted the problem to the cxf-dev mailing list, you can read more on the problem there. The solution was to artificially recreate the EJB package structure, inserting package-info.java classes to control the mapping. Then, by placing this hierarchy in front of the EJB jars in the classpath, we were able to take control of the mapping as if the annotations had been their all along. Tricky, but fun: in the end we decided this was "Artful Hackery".

Second problem: public fields clash with set/get methods.
Some of the data types used by EJB had a public member (say foo) along with a getFoo() method:

class MyDataType {
public int foo;
public int getFoo();
}

Alarmingly, the java2wsdl compiler complained that there were "two members with the same name." Double ouch. It turns out that to fix this problem, I had to add some smarts to the package-info.java file I created to front the package containing the data types, setting the XmlAccessorType to be FIELD.

Something like this:

@javax.xml.bind.annotation.XmlAccessorType(
javax.xml.bind.annotation.XmlAccessType.FIELD
)


Third problem: beans need to have a no-argument constructor. One of the data-types used in an interface didn't have a default no-argument constructor. This was enough to stop the java2wsdl compiler in its tracks. I couldn't find a JAX-B workaround for this; also I don't know of a way to inject a no-argument constructor into an existing class.

So: hurrah for Java decompilers: I decompiled the offending class, added a constructor, recompiled it and placed it in the CLASSPATH ahead of the Jar so that it would be picked up first.

Phew. "Apart from that, it all went swimmingly".

No comments: