In my last article - Spring Boot SOAP Client, we discussed about consuming SOAP web services through spring boot using WebServiceTemplate. Now, it's time to implement a custom exception handling mechanism while consuming SOAP web services through Spring.
WebServiceTemplate already handles exception in a perfect way but sometimes it is required to add our custom logic while executing the exception callbacks as most of the web services are designed in a such a way that exceptions are thrown in a custom fashion. For example, many web services tend to provide a status of 200 even during exceptions and there is a nested status code which actually tells about the exception. In that case, default exception handling provided by WebServiceTemplate is unable to handle that.
Hence, in this article we will try provide some custom mechanism to handle esception while consuming SOAP web services.
Exception Flow in WebServiceTemplate
As discussed here in the spring documentation, WebServiceTemplate uses the following algorithm for sending and receiving.
First a coonection is created using createConnection().
Next a request message is created and doWithMessage() is invoked on the request callback.
Next interceptors are executed if any and then send() is invoked on the connection. In case, the connection has an error, handleError() is invoked and execution stops.
For a succesfull connection, receive() is invoked on the connection to read the response message.
Once, we have the response hasFault() is invokded to determine the fault and if there is any interceptor present then ClientInterceptor.handleFault(MessageContext) is executed.
Now, if we want to handle any custom exceptions, we need to intercept at this particular point and write our custom logic to handle it.For that we need to imple,ent a custom interceptor and tell WebServiceTemplate to use our custom interceptor. We can also use this custom interceptor to add custom headers.
Custom Interceptor to Handle Exception
For a custom interceptor, we need to implement ClientInterceptor and override the abstract methods. Make sure these methods return true for marshalling and unmarshalling the request and response further. This is the preferred way for performing any custom operations in the SOAP request or response. In the next article, we will see how custom header can be added using these interceptor.
SoapClientInterceptor.java
public class SoapClientInterceptor implements ClientInterceptor {
@Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
return true;
}
@Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
return true;
}
@Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
//Your custom exception handling logic goes here.
return true;
}
@Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
}
}
Below is an implementation of handleFault() to log the fault string.
@Override public boolean handleFault(MessageContext messageContext) throws WebServiceClientException { logger.info("intercepted a fault."); WebServiceMessage message = messageContext.getResponse(); SaajSoapMessage saajSoapMessage = (SaajSoapMessage)message; SOAPMessage soapMessage = saajSoapMessage.getSaajMessage(); SOAPPart soapPart = soapMessage.getSOAPPart(); try { SOAPEnvelope soapEnvelope = soapPart.getEnvelope(); SOAPBody soapBody = soapEnvelope.getBody(); SOAPFault soapFault = soapBody.getFault(); logger.error(String.format("Error occurred while invoking web service - %s ",soapFault.getFaultString())); throw new RuntimeException(String.format("Error occurred while invoking web service - %s ",soapFault.getFaultString())); } catch (SOAPException e) { e.printStackTrace(); } return true; }
To register above class as an interceptor, we need little modifications in the bean config. Here we are using setInterceptors()
to add our custom interceptors.
@Configuration public class BeanConfig { @Bean public Jaxb2Marshaller marshaller() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setContextPath("com.devglan.springbootsoapclient.generated.blz"); return marshaller; } @Bean public BlzServiceAdapter soapConnector(Jaxb2Marshaller marshaller) { BlzServiceAdapter client = new BlzServiceAdapter(); client.setDefaultUri("http://www.thomas-bayer.com/axis2/services/BLZService"); client.setMarshaller(marshaller); client.setUnmarshaller(marshaller); ClientInterceptor[] interceptors = new ClientInterceptor[] {interceptor()}; client.setInterceptors(interceptors); return client; } @Bean public SoapClientInterceptor interceptor() { return new SoapClientInterceptor(); } }
Sometimes, we encounter situation to handle custom exceptions such as one in below xml. In such cases, we can use TransformerFactory to log the fault response.
SOAPFault soapFault = soapBody.getFault(); StringWriter sw = new StringWriter(); TransformerFactory.newInstance().newTransformer().transform( new DOMSource(soapFault), new StreamResult(sw)); String faultResponse = sw.toString(); System.out.println(faultResponse);
Exception Handling through WebServiceMessageCallback
We can also handle our custom exceptions by providing our own implementation of WebServiceMessageCallback. This is usefull when we have some nested excpetion code but the web service response status is 200. This implentation can also be used to alter the request, response or header. Below is the sample. You can visit my another article to add custom header in the SOAP request.
BlzServiceAdapter.javapublic class BlzServiceAdapter extends WebServiceGatewaySupport { public GetBankResponseType getBank(String url, Object requestPayload){ WebServiceTemplate webServiceTemplate = getWebServiceTemplate(); JAXBElement<GetBankResponseType> res = (JAXBElement<GetBankResponseType>) webServiceTemplate.sendAndReceive(url, new WebServiceMessageCallback(){ @Override public void doWithMessage(final WebServiceMessage request) throws IOException { if (requestPayload != null) { if (webServiceTemplate.getMarshaller() == null) { throw new IllegalStateException("No marshaller registered. Check configuration of WebServiceTemplate."); } MarshallingUtils.marshal(webServiceTemplate.getMarshaller(), requestPayload, request); } } }, new WebServiceMessageExtractor<Object>() { @Override public Object extractData(final WebServiceMessage response) throws IOException { if (webServiceTemplate.getUnmarshaller() == null) { throw new IllegalStateException("No unmarshaller registered. Check configuration of WebServiceTemplate."); } if (hasFault(response)) { throw new SoapFaultClientException((SoapMessage) response); } else { return MarshallingUtils.unmarshal(webServiceTemplate.getUnmarshaller(), response); } } protected boolean hasFault(final WebServiceMessage response) { if (response instanceof FaultAwareWebServiceMessage) { FaultAwareWebServiceMessage faultMessage = (FaultAwareWebServiceMessage) response; return faultMessage.hasFault(); } return false; } }); return res.getValue(); } }
One thing to note here, the above implementation can also be achieved vis interceptors that we defined above. WebServiceMessageCallback is mostly used whenever we want to perform any specific actions for a particular web service call.
Conclusion
In this article, we discussed about exception handling while consuming SOAP web services using Spring SOAP client. We created our custom interceptor to define our custom exception handling logic.