4

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 SoapActionCallbacks, 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).

nachteil
  • 452
  • 1
  • 5
  • 17
  • 1
    Have you checked http://docs.spring.io/spring-ws/site/reference/html/security.html ? Last I recall, you define a `Wss4jSecurityInterceptor` (which uses the same WS Security lib as CXF, WSS4J), configure it with the proper action to do, and it does the work for you... – GPI Aug 13 '14 at 17:43
  • @GPI Thanks for your reply. Yes, I've seen that and that's not what I'm looking for (I edited my post and it's title). Security header was just an example, my service uses one more custom header (eb:MessageHeader). And again, I have corresponding schema compiled to Java class, but I'm not able to insert it into the message. `Wss4jSecurityInterceptor` is just another way of creating header from scratch, while I have fully composed object I want to insert into header. – nachteil Aug 14 '14 at 07:35
  • See : http://stackoverflow.com/questions/2274378/add-soapheader-to-org-springframework-ws-webservicemessage. Seems there is no easy way. Note that, since you compiled your schema to objects, there certainly is a way to have JAXB generate a DOM tree and insert it into the header : http://stackoverflow.com/questions/17078308/how-to-marshal-a-jaxb-object-to-org-w3c-dom-document – GPI Aug 14 '14 at 10:16

1 Answers1

4

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();
    }
}
nachteil
  • 452
  • 1
  • 5
  • 17
  • 2
    Not sure why you need to create the document here, why don't you simply marshal to the header's Result directly? As in: `marshaller.marshal(marshaller.marshal(HeaderFactory.getHeader(action), soapHeader.getResult())`. Also, you might want to consider wiring up a Spring OXM [`Jaxb2Marshaller`](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/oxm/jaxb/Jaxb2Marshaller.html) and inject that into the callback class. That would save you the hassle of creating the JAXB context and the marshaller. – Arjen Poutsma Aug 15 '14 at 08:26
  • marshaller.marshal( header, soapHeader.getResult() ); marshaller.marshal( security, soapHeader.getResult() ); header and security - are these the class instances? – Preety Singh Jul 20 '22 at 10:13