Of ExceptionResolvers and XMLBeans

February 18th, 2009 by stevi | Filed under General, Java, spring

We’re using XMLBeans with Spring Web Services, and the set-up was quite easy. But the one issue I found frustrating was handling the SoapFault. The requirements in this case required including a SoapFaultDetail that contained a complex type. And while the XmlBeansMarshaller transparently handles transforming the incoming and outgoing SOAP requests, it didn’t magically include the type defined as the fault in the WSDL within the SoapFault message.

Digging through the tutorial and some online forum postings, I finally found a solution. The AbstractSoapFaultDefinitionExceptionResolver provides a protected method void customizeFault(Object endpoint, Exception ex, SoapFault fault) that subclasses can override to modify the SoapFault before it is returned. Since this method includes the exception as a parameter, I chose to create an exception that is thrown by my endpoints that includes my desired XmlBean, already populated, so I can just use SoapFault.getResult() to marshall the information into the details.

For simplicity’s sake, I chose to override the existing SoapFaultMappingExceptionResolver.

Below, ServiceFaultDocument is an XmlObject generated by XMLBeans from my schema:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"  xmlns:s1="http://stevideter.com/webservice">
    <xs:element name="ServiceFault" type="s1:ServiceFault"/>
    <xs:complexType name="ServiceFault">
        <xs:sequence>
            <xs:element name="errorCode" type="xs:string" minOccurs="0" maxOccurs="1"/>
	    <xs:element name="errorDescription" type="xs:string" minOccurs="0" maxOccurs="1"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

For my Exception class:

package com.stevideter.webservice;
 
import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;
 
import com.stevideter.webservice.ServiceFaultDocument;
/**
 * An exception that carries a ServiceFault document for inclusion in 
 * the SoapFault
 * @author stevi.deter
 *
 */
@SoapFault(faultCode = FaultCode.SERVER,faultStringOrReason="SERVICE-ERR",locale="en")
public class ServiceFaultException extends Exception {
 
	private static final long serialVersionUID = 1L;
	private ServiceFaultDocument faultMessage;
 
	public ServiceFaultException() {
		super("ServiceFaultException");
	}
 
	public ServiceFaultException(String s) {
		super(s);
	}
 
	public ServiceFaultException(String s, Throwable ex) {
		super(s, ex);
	}
 
	public ServiceFaultException(String s, Throwable ex, ServiceFaultDocument msg) {
		super(s,ex);
		setFaultMessage(msg);
 
	}
 
	public void setFaultMessage(ServiceFaultDocument msg) {
		faultMessage = msg;
	}
 
	public ServiceFaultDocument getFaultMessage() {
		return faultMessage;
	}
 
}

And finally my ExceptionResolver. Note that logger is ultimately inherited from AbstractEndpointExceptionResolver, which is why you don’t see it declared in the code displayed.

package com.stevideter.webservice.soap.server.endpoint;
 
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
 
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver;
import org.springframework.xml.transform.StringSource;
 
import com.stevideter.webservice.ServiceFaultException;
import com.stevideter.webservice.ServiceFaultDocument;
 
public class DetailSoapFaultDefinitionExceptionResolver extends
		SoapFaultMappingExceptionResolver {
 
	@Override
	protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
		ServiceFaultException msg = null;
		if (ex instanceof ServiceFaultException) {
			msg = (ServiceFaultException) ex;
		} else {
			msg = createFaultMessage(ex);
		}
		addServiceFaultDetail(msg, fault);
	}
 
	private void addServiceFaultDetail(ServiceFaultException msg, SoapFault fault)
			throws TransformerFactoryConfigurationError {
		Transformer trn;
		try {
			trn = TransformerFactory.newInstance().newTransformer();
			SoapFaultDetail faultDetail = fault.addFaultDetail();
			Result result = faultDetail.getResult();
			ServiceFaultDocument doc = msg.getFaultMessage();
			if (doc == null) {
				logger.error("ServiceFaultException thrown with no serviceFaultDocument!",msg);
			} else {
				trn.transform(new StringSource(doc.toString()), result);
			}
		} catch (TransformerException e) {
			logger.error("problem with XML transform: ", e);
		}
	}
 
	private ServiceFaultException createFaultMessage(Exception e) {
		ServiceFaultDocument faultDocument = ServiceFaultDocument.Factory.newInstance();
		ServiceFault fault = faultDocument.addNewServiceFault();
		fault.setErrorCode("SERVICE-ERR");
		fault.setErrorDescription(e.getMessage());
		ServiceFaultException faultMsg = new ServiceFaultException(e.getMessage(),e,faultDocument);
		return faultMsg;
	}
}

The final step is injecting my ExceptionResolver in my webservice’s servlet.xml; just showing the single bean definition here:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<!-- rest of web service bean def's elided -->
 
    <bean id="exceptionResolver"
	        class="com.stevideter.webservice.soap.server.endpoint.DetailSoapFaultDefinitionExceptionResolver">
        <property name="defaultFault" value="SERVER"/>
        <property name="exceptionMappings">
            <value>
	    com.stevideter.webservice.ServiceFaultException=SERVER,FaultMsg
            </value>
	</property>
    </bean>
</beans>

Now whether my Endpoints throw an Exception, the ExceptionResolver transforms it into the SoapFaultDetail, and the calling system get the results they desire:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <SOAP-ENV:Fault>
         <faultcode>SOAP-ENV:Server</faultcode>
         <faultstring xml:lang="en">FaultMsg</faultstring>
         <detail>
            <ServiceFault xmlns="http://stevideter.com/webservice">
               <errorCode xmlns="">SERVICE-ERR</errorCode>
               <errorDescription xmlns="">you can't do that!</errorDescription>
            </ServiceFault>
         </detail>
      </SOAP-ENV:Fault>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

One issue I discovered in developing this was that we were using Saxon 8.8, a version which apparently has a bug that throws an exception on the empty namespaces in the Service Fault elements during the transform. According to the information I found, upgrading to Saxon 8.9 fixes that; I went ahead and upgraded to Saxon 9.1.0.5 (current version as I write this) and the problem went away. Be sure to include saxon9.jar and saxon9-dom.jar if you go this route!

How have you used ExceptionResolvers in Web Services?

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

tag_iconTags: | | |

You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

12 Responses to “Of ExceptionResolvers and XMLBeans”.

  1. djeley :

    Nice post! Very useful. Thanks.

  2. Abhi :

    ServiceFaultDocument.toString() method is normal toString implementation

  3. JamesD :

    Thanks for the useful info. It’s so interesting

  4. srini :

    Thank you for sharing this! Very useful.

  5. Do you know, How can we exclude some of our endpoints, from this (Exception to SoapFaultDetail) mapping?

    I mean, How can we expilicitely include specific endpoints to this ExceptionResolving issue?

    By the way it is useful and great post. Thanx.

  6. Osman,
    Glad it was useful.

    Setting the desired endpoints in the mappedEndpoints property of the exceptionResolver should be what you need. Check out the API for AbstractEndpointExceptionResolver (parent of SoapFaultMappingExceptionResolver):

    http://static.springsource.org/spring-ws/sites/1.5/apidocs/org/springframework/ws/server/endpoint/AbstractEndpointExceptionResolver.html#setMappedEndpoints(java.util.Set)

  7. thx again.

  8. Joris Vleminckx :

    I solved it this way:

    created a class extending org.springframework.ws.soap.server.endpoint.SimpleSoapExceptionResolver
    and overriding the template method customizeFault() as follows to add a custom errorCode in the soapfaultdetail part:

    @Override
    protected void customizeFault(MessageContext messageContext, Object endpoint, Exception ex, SoapFault fault) {
    SoapFaultDetail soapFaultDetail = fault.addFaultDetail();
    Result result = soapFaultDetail.getResult();
    try {
    Transformer trn = TransformerFactory.newInstance().newTransformer();
    trn.transform(new StringSource(“” + ((MyException) ex).getErrorCode() + “”), result);
    } catch (TransformerException e) {
    e.printStackTrace();
    logger.error(“Error transformando: ” + e.getMessage());
    }
    }

    This leads to the following soapfault:

    SOAP-ENV:ServerMy error message24

  9. Joris Vleminckx :

    New try:

    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Server</faultcode><faultstring xml:lang="en">My error message</faultstring><detail><errorCode>24</errorCode></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>

  10. Rosh :

    Hey,
    Can you please post the implementation of ServiceFaultDocument.

  11. Rosh,
    In my specific case, ServiceFaultDocument is generated by XMLBeans from the given schema. You can give XMLBeans a try and see what it generates!

  12. Sri :

    Stevi
    if you have a servicefaultdocument sample that you can post that will be helpful, eventhough it’s generated by beans.
    this blog will be complete with that info and readers can implement any way they want.
    By the way, this is absolutely a GREAT posting !!
    Thanks a lot !

Leave a comment.

To leave a comment, please fill in the fields below.