Search code examples
javaspringweb-servicessoapspring-ws

Add custom element to SOAP header in Spring WS


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


Solution

  • 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();
        }
    }