Sunday, December 7, 2008

Modifying SOAP Headers with Spring Web Services



How do I send soap headers if I am using the Spring Web Services?:


It's amazing that such a common problem as
sending a and receiving customized SOAP headers
using the Spring Web Service Stack is so
poorly documented.


I had to research quite a bit before I found the right
answer to this question. In the hope that it might help
someone else, I am putting this down. I will discuss both the
options - the spring ws client sending soap headers and also
the server responding back with it's own soap headers.


Client -> Server:


Spring uses the WebServiceTemplate for the purposes sending
soap messages from the client to the server.
However, most people (including myself) use the
WebServiceGatewaySupport class to send web service requests.
The WebServiceGatewaySupport class has an embedded WebServiceTemplate
instance and will reduce some of the coding and configuration required
to shuttle messages across.


First a little bit of conceptual understanding:

  • You write a class A that extends
    org.springframework.ws.client.core.support.WebServiceGatewaySupport.
    This class will then act as your client proxy to send the
    messages.

  • In my case, I used the JAXB support infrastructure to
    marshall/unmarshall the requests/responses. Once I am
    ready with all the populated java objects I use
    JAXB to marshall the request. I end up with an XML
    request that I am ready to send to the server.
    There is a way to automatically ask spring to handle the
    jaxb marshalling as well. But I am using a very complex schema,
    which, due to a marshalling error inside the soap stack is not
    coming out properly.
    The sendSourceAndReceiveToResult method will take
    my message, put the soap structures
    around it and ship it off to the server.

  • Before sending it off to the server (and this is the
    critical piece to add the soap headers), I register a
    call back handler. This class will implement the interface
    org.springframework.ws.client.core.WebServiceMessageCallback.
    This interface has only one method doWithMessage. It is here
    that we can get a handle on the SOAP message, extract the
    header out and put in our stuff into it. Let's call this
    class B. I pass this class B as a paramter to the
    sendSourceAndReceiveResult method.

  • Inside the class B (the call back handler), I extract the soap
    header, insert a text that can then be shipped to the server.

  • The server does what is has to and comes back with the
    response which I then unmarshall as a nice little
    java object to poke around.




So here's the code



//First let me start of with the call back handler as our
//focus now is on the soap headers.
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.SoapHeader;
javax.xml.transform.TransformerFactory;
javax.xml.transform.Transformer;
import org.springframework.xml.transform.StringSource;
import javax.xml.transform.TransformerException;
import java.io.IOException;

public class B implements WebServiceMessageCallback {
public void doWithMessage(WebServiceMessage wsm)
throws IOException, TransformerException{
//We know we are dealing with soap here
SoapMessage soapMessage = (SoapMessage) wsm;
SoapHeader soapHeader = soapMessage.getSoapHeader();
//There you go, you got the soapHeader.
//Do whatever you want with it
//But wait a minute...
//how exactly should I add the header?

//Ok. Here's how.
//Lets say you want to add
//<header><a>I am A</>a</header>
String myAddition = " <header><a>I am A</>a</header>";
//We then have to use the java transformer
//API to create a Source tree from
//your myAddition string and move it onto the
//Result tree of the soapHeader.
//Here's the code
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer Transformer transformer = transformerFactory.newTransformer();
//Here we create a Source Tree
StringSource s = new StringSource(myAddition);
transformer.transform(s, soapHeader.getResult());
//That's it you are done.
}
}





Here's how we use the above class that we just created.



//I extend the
//org.springframework.ws.client.core.support.WebServiceGatewaySupport

public class A extends WebServiceGatewaySupport{
//My JAXB object that should be marshalled
private RequestObj reqObj;
//Lets assume sendMessage is
//called by some other piece of code that wants
//to send the webservice request over
public sendMessage() throws JAXBException{
//To keep things simple, lets assume for a moment
// that the jaxb reqObj is populated somehow
//by the time we get here
//Now initialize a JAXBContext
//Usually not done everytime because it's an expensive operation
//but for simplicity lets just do it here.
JAXBContext context = JAXBContext.newInstance(MyJAXBObject.class.getPackage().getName());
//Then use this context and create a JAXBResult and
JAXBSource jaxbSource = new JAXBSource(context, reqObj);
JAXBResult jaxbResult = new JAXBResult(context);

//Now create the call back handler instance
B b = new B();
boolean resultReturned =
getWebServiceTemplate().sendSourceAndReceiveToResult(jaxbSource,
reqCallBack, jaxbResult);
//resultReturned will be true if you successfully got a response back.
//handle the response in your code

}
}





Server -> Client:


To send customized SOAP headers
from the server back to the client, the only difference
is in where you add a similar piece of code
as shown above rather than the how.
On the server we have to implement an interface called
org.springframework.ws.soap.server.SoapEndpointInterceptor.
This interface has 4 methods. You can return a "true"
from the rest of the methods focus
attention on the handle response method.




import org.springframework.ws.soap.server.SoapEndpointInterceptor;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import org.springframework.xml.transform.StringSource;

public class EndpointInterceptor implements SoapEndpointInterceptor{
public boolean handleResponse(MessageContext msgContext, Object ep)
throws Exception {
SoapMessage soapMessage = (SoapMessage) msgContext.getResponse();
SoapHeader soapHeader = soapMessage.getSoapHeader();
//Add your header as in the above example
//transform the result
TransformerFactory transformerFactory =
TransformerFactory.newInstance();
Transformer Transformer transformer =
transformerFactory.newTransformer();
//Here we create a Source Tree
StringSource s = new StringSource(myAddition);
transformer.transform(s, soapHeader.getResult());
//You are set.
}

//Rest of the methods...just return true/false based on your
//requirements
}





The only remaining thing is to know how to
use this SoapEndpointInterceptor. This is
wired as an interceptor of the Soap endpoint


Declare your endpoint interceptor bean


<bean name="endpointInterceptor" class="EndpointInterceptor"/>

<bean
class="org.springframework.ws.server.endpoint.mapping.
PayloadRootQNameEndpointMapping">

<--Add all your endpoint mappings -->

<property name="interceptors">
<list>
<ref bean="endpointInterceptor"/>
</list>
</property>
</bean>



4 comments:

Mahesh Ediga said...

Great job..

Vinay said...

Hi Satish, Very nice post and very well described. I have three questions.

1) In Spring (SWS) (on Server), we generate the wsdl automatically and the OrderRequest, OrderResponse, OrderHeader have to be defined as a triplet for SWS to generate correct wsdl. Question is, this makes the header tags to be specific to request. Every operation ends up haveing a different custom header.
Is there a way to have a common header?

2) On the client, I noticed, you are placing raw xml in header. How would the name spaces get included in the outermost tags?

3) Can we not use JAXB objects and get XML from them, instead of hard coding header XML?

Thanks Again,

Vinay

Bhavishya said...

If i have namesapce prefix in header element eg ()then how can i convert it .

Unknown said...

org.springframework.ws.soap.SoapHeader should have an addChildElement(..) but what you have laid out here is very helpful, thanks so much, needed exactly this, not just adding value but value