czwartek, 10 października 2013

Handling soap fault in spring ws

Recently I was working on a integration with some other project. Our applications communicate with web services with WS-Security - each message is signed and encrypted. I was having some issues with proper handling of the soap fault in case of errors.
So, my project uses spring-ws for exposing web services, as well as calling web services exposed by the other application. I use xjc for generating client classes directly from the wsdl of the target application. It looked like this
WebServiceTemplate wsTemplate = getWsTemplate();
Request xmlRequest = objectFactory.createRequest();
JAXBElement<Request> requestPayload = objectFactory.createRequest(xmlRequest);
JAXBElement<Response> xmlResponse = (JAXBElement<Response>)wsTemplate.marshalSendAndReceive(requestPayload);
When the server responded with SOAP Fault I was getting an unmarshalling exception... It was strange for me and I didn't know why. I started to debug, and noticed that spring uses a HTTPConnection under the hood for verifying the HTTP response status. If it is 500, it correctly detects that the response is a SOAP fault. That was not the case in my scenario, as the web service I was calling was returning a HTTP 200... Fortunatelly, spring-ws has something for that the checkConnectionForFault parameter. So I thought I was saved. But no - maybe it would be enough for a standard case, but I had my message encrypted by WS-Security rules, so it structure could not be validated for soap fault standard untill it was deciphered. So I needed to delay this check to the last possible moment. This is what I came up with
WebServiceTemplate wsTemplate = getWsTemplate();
Request xmlRequest = objectFactory.createRequest();
JAXBElement<Request > requestPayload = objectFactory.createRequest(xmlRequest);
JAXBElement<Response> xmlResponse = (JAXBElement<Response>)wsTemplate.sendAndReceive(wsTemplate.getDefaultUri(), new WebServiceMessageCallback() {

   @Override
   public void doWithMessage(final WebServiceMessage request) throws IOException, TransformerException {
    if (requestPayload != null) {
     if (marshaller == null) {
      throw new IllegalStateException("No marshaller registered. Check configuration of WebServiceTemplate.");
     }
     MarshallingUtils.marshal(marshaller, requestPayload, request);
    }
   }
  }, new WebServiceMessageExtractor<Object>() {

   @Override
   public Object extractData(final WebServiceMessage response) throws IOException {
    if (unmarshaller == null) {
     throw new IllegalStateException("No unmarshaller registered. Check configuration of WebServiceTemplate.");
    }
    if (hasFault(response)) {
     throw new SoapFaultClientException((SoapMessage) response);
    } else {
     return MarshallingUtils.unmarshal(unmarshaller, response);
    }
   }

   protected boolean hasFault(final WebServiceMessage response) throws IOException {
    if (response instanceof FaultAwareWebServiceMessage) {
     FaultAwareWebServiceMessage faultMessage = (FaultAwareWebServiceMessage) response;
     return faultMessage.hasFault();
    }
    return false;
   }
  });
This is mostly the same that is done by spring when you call just marshalSendAndReceive method. But this way, I could influence the extractData in the WebServiceMessageExtractor to add additional hasFault check. At this point, my message is already decrypted, so I can validate if it contains soap fault.

1 komentarz: