Search code examples
javacxfjax-wswinrm

Optional part in JAX-WS response message


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.

Solution

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