Thursday, March 27, 2008

SOAP faults with spring ws and JAXB 2.0

When migrating existing web services from Axis2 to spring web services (1.0.3) and JAXB (2.0), I ran into three problems related to SOAP faults. I found some hints towards a solution on the net, but none of them were really exhaustive. In this blog post, I give a step by step overview of how we solved these three issues.

Let's start by sketching the context in which we encountered the problems. We already had a contract first approach (actually, our WSDL was generated using an AndroMDA cartridge), so we could start from an existing WSDL. We only had to move the types into a separate XSD file (for JAXB, see further). Our WSDL defines a lot of operations with input, output and fault messages. With axis2, these faults were represented as java exceptions (inheriting from java.lang.Exception). When serializing these java classes back to XML, they were represented as SOAP faults with as detail element of the soap fault a serialized version of the java exception (as defined in the XML schema). This is an example of a SOAP fault message sent to the client:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
     <soapenv:Fault>
        <faultcode>soapenv:Client</faultcode>
        <faultstring>FooException: null</faultstring>
        <detail>
           <ns:FooException xsi:type="ns:FooException" xmlns:ns="http://our.namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
              <ns:customField>0</ns:customField>
           </ns:FooException>
        </detail>
     </soapenv:Fault>
  </soapenv:Body>
</soapenv:Envelope>
The first problem when migrating to spring web services and JAXB, is that JAXB generates java types from the XSD rather then from the WSDL. The types of the fault messages are off course known as an XSD element, but the XSD has no information to distinguish them from other types (input and output messages). Typically all input messages end with "Request", all output messages with "Response" and all exceptions with "Exception", but JAXB doesn't do anything special with these naming conventions. Apart from these names, there is thus nothing special about the java types that JAXB generates for them. For exceptions, this leads to the confusing situation of having something named FooException that doesn't inherit from java.lang.Exception (I'll call them "fake" exceptions from now on).

This means that in our AbstractMarshallingPayloadEndpoint implementation (which is what you use if you want to implement web services with spring and a marshalling technology like JAXB), we can't throw these exceptions (as they are not throwable).

The second problem is that these fake exceptions don't have the JAXB @XmlRootElement annotation on them. This means that they can not directly be serialized: you first have to create a valid JAXBElement for them using the ObjectFactory that is generated by JAXB, something like
final JAXBElement element = new ObjectFactory().createFooException((FooException) exception);
The third problem is that spring's built in support for dealing with exceptions doesn't serialize the exception in the detail part of a SOAP fault. By default, spring only renders a fault code and error message in the SOAP fault.

Serializing exceptions as SOAP fault detail messages

Let's forget about the first and second problems for now and assume all exceptions properly inherit from java.lang.Exception and have the JAXB @XmlRootElement annotation on them. By writing a custom SoapFaultMappingExceptionResolver (or directly implementing EndpointExceptionResolver if you want to), you can customize the way the SOAP fault is generated. This is how the customizeFault method could be implemented:
@Override
protected void customizeFault(
 final Object endpoint, 
 final Exception exception, 
 final SoapFault fault)
{
 super.customizeFault(endpoint, exception, fault);

 // get the marshaller
 AbstractMarshallingPayloadEndpoint marshallingendEndpoint = (AbstractMarshallingPayloadEndpoint) endpoint;

 // get the result inside the fault detail to marshal to
 Result result = fault.addFaultDetail().getResult();

 // marshal
 try
 {
   marshallingendEndpoint.getMarshaller().marshal(exception, result);
 } catch (IOException e)
 {
   throw new RuntimeException(e);
 }
}
Note how we use the marshaller from the given endpoint to marshal the exception into the detail of the SOAP fault (represented using the Result interface).

Using JAXB's ObjectFactory to solve the second problem

As explained already, the second of the three problems forces us to use JAXB's ObjectFactory for the exceptions. This means that, in the code example above, we can't directly pass the exception to the marshaller. We first have to create a JAXBElement from the exception. This forces us to known the exact type of the exception, and thus requires ugly casting, for example:
@Override
protected void customizeFault(
 final Object endpoint,
 final Exception exception,
 final SoapFault fault)
{
 super.customizeFault(endpoint, exception, fault);

 // get the marshaller
 AbstractMarshallingPayloadEndpoint marshallingendEndpoint = (AbstractMarshallingPayloadEndpoint) endpoint;

 // get the result inside the fault detail to marshal to
 Result result = fault.addFaultDetail().getResult();

 // create the corresponding jaxb element
 final JAXBElement element;
 if (exception instanceof FooException)
 {
  element = new ObjectFactory().createFooException((FooException) exception);
 } else if (...)
 {
  // else if required for all possible exceptions
 }

 // marshal
 try
 {
   marshallingendEndpoint.getMarshaller().marshal(exception, result);
 } catch (IOException e)
 {
  throw new RuntimeException(e);
 }
}
As you can see, this code is no longer scalable. For every new exception, you have to add code.

Wrapping fake exceptions in a real exception

The last problem to solve is that the fake exceptions are not really java exceptions. We've looked into possibilities to customize the JAXB generation of java types in such a way that everything that ends with "Exception" is actually a real exception, but that didn't seem to be possible.

We ended up writing a solution that works, although I must say it is not very elegant. We've created a custom exception to wrap the "fake" exceptions generated by JAXB (called it UnmarshalledExceptionWrapperException). The wrapper wraps instances of type Object and verifies that their class names end with "Exception" (that really seems to be the best thing we can do). The custom SoapFaultMappingExceptionResolver now has to unwrap these fake exceptions and build a valid JAXB element from them:
@Override
protected void customizeFault(
 final Object endpoint,
 final Exception exception,
 final SoapFault fault)
{
 super.customizeFault(endpoint, exception, fault);

 // get the wrapper
 UnmarshalledExceptionWrapperException exception = (UnmarshalledExceptionWrapperException) ex;

 // unwrap the exception
 Object unwrappedException = exception.getWrappedException();

 // get the marshaller
 AbstractMarshallingPayloadEndpoint 
  marshallingendEndpoint = (AbstractMarshallingPayloadEndpoint) endpoint;

 // get the result inside the fault detail to marshal to
 Result result = fault.addFaultDetail().getResult();

 // create the corresponding jaxb element
 final JAXBElement element;
 if (unwrappedException instanceof FooException)
 {
  element = new ObjectFactory().createFooException((FooException) unwrappedException);
 } else if (...)
 {
  // else if required for all possible exceptions
 }

 // marshal
 try
 {
   marshallingendEndpoint.getMarshaller().marshal(exception, result);
 } catch (IOException e)
 {
  throw new RuntimeException(e);
 }
}

Conclusion

We ended up with a working spring web services implementation to mimic the behavior of Axis2 in respect to SOAP faults, although it is not the kind of java code one can be very proud of. The major problem seems to be that code modification is required when adding, removing or changing the available fault messages in the WSDL. We would have a far more elegant solution if we could get JAXB 2.0 to generate real exceptions with an @XmlRootElement annotation on them.

6 comments:

Wim said...

really helpful, thx a lot

Henk said...

Thanks! It solved my problem hours before deadline

Anonymous said...

I'm getting following exception.

java.lang.ClassCastException: org.springframework.ws.server.endpoint.MethodEndpoint cannot be cast to org.springframework.ws.server.endpoint.AbstractMarshallingPayloadEndpoint

anyone can help me please.

Justin M said...

That helps, but i'm not clear about what happens in UnmarshalledExceptionWrapperException. Can you post the code for it?

Thanks.

Unknown said...


Also struggling with this and it's helping, did your spring generated WSDL (based on an XSD) contain the faults?

I'm trying to port so I get WSDL compatibility from an IBM product.

For manipulating the XMLRootElement last part could you potentially add some binding instructions / annotations in xjb? (About to try, will let you know how I go)

http://stackoverflow.com/questions/8702559/adding-an-annotation-to-a-jaxb-binding-class-from-a-schema

Also, the AbstractMarshallingPayloadEndpoint is now deprecated - do you know of an updated solution with the latest Spring-ws framework. Would be really good if Spring-io gave an example of this or some better options as so far all solutions need a lot of custom code;

marcus said...

It's 2017 and this was really helpful for us. Thanks for publishing it back then!