Monday, November 8, 2010

CXF backwards compatibility: adding an attribute on the server causes an UnmarshalException on the client

When updating CXF (and therefore JAXB) from cxf-bundle 2.2.3 to 2.2.10 we ran into a problem where our client and server became more coupled than we had in mind. Specifically, adding an attribute to a server entity would cause an exception if the object was returned to a client that wasn't expecting that attribute (eg was using the previous definition of the server interface):

javax.xml.ws.soap.SOAPFaultException: Unmarshalling Error: unexpected element (uri:"", local:"myNewProperty"). Expected elements are <...list of existing properties...> 
 at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:146)
  ...excitingly lengthy callstack...
Caused by: javax.xml.bind.UnmarshalException
 - with linked exception:
[javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"myNewProperty"). Expected elements are <...list of existing properties...>]
 at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.handleStreamException(UnmarshallerImpl.java:425)
 at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:362)
 at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:339)
 at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:755)
 at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:624)
...excitingly lengthy callstack...

This makes it a bit hard to update our server without updating the client in lockstep. We specifically wanted to be able to add attributes to entities without breaking our clients to avoid tying teams together too closely. Luckily it turns out that this problem can be ignored by providing a custom ValidationEventHandler as of CXF 2.2.4 using new properties added for CXF-2455. Unfortunately the event is not very distinct so we have to either follow worst-practices and examine the message to decide if this is the specific error we want to suppress or simply suppress all errors:
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;


public class IgnoreUnexpectedElementsHandler implements ValidationEventHandler {
 @Override
 public boolean handleEvent(ValidationEvent event) {
  //true: keep going. In this case we only want to continue for the error we're trying to hide.
  return event.getMessage().startsWith("unexpected element (");
 }
}
Note that for this example we're assuming our handler goes into the default package which isn't a particularly good idea in reality. Once we have created our validation event handler we need to tell our client to use it. In our case in the Spring configuration of the client:
<jaxws:client address="${a.property.for.my.services.url}" id="myServiceClient" serviceclass="com.something.myService">
  <jaxws:properties>
   <entry key="jaxb-validation-event-handler" value-ref="ignoreUnexpectedElementsHandler">
  </entry></jaxws:properties>
 </jaxws:client>
Kind of lame to have to do really.

2 comments:

icyitscold said...

It can be done a little bit more easily by setting the "set-jaxb-validation-event-handler" flag to "false"

http://mail-archives.apache.org/mod_mbox/cxf-users/201001.mbox/%3C201001291547.56047.dkulp@apache.org%3E

VladTs said...

Guys thx for solution!!

Post a Comment