Optional part in JAX-WS response message

602 views Asked by At

TL;DR: How can I have an optional <part> in the response <message> for a wsdl service.

I am:

  • targeting an existing service
  • writing a client to talk to the service
  • implemented the service definition as a Java interface

The problem: Depending on the version of the service there could be an additional element in the response Body element.

With the following service definition I can target v1 of the service:

  <message name="CreateResponse">
    <part name="ResourceCreated" element="ns7:ResourceCreated" />
  </message>

And this one works with v2 of the service:

  <message name="CreateResponse">
    <part name="ResourceCreated" element="ns7:ResourceCreated" />
    <part name="Shell" element="ns8:Shell" />
  </message>

Question: How can I target both versions with the same service definition? I don't really need the second element so just ignoring it would be fine.

Details:

  • The service is the Windows Remote Management Service.
  • I am writing a Java client to target it.
  • The code is available at https://github.com/cloudsoft/winrm4j
  • When talking to Windows 7 the Create response contains a single ResponseCreated element.
  • When talking to Windows 2012 the Create response contains two elements - ResponseCreated and Shell.
1

There are 1 answers

0
Svet On BEST ANSWER

I don't believe there's a clean solution to the problem - it's not possible to mark <part> elements in the <message> as optional.

The workaround in this case is to strip the additional element before it gets to the JAX-WS parser. This can be done by creating a CXF interceptor or a JAX-WS handler and manipulating the response XML.

Create the JAX-WS handler:

public class StripHandler implements SOAPHandler<SOAPMessageContext> {

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        boolean isResponse = Boolean.FALSE.equals(context.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY));
        if (isResponse) {
            QName action = (QName) context.get(SOAPMessageContext.WSDL_OPERATION);
            if ("Create".equals(action.getLocalPart())) {
                Iterator<?> childIter = getBodyChildren(context);
                while(childIter.hasNext()) {
                    SOAPElement el = (SOAPElement) childIter.next();
                    if ("Shell".equals(el.getLocalName())) {
                        childIter.remove();
                    }
                }
            }
        }
        return true;
    }

    private Iterator<?> getBodyChildren(SOAPMessageContext context) {
        try {
            SOAPEnvelope envelope = context.getMessage().getSOAPPart().getEnvelope();
            SOAPBody body = envelope.getBody();
            return body.getChildElements();
        } catch (SOAPException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {return true;}
    @Override
    public void close(MessageContext context) {}
    @Override
    public Set<QName> getHeaders() {return null;}

}

Register the JAX-WS handler:

Declaratively

Create handlers.xml file alongside the service interface:

<handler-chains xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee">
    <handler-chain>
        <handler>
            <handler-name>StripHandlerr</handler-name>
            <handler-class>fully.qualified.StripHandler</handler-class>
        </handler>
    </handler-chain>
</handler-chains>

And attach it to the service definition using the annotation @HandlerChain(file = "handlers.xml")

Programmatically

That's an alternative approach which doesn't require the extra xml file.

((BindingProvider)service).getBinding().setHandlerChain(Arrays.<Handler>asList(new StripHandler());