Spring WS Adding Detail to SoapFault Exception Handling
In this tutorial we show you how to add detail information to your exceptions. Spring WS enables easy translation of errors penetrated from business logic. Exceptions are automatically thrown up the chain with the use of EndpointExceptionResolvers
. Rather than expose the internals of your application by giving an exception and stack trace, you can handle the exceptions programatically and add an appropriate error code and description. This example shows you how to add detailed information to your SoapFault Exception.
Endpoint Throws Exception
This endpoint throws an exception. We provide a message and a custom ServiceFault
. This ServiceFault
has a code, describing what error has occurred with a detailed message what happened.
package com.memorynotfound.server;
import com.memorynotfound.beer.*;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
@Endpoint
public class BeerEndpoint {
public static final String NAMESPACE_URI = "https://memorynotfound.com/beer";
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getBeerRequest")
@ResponsePayload
public GetBeerResponse getBeer(@RequestPayload GetBeerRequest request) {
throw new ServiceFaultException("ERROR",new ServiceFault(
"NOT_FOUND", "Beer with id: " + request.getId() + " not found."));
}
}
ServiceFaultException Exception
The ServiceFaultException
extends from RuntimeException
, so we don’t need to catch or add it to our method signature. With the ServiceFault
object we can add some detailed information about the error that occurred.
package com.memorynotfound.server;
public class ServiceFaultException extends RuntimeException {
private ServiceFault serviceFault;
public ServiceFaultException(String message, ServiceFault serviceFault) {
super(message);
this.serviceFault = serviceFault;
}
public ServiceFaultException(String message, Throwable e, ServiceFault serviceFault) {
super(message, e);
this.serviceFault = serviceFault;
}
public ServiceFault getServiceFault() {
return serviceFault;
}
public void setServiceFault(ServiceFault serviceFault) {
this.serviceFault = serviceFault;
}
}
The ServiceFault
class is used to propagate the appropriate error code and description up the chain. Typically the code is used to map the exception on the client side and the message is just a description indicating the user/developer what exactly went wrong.
package com.memorynotfound.server;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ServiceFault", propOrder = {
"code",
"description"
})
public class ServiceFault {
private String code;
private String description;
public ServiceFault() {
}
public ServiceFault(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Enhancing Exception with Detailed Information
The DetailSoapFaultDefinitionExceptionResolver
extends from the SoapFaultMappingExceptionResolver
and is used for enhancing the SoapFault
with detailed information about the error that occurred. We can override the customizeFault
method to enhance the exception with detail information. We do this by calling the addFaultDetail
method of the SoapFault
class, and adding QName
indicating the element with the addFaultDetailElement
.
package com.memorynotfound.server;
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver;
import javax.xml.namespace.QName;
public class DetailSoapFaultDefinitionExceptionResolver extends SoapFaultMappingExceptionResolver {
private static final QName CODE = new QName("code");
private static final QName DESCRIPTION = new QName("description");
@Override
protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
logger.warn("Exception processed ", ex);
if (ex instanceof ServiceFaultException) {
ServiceFault serviceFault = ((ServiceFaultException) ex).getServiceFault();
SoapFaultDetail detail = fault.addFaultDetail();
detail.addFaultDetailElement(CODE).addText(serviceFault.getCode());
detail.addFaultDetailElement(DESCRIPTION).addText(serviceFault.getDescription());
}
}
}
Mapping/Configuring Detailed Exception
For the soap exceptions to be propagated properly we must register our SoapFaultMappingExceptionResolver
. We can define a default SoapFaultDefinition
. This default is used when the SoapFaultMappingExceptionResolver
does not have any appropriate mappings to handle the exception. We can map our Custom Exception by setting the mapping properties to the exception resolver. We do this by providing the fully qualified class name of the exception as the key and SoapFaultDefinition.SERVER
as the value. Finally, we have to specify an order because otherwise for some reason the mappings will not work.
package com.memorynotfound.server;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.soap.server.endpoint.SoapFaultDefinition;
import org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
@EnableWs
@Configuration
public class SoapServerConfig extends WsConfigurerAdapter {
@Bean
public SoapFaultMappingExceptionResolver exceptionResolver(){
SoapFaultMappingExceptionResolver exceptionResolver = new DetailSoapFaultDefinitionExceptionResolver();
SoapFaultDefinition faultDefinition = new SoapFaultDefinition();
faultDefinition.setFaultCode(SoapFaultDefinition.SERVER);
exceptionResolver.setDefaultFault(faultDefinition);
Properties errorMappings = new Properties();
errorMappings.setProperty(Exception.class.getName(), SoapFaultDefinition.SERVER.toString());
errorMappings.setProperty(ServiceFaultException.class.getName(), SoapFaultDefinition.SERVER.toString());
exceptionResolver.setExceptionMappings(errorMappings);
exceptionResolver.setOrder(1);
return exceptionResolver;
}
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext appContext){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(appContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
@Bean(name = "beers")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schema){
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BeersPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace(BeerEndpoint.NAMESPACE_URI);
wsdl11Definition.setSchema(schema);
return wsdl11Definition;
}
@Bean
public XsdSchema beersSchema(){
return new SimpleXsdSchema(new ClassPathResource("xsd/beers.xsd"));
}
}
Detailed Soap Exception Example
This is an example output of the soap exception. We can see that there is detailed information about the exception.
<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>ERROR</faultstring>
<detail>
<code>NOT_FOUND</code>
<description>Beer with id: 2 not found.</description>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
References
- SoapFault JavaDoc
- SoapFaultMappingExceptionResolver JavaDoc
- Spring WS API
- Spring WS Reference Documentation
Fantastic. Just worked by referring the above steps. Thanks for your efforts.
Thanks!
BTW, you can use detail.getResult to marshal fault info into detail element. For example, if you have FaultInfo class generated by JAXB from WSDL, you can use:
if (nonNull(faultInfo)) {
SoapFaultDetail detail = fault.addFaultDetail();
Result result = detail.getResult();
try {
JAXB_CTX.createMarshaller().marshal(faultInfo, result);
} catch (JAXBException e) {
LOGGER.error(“Error serializing [{}] fault info”, ex.getClass().getSimpleName(), e);
}
}
Looks great.
How can I call this from e.g. Chome Widzler? Then I need an URL with a WSDL .
How do you customize the fault code?
You can pass a
code
in theServiceFault
. When throwing an exception.Simply excellent!!!
Just so many thanks! i try lots of different implementations and nothing works.. until now!