Thursday, October 23, 2008

Bad Unmarshalling of a CXF object which results in a null object

The story in short:
I have a webservice which gathers some information from my DB, creates an object containing the info, then sends it to my client.
Sounds like a simple CXF webservice scene?
Well, for the last two days i'm fighting it; why?
Cause my client gets only a nullified object, it's quite frustrating...
So i caused some logs to appear and discovered that - The SOAP message actually contains the whole Object.
My client just can't build it (Unmarshal it) into the object it's supposed to be.
So, whose fault is it and how do I fix it?
Aegis' binding is my guess (not that it helps much), so that's the road i'm taking.
What did I find (OK, what did my boss find?) ?

Here are some interesting things that I learned in the process of getting the Peering WS client to connect up with the Peering WS:
  • The basic mechanism for serializing and deserializing the data of a web service is the databinding implementation.  I’ve been using Aegis, which is described here:
  • In the absence of explicit XML data-mapping files or annotations in the code, Aegis bases its understanding of how to express an object in XML on the JavaBean standard.  JavaBeans are Java classes that have public default constructors and public “getter” and “setter” methods for each aspect of an object’s state.  The latter means that if a class has a height field and a weight field, then it has to have public getHeight(), setHeight(), getWeight() and setWeight() methods.  If the field is Boolean, by-the-way, it can be represented by an is method, rather than a get.
  • So, a good deal of the work necessary to getting Aegis to work correctly with the data being transferred by the WS was just bringing the necessary classes into compliance with the JavaBean specification.  Here are some specific issues and how I got around them:
  • First there was the matter of just ensuring that each class had a public default constructor.  When one does this one must take note of any object initialization that takes place in non-default constructors, because maybe one has to make the default constructor does the same.
  • Inner classes have to be made public and static.  Why static?  Because when a non-static inner class is instantiated – pay attention here – there is an implicit first argument to the constructor:  A pointer to the surrounding object.  When that happens, Aegis doesn’t recognize the inner class as a JavaBean, because it can’t find a default constructor, since the constructor that you created with no parameters actually has one implicit parameter – the enclosing object.  Thus, static.
  • Several classes had get or is methods that weren’t simply returning the value of a field.  Given that, it didn’t make any sense to have a corresponding set method, and yet Aegis insisted on one, on the assumption that where there’s a get there must be a field.  At first I tried adding dummy setters, which worked but was ugly.  Then I tried renaming the getters, which also worked but was only slightly less ugly.  Ultimately I found that Aegis has provided for such cases with the @IgnoreProperty annotation, so I could leave the getters as they were and just mark them with this annotation so that Aegis would leave me alone.  To do this one must include a jar file like cxf-rt-databinding-aegis-2.1.2.jar, or perhaps one of the overall CXF jars.
  • I’ll reiterate that the getters and setters have to be public, because I wasted a couple of hours on one class before realizing that while two of the properties had public setters, their getters were protected!
  • Collection implementation classes should be used almost exclusively where a collection is being instantiated – elsewhere the corresponding interface should be used, as in Set vs. HashSet or TreeSet.  In particular, it seems that if Aegis sees a mismatch between interfaces and implementation classes (and I don’t remember anymore whether this was between the actual field type and the getter/setter, or between the getter and the setter, or something else), then it won’t recognize the class as a JavaBean.
That's all for today folks.

No comments: