Search code examples
javasoapjax-ws

JAX-WS Client won't accept blank namespace in SOAP Response


I'm trying to pull data from a webservice using JAX-WS and code generated by wsimport. I'm able to send the request and get a response from the server, but JAX-WS is throwing an exception when it tries to read the response because no elements in the response body have a declared namespace.

Exception in thread "main" com.sun.xml.internal.ws.streaming.XMLStreamReaderException: unexpected XML tag. expected: {http://www.theserver.com/cmdb_rel_ci}getRecordsResponse but found: {null}getRecordsResponse

WSDL Excerpt:

<wsdl:definitions xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:tns="http://www.theserver.com/cmdb_rel_ci" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sncns="http://www.theserver.com" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://www.theserver.com">
  <wsdl:types>
    <xsd:schema elementFormDefault="unqualified" targetNamespace="http://www.theserver.com/cmdb_rel_ci">
      <xsd:element name="getRecords">
        <xsd:complexType>
          <xsd:sequence>
            <!-- Request arguments -->
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="getRecordsResponse">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element maxOccurs="unbounded" minOccurs="0" name="getRecordsResult">
              <xsd:complexType>
                <xsd:sequence>
                  <!-- Response Fields -->
                </xsd:sequence>
              </xsd:complexType>
            </xsd:element>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="getRecordsSoapIn">
    <wsdl:part name="cmdb_rel_ci" element="tns:getRecords"></wsdl:part>
  </wsdl:message>
  <wsdl:message name="getRecordsSoapOut">
    <wsdl:part name="cmdb_rel_ci" element="tns:getRecordsResponse"></wsdl:part>
  </wsdl:message>
</wsdl:definitions>

Successful Request and Response in SoapUI:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://www.theserver.com/cmdb_rel_ci">
  <soapenv:Header/>
  <soapenv:Body>
    <ns2:getRecords>
      <arg1>value</arg1>
      <!-- Remaining Arguments -->
    </ns2:getRecords>
  </soapenv:Body>
</soapenv:Envelope>


<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <SOAP-ENV:Body>
    <getRecordsResponse>
      <getRecordsResult>
        <resultField1>value</resultField1>
        <resultField2>value2</resultField2>
        <!-- etc. -->
      </getRecordsResult>
      <!-- Other getRecordsResult elements -->
    </getRecordsResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

How do I tell JAX-WS that the <getRecordsResponse> element won't have a namespace? I've tried setting targetNamespace = "" in the @ResponseWrapper annotation for getRecords(), but that just makes it expect the targetNamespace parameter from @WebService instead. And when I tried as a last-ditch effort to set the WebService's target namespace blank, it tried to infer a namespace from the Java package name (e.g. "http://my_package.com").


Solution

  • I was unable to find a way to tell JAX-WS not to expect a namespace, but I was able to work around it by pre-processing the response in a SOAPHandler and manually adding a namespace to the XML element (by giving it a new QName that included a namespace declaration).

    MyHandler.java:

    public class MyHandler implements SOAPHandler<SOAPMessageContext> {
        @Override
        public void close(MessageContext context) { 
        }
    
        @Override
        public boolean handleMessage(SOAPMessageContext context) {
            //Don't intercept outbound messages (SOAP Requests)
            if ((Boolean)context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) {return true;}
    
            SOAPMessage message = context.getMessage();
            try {
                SOAPBody body = message.getSOAPBody();
                Iterator<SOAPElement> bodyIterator = body.getChildElements();
                SOAPElement responseElement = bodyIterator.next();
                responseElement.setElementQName(new QName("http://www.theserver.com/cmdb_rel_ci", origGetRecordsResponse.getLocalName(), "cmdb"));
            } catch (Exception e) {e.printStackTrace();}
            return true;
        }
    
        @Override
        public boolean handleFault(SOAPMessageContext context) {
            return false;
        }
    
        @Override
        public Set<QName> getHeaders() {
            return Collections.emptySet();
        }
    }
    

    Then I just had to add my Handler to the HandlerChain with the following code in my main class, and it worked.

    Binding binding = ((BindingProvider)port).getBinding();
    List<Handler> handlers = binding.getHandlerChain();
    handlers.add(new MyHandler());
    binding.setHandlerChain(handlers);