Search code examples
web-servicessoapapache-camelcxfws-addressing

How do I set the WS-Addressing MessageId header when using CXF with Apache Camel?


I'm invoking a web service that requires WS-Addressing SOAP headers. I'm using Apache Camel with CXF to invoke the web service. When I configure the CXF endpoint with the web service's WSDL, it's smart enough to automatically add WS-Adressing SOAP headers, but I need to set a custom MessageId.

Here is the message that is currently being sent:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
    <soap:Header>
        <ws:international xmlns:ws="http://www.w3.org/2005/09/ws-i18n">
            <ws:locale xmlns:ws="http://www.w3.org/2005/09/ws-i18n">en_CA</ws:locale>
        </ws:international>
        <fram:user wsa:IsReferenceParameter="true" xmlns:fram="http://wsbo.webservice.ephs.pdc.ibm.com/Framework/" xmlns:wsa="http://www.w3.org/2005/08/addressing">BESTSystem</fram:user>
        <Action soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">http://webservice.ephs.pdc.ibm.com/Client/QueryHumanSubjects</Action>
        <MessageID soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:945cfd10-9fd2-48f9-80b4-ac1b9f3293c6</MessageID>
        <To soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">https://panweb5.panorama.gov.bc.ca:8081/ClientWebServicesWeb/ClientProvider</To>
        <ReplyTo soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">
            <Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
        </ReplyTo>
    </soap:Header>
    <soap:Body>
        <ns2:queryHumanSubjectsRequest xmlns:ns2="http://wsbo.webservice.ephs.pdc.ibm.com/Client/" xmlns:ns3="http://wsbo.webservice.ephs.pdc.ibm.com/FamilyHealth/">
            <!-- stuff -->
        </ns2:queryHumanSubjectsRequest>
    </soap:Body>
</soap:Envelope>

As you can see, the MessageId value is "urn:uuid:945cfd10-9fd2-48f9-80b4-ac1b9f3293c6". I need to set a custom value.

I tried adding the MessageId header they way I add the other headers like "international" and "user", but some part of the framework overrides the value.

// Note this doesn't work! Something overrides the value. It works for other headers.
@Override
public void process(Exchange exchange) throws Exception {

    Message in = exchange.getIn();
    List<SoapHeader> headers = CastUtils.cast((List<?>) in.getHeader(Header.HEADER_LIST));

    SOAPFactory sf = SOAPFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
    QName MESSAGE_ID_HEADER = new QName("http://www.w3.org/2005/08/addressing", "MessageID", "wsa");
    SOAPElement messageId = sf.createElement(MESSAGE_ID_HEADER);
    messageId.setTextContent("customValue");
    SoapHeader soapHeader = new SoapHeader(MESSAGE_ID_HEADER, messageId);
    headers.add(soapHeader);
}

The CXF website has some documentation on how to set WS-Addressing headers, but I don't see how to apply it to Apache Camel. The Apache Camel CXF documentation doesn't specifically mention WS-Addressing either.


Solution

  • The documentation links you posted actually do have the information you need, although it's not immediately obvious how to apply it to Camel.

    The CXF documentation says that:

    The CXF org.apache.cxf.ws.addressing.impl.AddressingPropertiesImpl object can be used to control many aspects of WS-Addressing including the Reply-To:

    AddressingProperties maps = new AddressingPropertiesImpl();
    EndpointReferenceType ref = new EndpointReferenceType();
    AttributedURIType add = new AttributedURIType();
    add.setValue("http://localhost:9090/decoupled_endpoint");
    ref.setAddress(add);
    maps.setReplyTo(ref);
    maps.setFaultTo(ref);
    ((BindingProvider)port).getRequestContext()
            .put("javax.xml.ws.addressing.context", maps);
    

    Note that it sets the addressing properties on the "RequestContext".

    The Apache Camel documentation says that:

    How to propagate a camel-cxf endpoint’s request and response context

    CXF client API provides a way to invoke the operation with request and response context. If you are using a camel-cxf endpoint producer to invoke the outside web service, you can set the request context and get response context with the following code:

    CxfExchange exchange = (CxfExchange)template.send(getJaxwsEndpointUri(), new Processor() {
        public void process(final Exchange exchange) {
            final List<String> params = new ArrayList<String>();
            params.add(TEST_MESSAGE);
            // Set the request context to the inMessage
            Map<String, Object> requestContext = new HashMap<String, Object>();
            requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, JAXWS_SERVER_ADDRESS);
            exchange.getIn().setBody(params);
            exchange.getIn().setHeader(Client.REQUEST_CONTEXT , requestContext);
            exchange.getIn().setHeader(CxfConstants.OPERATION_NAME, GREET_ME_OPERATION);
        }
    });
    

    The above example has some stuff we don't need, but the important thing is that it shows us how to set the CXF Request Context.

    Put them together and you get:

    @Override
    public void process(Exchange exchange) throws Exception {
        AttributedURIType messageIDAttr = new AttributedURIType();
        messageIDAttr.setValue("customValue");
    
        AddressingProperties maps = new AddressingProperties();
        maps.setMessageID(messageIDAttr);
    
        Map<String, Object> requestContext = new HashMap<>();
        requestContext.put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES, maps);
        exchange.getIn().setHeader(Client.REQUEST_CONTEXT, requestContext);
    }
    
    // org.apache.cxf.ws.addressing.JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES = "javax.xml.ws.addressing.context"
    // org.apache.cxf.endpoint.Client.REQUEST_CONTEXT = "RequestContext"
    
    

    Warning: In my route, I invoke multiple different web services sequentially. I discovered that after setting the RequestContext as shown above, Camel started using the same RequestContext for all web services, which resulted in an error: "A header representing a Message Addressing Property is not valid and the message cannot be processed". This is because the incorrect "Action" header was used for all web service invocations after the first.

    I traced this back to Apache Camel using a "RequestContext" Exchange property, separate from the header we set, which apparently takes priority over the header. If I remove this property prior to calling subsequent web services, CXF automatically fills in the correct Action header.