Add custom element to SOAP header in Spring WS
I am coding a web services client application using Spring WS with JAXB binding. Services where I am using requiers authentication via wsse: security element in SOAP header (and another custom header). I have all the schematics I need, including those wsse.xsd
compiled for
org.xmlsoap.schemas.ws._2002._12.secext.Security
.
However, I cannot find a way to insert this or any other element into the SOAP header. I know I can use interceptors or SoapActionCallback
s, but what they allow me to do is manually create a header and add it to the header section via ((SaajSoapMessage)webServiceMessage).getSoapHeader().addHeaderElement(qName)
and so on. But I don't want to create this header manually as I have a corresponding class that I can easily customize.
My question is, is there a way to inject an entity into a SOAP header (or other part of an envelope) when invoking a web service in Spring WS? I have used Apache CXF and Axis2 to consume web services and this has never been a problem - frameworks just did it for me behind the scenes (usually via a service stub mechanism).
source to share
I managed to solve this somehow, thanks @GPI for the advice. I'm new to Spring WS and javax.xml.whatever, so I can't tell if this is the correct or elegant way to do it, 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 the Transformer knows where I want to put these elements, but it puts them in the header section correctly.
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 just pass the object to the HeaderComposingCallback
method marshalSendAndReceive()
during the service call.
EDIT (after Arjen's comment)
Arjen is on the right. What I wanted to do could have been easier. Now my method doWithMessage
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();
}
}
source to share