Wednesday, March 31, 2010

Handling malformed SOAP messages gracefully in Spring WS.

 


I'm going to discuss how to modify Spring WS 1.5.9 to gracefully handle malformed SOAP message like this one :


<p style="MARGIN-BOTTOM: 0in"><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://namespace.du.jour"> <p style="MARGIN-BOTTOM: 0in"><soapenv:Header> <p style="MARGIN-BOTTOM: 0in"></soapenv:Header> <p style="MARGIN-BOTTOM: 0in"><soapenv:Body> <p style="MARGIN-BOTTOM: 0in"><web:publish> <p style="MARGIN-BOTTOM: 0in"><ArrayObject> <p style="MARGIN-BOTTOM: 0in"></web:publishIirRecord> <p style="MARGIN-BOTTOM: 0in"></soapenv:Body> <p style="MARGIN-BOTTOM: 0in"></soapenv:Envelope> <p style="MARGIN-BOTTOM: 0in">


Notice that the ArrayObject tag is not closed. Out of the box if you send Spring WS a malformed message you will get an HTTP 500 Error with an stack trace that looks like this:


<p style="MARGIN-BOTTOM: 0in">at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1231) <p style="MARGIN-BOTTOM: 0in">at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522) <p style="MARGIN-BOTTOM: 0in">at org.xml.sax.helpers.XMLFilterImpl.parse(XMLFilterImpl.java:333) <p style="MARGIN-BOTTOM: 0in">at org.apache.xalan.transformer.TransformerIdentityImpl.transform(TransformerIdentityImpl.java:484) <p style="MARGIN-BOTTOM: 0in">at com.sun.xml.internal.messaging.saaj.util.transform.EfficientStreamingTransformer.transform(EfficientStreamingTransformer.java:393) <p style="MARGIN-BOTTOM: 0in">at com.sun.xml.internal.messaging.saaj.soap.EnvelopeFactory.createEnvelope(EnvelopeFactory.java:102) <p style="MARGIN-BOTTOM: 0in">at com.sun.xml.internal.messaging.saaj.soap.ver1_1.SOAPPart1_1Impl.createEnvelopeFromSource(SOAPPart1_1Impl.java:70) <p style="MARGIN-BOTTOM: 0in">at com.sun.xml.internal.messaging.saaj.soap.SOAPPartImpl.getEnvelope(SOAPPartImpl.java:122) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.soap.saaj.support.SaajUtils.getSaajVersion(SaajUtils.java:159) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.soap.saaj.SaajSoapMessage.getImplementation(SaajSoapMessage.java:251) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.soap.saaj.SaajSoapMessage.<init>(SaajSoapMessage.java:84) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.soap.saaj.SaajSoapMessage.<init>(SaajSoapMessage.java:70) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.soap.saaj.SaajSoapMessageFactory.createWebServiceMessage(SaajSoapMessageFactory.java:168) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.transport.AbstractWebServiceConnection.receive(AbstractWebServiceConnection.java:90) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handleConnection(WebServiceMessageReceiverObjectSupport.java:86) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:57) <p style="MARGIN-BOTTOM: 0in">at org.springframework.ws.transport.http.MessageDispatcherServlet2.doService(MessageDispatcherServlet2.java:148) <p style="MARGIN-BOTTOM: 0in">at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571) <p style="MARGIN-BOTTOM: 0in">at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:511) <p style="MARGIN-BOTTOM: 0in">


In this post I'm going to show you how to have Spring-WS respond to such messages with a correctly formed SOAP fault that looks like this :


<p style="MARGIN-BOTTOM: 0in"><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <p style="MARGIN-BOTTOM: 0in"><SOAP-ENV:Header/> <p style="MARGIN-BOTTOM: 0in"><SOAP-ENV:Body> <p style="MARGIN-BOTTOM: 0in"><SOAP-ENV:Fault> <p style="MARGIN-BOTTOM: 0in"><faultcode>SOAP-ENV:Server</faultcode> <p style="MARGIN-BOTTOM: 0in"><faultstring xml:lang="en">Could not access envelope: Unable to create envelope from given source: ; nested exception is com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl: Unable to create envelope from given source:</faultstring> <p style="MARGIN-BOTTOM: 0in"><detail/> <p style="MARGIN-BOTTOM: 0in"></SOAP-ENV:Fault> <p style="MARGIN-BOTTOM: 0in"></SOAP-ENV:Body> <p style="MARGIN-BOTTOM: 0in"></SOAP-ENV:Envelope> <p style="MARGIN-BOTTOM: 0in">



Spring WS returns a HTTP 500 error when dealing with a malformed message because it does not catch XML parsing errors. So we need to find a point in the code where we can easily catch these exceptions, create a SOAP fault message and return that SOAP fault message. Which naturally, begs the question of what's the correct point in the stack to intercept these exceptions?


We want to catch the XML parsing exception as high up in the stack as possible, this will ensure that the solution works for as many configurations of Spring WS as possible. This would suggest that we catch the message in MessageDispatcherServlet2.doService. Which is the earliest point the code that Spring WS enters into the stack.


However, intercepting the exception in MessageDispatcherServlet2.doService has a major draw back, we don't have access to a SOAP message factory at that point in stack. We could inject a SOAP message factory into the MessageDispatcherServlet2 however this isn't ideal because we'd simply be duplicating functionality found in the WebServiceMessageReceiverHandlerAdapter. Given that WebServiceMessageReceiverHandlerAdapter.handle(...) is called by MessageDispatcherServlet2.doService(...), WebServiceMessageReceiverHandlerAdapter.handle(...) seems to be as a good a place as any to catch the exception.


So now we have to write a class that extends WebServiceMessageRecivierHandlerAdapter and override the handle method to catch and process XML parsing exceptions:


<p style="MARGIN-BOTTOM: 0in">package org.springframework.ws.transport.http; <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">import java.io.IOException; <p style="MARGIN-BOTTOM: 0in">import java.util.Locale; <p style="MARGIN-BOTTOM: 0in">import javax.servlet.http.HttpServletRequest; <p style="MARGIN-BOTTOM: 0in">import javax.servlet.http.HttpServletResponse; <p style="MARGIN-BOTTOM: 0in">import org.springframework.web.servlet.ModelAndView; <p style="MARGIN-BOTTOM: 0in">import org.springframework.web.servlet.ModelAndView; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.context.DefaultMessageContext; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.context.MessageContext; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.soap.SoapBody; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.soap.SoapBodyException; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.soap.SoapFault; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.soap.SoapFaultDetail; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.soap.SoapFaultException; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.soap.SoapMessage; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.transport.WebServiceConnection; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.transport.context.DefaultTransportContext; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.transport.context.TransportContext; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.transport.context.TransportContextHolder; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.transport.http.HttpServletConnection; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter; <p style="MARGIN-BOTTOM: 0in">import org.springframework.ws.transport.support.TransportUtils; <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">public class ErrorAwareWebServiceMessageReceiverHandlerAdapter extends WebServiceMessageReceiverHandlerAdapter { <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">@Override <p style="MARGIN-BOTTOM: 0in">public void afterPropertiesSet() throws Exception { <p style="MARGIN-BOTTOM: 0in">//do nothing <p style="MARGIN-BOTTOM: 0in">} <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">@Override <p style="MARGIN-BOTTOM: 0in">public ModelAndView handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { <p style="MARGIN-BOTTOM: 0in">try { <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">super.handle(httpServletRequest, httpServletResponse, handler); <p style="MARGIN-BOTTOM: 0in">} catch (Exception ex) { <p style="MARGIN-BOTTOM: 0in">createSoapFaultFromException(httpServletRequest, httpServletResponse, ex); <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">} <p style="MARGIN-BOTTOM: 0in">return null; <p style="MARGIN-BOTTOM: 0in">} <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">protected void createSoapFaultFromException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Exception ex) throws SoapBodyException, IOException, SoapFaultException { <p style="MARGIN-BOTTOM: 0in">WebServiceConnection connection = new HttpServletConnection(httpServletRequest, httpServletResponse); <p style="MARGIN-BOTTOM: 0in">TransportContext previousTransportContext = TransportContextHolder.getTransportContext(); <p style="MARGIN-BOTTOM: 0in">TransportContextHolder.setTransportContext(new DefaultTransportContext(connection)); <p style="MARGIN-BOTTOM: 0in">try { <p style="MARGIN-BOTTOM: 0in">MessageContext messageContext = new DefaultMessageContext(getMessageFactory().createWebServiceMessage(), getMessageFactory()); <p style="MARGIN-BOTTOM: 0in">SoapBody soapBody = ((SoapMessage) messageContext.getResponse()).getSoapBody(); <p style="MARGIN-BOTTOM: 0in">SoapFault fault = soapBody.addServerOrReceiverFault(ex.getLocalizedMessage(), Locale.ENGLISH); <p style="MARGIN-BOTTOM: 0in">SoapFaultDetail faultDetail = fault.addFaultDetail(); <p style="MARGIN-BOTTOM: 0in">connection.send(messageContext.getResponse()); <p style="MARGIN-BOTTOM: 0in">} finally { <p style="MARGIN-BOTTOM: 0in">TransportUtils.closeConnection(connection); <p style="MARGIN-BOTTOM: 0in">TransportContextHolder.setTransportContext(previousTransportContext); <p style="MARGIN-BOTTOM: 0in">} <p style="MARGIN-BOTTOM: 0in">} <p style="MARGIN-BOTTOM: 0in">} <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">


As you can see I overrode the handle function to call the super implementation and to catch and convert any exceptions that are thrown into SOAP messages. The only tricky thing about this class is that it is a member of the org.springframework.ws.transport.http package. This is because it uses HTTPServletConnection which is package protected. This also, unfortunately, means that if the the HTTPServletConnection class is modified in the future this class will cease to work.


Finally, you have to modify your application context to use the ErrorAwareWebServiceMessageReceiverHandlerAdapter rather than the default WebServiceMessageRecieverHandlerAdapter. You can do this by adding this line to you ApplicationContext.xml:


<p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in"><bean id="messageReceiverHandlerAdapter" class="org.springframework.ws.transport.http.ErrorAwareWebServiceMessageReceiverHandlerAdapter" /> <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in"> <p style="MARGIN-BOTTOM: 0in">

Posted via email from Karthik's posterous

No comments:

Post a Comment