The Sorry State of SAAJ
When I started working on Spring Web Services some three years ago, I needed a way to represent a SOAP message. I was a good boy and used J2EE, or more specifically: the SOAP with Attachments API for Java™, also know as SAAJ, also known as the package javax.xml.soap. Founding Spring-WS on a specification seemed like the Proper Thing To Do, and gave me a warm fuzzy feeling inside.
Over time, I found out that SAAJ is quite possibly the worst implemented piece of J2EE Java EE out there. Now I’m sharing the pain with you in this post because, well, sharing is the first step in acceptance.
A Bit Of History
SAAJ started out as being part of the Java™ API for XML Messaging (JAXM). JAXM actually was quite nice: none of this “look ma, it’s just a method call!” nonsense that JAX-RPC offered. Unfortunately, JAXM was pulled in favor of JAX-RPC. Some of JAXM survived, namely the representation of a SOAP message, and was rebranded SAAJ 1.1.
SAAJ 1.1 basically offered a DOM-like representation of a SOAP message: SOAPEnvelope has a SOAPBody, and an optional SOAPHeader, and so on. All elements extended SOAPElement, which in turn extended Node. javax.xml.soap.Node, that is, not org.w3c.dom.Node, which - coincidentally - was the main problem with SAAJ 1.1: there was no easy way to integrate SAAJ with existing JAXP code. Even though SAAJ was similar to org.w3c.dom, there was no clear conversion path, besides writing the whole message to a buffer and reading that with JAXP. Yay.
The wise folks at the JCP decided to change that in SAAJ 1.2: in this version, all SAAJ classes extend their W3C DOM counterparts. So javax.xml.soap.Node extends org.w3c.dom.Node, SOAPElement extends Element, and so on, and so forth. Not everybody thought this was a clever idea; clearly Hani was not among those in favor. At this point, SAAJ was added to J2EE 1.4. Some J2EE vendors seem to agree with Hani as to whether this was a good idea, more about that later.
SAAJ 1.3 introduced some more stuff: support for SOAP 1.2, MTOM, and more. As a result of this plethora of features, SAAJ graduated from Java EE to Java SE in version 6. I think this was probably more a side-effect of SUN putting JAX-WS in Java 6, which is subject to a different rant altogether (short rant preview: had I implemented JAX-WS, I would be pretty pissed at SUN. Aren’t there laws against this sort of bundling?)
The SAAJ Saga continues
So, as of SAAJ 1.2, a SOAPElement isa W3C DOM Element. This makes sense, because now you can use JAXP to put XML content into the some message. For instance, you can use standard XML-DSIG or XML-ENC libraries to sign or decrypt a SOAP message, handy when doing WS-Security. Or, you can read a bit of XML from a file, and dump that into the SOAP body as a payload. The only caveat mentioned in the javax.xml.soap javadoc is that:
an application that starts to use SAAJ APIs on a tree after manipulating it using DOM APIs must assume that the tree has been translated into an all SAAJ tree and that any references to objects within the tree that were obtained using DOM APIs are no longer valid.
No problem, we can do that. Just don’t keep any references dangling around. So we might write something like:
MessageFactory messageFactory = MessageFactory.newInstance();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
SOAPMessage message = messageFactory.createMessage();
Document document = createDocument();
SOAPBody body = message.getSOAPBody();
transformer.transform(new DOMSource(document), new DOMResult(body));
body = message.getSOAPBody();
transformer.transform(new DOMSource(body), new StreamResult(System.out));
message.writeTo(System.out);
Here, we create a new SAAJ message using the MessageFactory. Next, we create a W3C DOM document, and we use a javax.xml.transform.Transformer to transform the document to the SOAP body. As a result the root element of the document should be appended to the SOAP body, it will become the payload of the message. Next, we write the body to the console, and finally we write the whole message to the console. Simple enough, and conforming to the spec.
Yet, this simple program does not work on the majority of Java EE servers.
The Acid Test
I created a simple Servlet which basically does the above, and ran it in a wide variety of Java EE application servers. Here is an overview, all J2EE 1.4 or higher (with the exception of Tomcat, of course, I used that as a baseline and to test the Axis 1 SAAJ implementation). I’ve listed the name, the MessageFactory implementation provided by the server, the test result, and the exception given - if any. Finally, I’ve submitted bugs where possible, because I am a Good Citizen.
| Application Server | MessageFactory | Result | Exception | Bug |
|---|---|---|---|---|
| Geronimo 2.1.2 with Jetty | org.apache.geronimo.webservices.saaj.GeronimoMessageFactory |
✔ | ||
| Geronimo 2.0.2 with Tomcat | org.apache.geronimo.webservices.saaj.GeronimoMessageFactory |
✘ | TransformerException when transforming Document to SOAPBody |
GERONIMO-4029 |
| GlassFish v2ur1 | com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl |
✔ | ||
| JBoss 4.2.2 | org.jboss.ws.core.soap.MessageFactoryImpl |
✘ | IndexOutOfBoundsException when transforming SOAPBody to stream |
|
| OC4J 10.1.3.1 | oracle.j2ee.ws.saaj.soap.MessageFactoryImpl |
✘ | TransformerException when transforming Document to SOAPBody |
|
| Tomcat 6.0.16 with SAAJ RI | com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl |
✔ | ||
| Tomcat 6.0.16 with Axis 1.4 SAAJ | org.apache.axis.soap.MessageFactoryImpl |
✔ | ||
| WebLogic 9.2 | weblogic.webservice.core.soap.MessageFactoryImpl |
✘ | UnsupportedOperationException when getting SOAPBody |
|
| WebLogic 10.0 | weblogic.webservice.core.soap.MessageFactoryImpl |
✘ | UnsupportedOperationException when getting SOAPBody |
|
| WebSphere 6.1 | com.ibm.ws.webservices.engine.soap.MessageFactoryImpl |
✔ |
Of this bunch, WebLogic is the clear winner. Every time you call SOAPMessage.getBody(), WebLogic barfs up a UnsupportedOperationException, saying that “This class does not support SAAJ 1.1″! The thing is: it supports SAAJ 1.1 just fine, it just doesn’t do SAAJ 1.2. You know, J2EE 1.4 and all that.
You Can Help!
Obviously, I am missing WebSphere in this overview. Because I run OS X, I can’t install WebSphere. So if you want to be a Good Citizen too, you can run my little test program on WebSphere, or any other app server I have missed. Update: craig has given me the info for WebSphere, and I’ve updated the table accordingly. Thanks! I’m still interested in any other J2EE 1.4+ app servers I’ve missed, though.
Or if you don’t trust me, you can test it yourself. Just deploy the WAR, and let me know in a comment below.
If you don’t trust me at all, you can get the sources. It has a build.xml and all:
Let’s hope that this post solves the issues with SAAJ, because I Want To Believe.
Dan Diephouse said,
May 19, 2008 @ 4:36
Why do you want to believe? SAAJ is lame, I don’t see much of a reason to use it.
Stefan Tilkov said,
May 19, 2008 @ 9:33
Dan, it would be nice to be able to write some logic against a SOAP message without depending on any particular stack (even if it’s yours or Arjen’s)
Davanum Srinivas said,
May 19, 2008 @ 10:30
Stefan, One could use Apache Axiom (http://ws.apache.org/commons/axiom/)
/me ducks and hides from Dan
Arjen, If you are not already depressed, try running a xsl stylesheet on that DOM. Guaranteed fireworks.
– dims
Alef Arendsen said,
May 19, 2008 @ 11:31
Well, you know, other than lame, SAAJ is also boring, so another reason not to bother…
p.s. for those of you that do not master the Dutch language, ’saaj’ is almost the same as ’saai’, which translates to boring… okay… this is lame in itself, I know…
craig said,
May 19, 2008 @ 18:18
websphere 6.1
Testing MessageFactory com.ibm.ws.webservices.engine.soap.MessageFactoryImpl
Trying to create SOAPMessage: success.
Trying to get SOAPBody: success.
Trying to transform from Document to SOAPBody: success.
Trying to get SOAPBody: success.
Trying to transform from SOAPBody to OutputStream: success.
Trying to write SOAPMessage: success.
All looks well.
kevan said,
May 19, 2008 @ 19:51
Your test works on Jetty/CXF distributions of Geronimo (which uses the Sun SAAJ implementation), but not Tomcat/Axis2 distributions. So, we’re only half-bad…
I posted a few work-arounds in your Geronimo Jira.
–kevan
Dan Diephouse said,
May 19, 2008 @ 20:25
I guess I’m of the opinion that if one is going to do in memory xml tree manipulation you should use a less lame library like DOM4J or XOM There isn’t attachment support in DOM4J/XOM, but someone could easily hack up a library using one of these two things if they were motivated.
Yes there is AXIOM also. AXIOM is better than SAAJ/DOM, but still not a fan of the API. If you want streaming it does that, but if you’re doing in memory manipulation its slower & more memory intensive then DOM4J/XOM.
Arjen Poutsma said,
May 19, 2008 @ 22:44
@Dims,
Yes, Axiom is another option, and it can be used in Spring-WS as an alternative to SAAJ. It’s an extra dependency, though, and I don’ want to require it. I alsohad issues with Axiom. They are fixed now, which is a Good Thing.
@Craig
Thanks for the update!
@Dan
I agree: W3C DOM is lame, and dom4j, JDOM, and XOM are better. However, I had a couple of reasons to using SAAJ + W3C DOM:
That is not to say I don’t support these alternative dom apis for users, we do. I just don’t want to require them.