I'm coding a web services client application using Spring WS with JAXB binding.
The services I'm using requiers authentication via wsse:security element in SOAP header (and one more custom header). I have all the necessary schemas compiled, including wsse.xsd
compiled to
org.xmlsoap.schemas.ws._2002._12.secext.Security
.
Yet I can't find a way to insert this or any other element into SOAP header. I know I can use interceptors or SoapActionCallback
s, but what they allow me to do is to manually construct the header and add it to header section via ((SaajSoapMessage)webServiceMessage).getSoapHeader().addHeaderElement(qName)
and so forth. But I don't want to build this header manually, as I have a corresponding class that I can easily marshall.
My question - is there a way to insert an object into SOAP header (or other part of the envelope) when invoking web service call in Spring WS? I used Apache CXF and Axis2 to consume web services and this was never an issue - frameworks just did it for me behind the scenes (usually via service stubs mechanism).
I've managed to solve this somehow, thanks @GPI for tips. I'm fairly new to the Spring WS and javax.xml.whatever, so I can't tell if this is either the right or elegant way of doing this, but it does exactly what I want.
This code adds custom header elements to <SOAP-ENV:Header>
, based on my objects generated from XSD schemas via JAXB. I have no idea how does the Transformer know where I want to put these elements, but it places them correctly in Header section.
public class HeaderComposingCallback implements WebServiceMessageCallback {
private final String action;
public HeaderComposingCallback( String action ) {
this.action = action;
}
@Override
public void doWithMessage(WebServiceMessage webServiceMessage) throws IOException, TransformerException {
SoapHeader soapHeader = ((SoapMessage)webServiceMessage).getSoapHeader();
try {
JAXBContext context = JAXBContext.newInstance( MessageHeader.class, Security.class );
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document securityDocument = builder.newDocument();
Document headerDocument = builder.newDocument();
Marshaller marshaller = context.createMarshaller();
marshaller.marshal( HeaderFactory.getHeader( action ), headerDocument );
marshaller.marshal( SecurityFactory.getSecurity(), securityDocument );
Transformer t = TransformerFactory.newInstance().newTransformer();
DOMSource headerSource = new DOMSource( headerDocument );
DOMSource securitySource = new DOMSource( securityDocument );
t.transform( headerSource, soapHeader.getResult() );
t.transform( securitySource, soapHeader.getResult() );
} catch (JAXBException | ParserConfigurationException e) {
e.printStackTrace();
}
}
}
Then I simply pass HeaderComposingCallback
object to marshalSendAndReceive()
method during service call.
EDIT (after Arjen's comment)
Arjen's right. What I wanted to do could be achieved simplier. Now my doWithMessage
method looks like this:
@Override
public void doWithMessage(WebServiceMessage webServiceMessage) throws IOException, TransformerException {
SoapHeader soapHeader = ((SoapMessage)webServiceMessage).getSoapHeader();
try {
JAXBContext context = JAXBContext.newInstance( MessageHeader.class, Security.class );
Marshaller marshaller = context.createMarshaller();
marshaller.marshal( header, soapHeader.getResult() );
marshaller.marshal( security, soapHeader.getResult() );
} catch (JAXBException e) {
e.printStackTrace();
}
}