Saturday, May 31, 2014

Multiple SOAP headers in Apache Camel's Spring WS endpoint

    Life is all about abstraction. When you read this article you do not think about electrical impulses which travel through your brain between synapses. When you drive a car you do not need to know the internal details on how it works. By no doubt abstraction is good. But is it always that good?
I have been recently working with Apache Camel 2.12.0 and Spring WS 2.1.3. Every external dependency in our application is hidden behind Apache Camel. It is really good because it hides the fact that I want to send a JMS message or I want to hit a SOAP service. What is more, the retry policy and monitoring are consistent and they are exposed out of the box through Camel's MBeans.
However, one day I needed to place two headers within SOAP envelope:

<soap-env:Header>
<MyFirstHeader>...</MyFirstHeader>
<MySecondHeader>...</MySecondHeader>
</soap-env:Header>

The creators of Camel's Spring WS endpoint provided a way for SOAP header addition by means of CamelSpringWebserviceSoapHeader property:
...
@EndpointInject(uri = MY_SOAP_ENDPOINT)
private ProducerTemplate mySoapEndpoint;

public sendSoapWithMultipleHeaders() {
 mySoapEndpoint.requestBodyAndHeaders(whateverSoapPayload, createHeader());
}


private Map<String, Object> createHeader() {
        Map<String, Object> headers = new HashMap<>();
        headers.put("CamelSpringWebserviceSoapHeader", "<MyFirstHeader>...</MyFirstHeader>
                                                        <MySecondHeader>...</MySecondHeader>");
        return headers;
}
...
It is easy, isn't it? Yes, it would be easy if the above code did not produce a nasty exception. The exception comes from SpringWebserviceProducer - XML of the headers is not well formed - it has two root elements. OK, let's try again:
        headers.put("CamelSpringWebserviceSoapHeader", "<soap-env:Header><MyFirstHeader>...</MyFirstHeader>
                                                        <MySecondHeader>...</MySecondHeader></soap-env:Header>");
No more exceptions but produced header is:
<soap-env:Header>
  <soap-env:Header> 
    <MyFirstHeader>...</MyFirstHeader>
    <MySecondHeader>...</MySecondHeader> 
  </soap-env:Header> 
</soap-env:Header>
What a nightmare. Camel's Spring WS producer blindly copies the provided header into:
<soap-env:Header>
It performs default identity XSL transformation provided by javax.xml.transform.Transformer.
So I could not set my header with Camel's Spring WS endpoint. Spring WS was my last resort. After some analysis it appeared that there is ClientInterceptor (allows to modify SOAP message right before request sending and right after response receiving) within WebServiceTemplate which is used by Camel's Spring WS endpoint. I defined:
    @Bean
    public WebServiceTemplate customWebServiceTemplate()
    {
        WebServiceTemplate template = new WebServiceTemplate();
        template.setInterceptors(new ClientInterceptor[]{new MultipleSoapHeaderSetterInterceptor()});
        return template;
    }
and Camel's route:
spring-ws:https://mysoapservice.com?webServiceTemplate=#customWebServiceTemplate
MultipleSoapHeaderSetterInterceptor retrieves the header and stores it correctly. With proposed solution the header is wrong for some time but right before message sending it is tailored to be in the correct form.
The conclusion is that the abstraction is good when it works without any difficulties. You can see that I needed to analyze low level details of the libraries. When there are any problems the abstraction becomes leaky and you need to dig deeper. Pssst... The magic disappears.

No comments :

Post a Comment