21

I've created a web service using Spring-WS. To maintain compatibility with the old system, I need to change namespace prefix from SOAP-ENV to soap.

I know that SOAP-ENV and soap are just namespace prefixes. As long as they refer to the correct namespace ("http://schemas.xmlsoap.org/soap/envelope/"), it should be fine.

But the old system hard coded the parser code to expect only soap namespace prefix.

Current response:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
   ...
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Expected response:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header/>
   <soap:Body>
   ...
   </soap:Body>
</soap:Envelope>

Here's what I've tried so far

  1. Create EndpointInterceptorAdapter subclass. This will intercept SOAP response/fault and alter the SOAP envelope. This works, but it's not ideal in terms of performance.

    public class CustomEndpointInterceptor extends EndpointInterceptorAdapter {
    
      private static final String DEFAULT_NS = "xmlns:SOAP-ENV";
      private static final String SOAP_ENV_NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/";
      private static final String PREFERRED_PREFIX = "soap";
      private static final String HEADER_LOCAL_NAME = "Header";
      private static final String BODY_LOCAL_NAME = "Body";
      private static final String FAULT_LOCAL_NAME = "Fault";
    
      @Override
      public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
        SaajSoapMessage soapResponse = (SaajSoapMessage) messageContext.getResponse();
        alterSoapEnvelope(soapResponse);
        return super.handleResponse(messageContext, endpoint);
      }
    
      @Override
      public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
        SaajSoapMessage soapResponse = (SaajSoapMessage) messageContext.getResponse();
        alterSoapEnvelope(soapResponse);
        return super.handleFault(messageContext, endpoint);
      }
    
      private void alterSoapEnvelope(SaajSoapMessage soapResponse) {
        Document doc = soapResponse.getDocument();
        Element rootElement = doc.getDocumentElement();
        rootElement.setPrefix(PREFERRED_PREFIX);
        // Remove default SOAP namespace
        rootElement.removeAttribute(DEFAULT_NS);
        NodeList headerNodes = doc.getElementsByTagNameNS(SOAP_ENV_NAMESPACE, HEADER_LOCAL_NAME);
        NodeList bodyNodes = doc.getElementsByTagNameNS(SOAP_ENV_NAMESPACE, BODY_LOCAL_NAME);
        NodeList faultNodes = doc.getElementsByTagNameNS(SOAP_ENV_NAMESPACE, FAULT_LOCAL_NAME);
        // Remove Header node.
        if (headerNodes.getLength() != 0) {
          rootElement.removeChild(headerNodes.item(0));
        }
        // Change Body's SOAP namespace prefix.
        if (bodyNodes.getLength() != 0) {
          Element bodyElement = (Element) bodyNodes.item(0);
          bodyElement.setPrefix(PREFERRED_PREFIX);
        }
        if (faultNodes.getLength() != 0) {
          Element faultElement = (Element) faultNodes.item(0);
          faultElement.setPrefix(PREFERRED_PREFIX);
        }
      }
    }
    
  2. Change package-info.java in the package that contain WSDL generated classes. I've successfully done this with my company's namespace prefix, but it doesn't work for SOAP-ENV prefix.

    @javax.xml.bind.annotation.XmlSchema(namespace = "http://www.example.com/ns/2008/02/02/webservices/blah",
    xmlns = {
      @javax.xml.bind.annotation.XmlNs(namespaceURI = "http://www.example.com/ns/2008/02/02/webservices/blah", prefix = ""),
      @javax.xml.bind.annotation.XmlNs(namespaceURI = "http://schemas.example.com/ns/2007/10/blah", prefix = "ns2"),
      @javax.xml.bind.annotation.XmlNs(namespaceURI = "http://example.com/ns/2007/23/05/blah/fundamental", prefix = "ns3"),
      @javax.xml.bind.annotation.XmlNs(namespaceURI = "http://schemas.xmlsoap.org/soap/envelope/", prefix = "soap") // doesn't work
    },
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
    package com.thomsonreuters.ts.ets.interdayws.soap.webservice;
    

Is there an ideal way to change SOAP-ENV to soap in Spring-WS?

By the way, here's the code that set this prefix. StroapElement.java

attomos
  • 1,112
  • 3
  • 16
  • 30
  • These guys had the same problem and they solved it. Check the answer from johnreiter : http://forum.spring.io/forum/spring-projects/web-services/38497-soap-envelope-prefix – Aydin K. Aug 31 '16 at 08:55
  • @AydinK. Thanks for point me to that forum. Actually, I've read this thread before, but it's discussing from a client side point of view. Reading that one more time help me figure out how to adapt his answer to my problem. – attomos Sep 01 '16 at 11:08

3 Answers3

25

A better solution

Use SOAPMessage API instead of DOM.

  private void alterSoapEnvelope(SaajSoapMessage soapResponse) {
    try {
      SOAPMessage soapMessage = soapResponse.getSaajMessage();
      SOAPPart soapPart = soapMessage.getSOAPPart();
      SOAPEnvelope envelope = soapPart.getEnvelope();
      SOAPHeader header = soapMessage.getSOAPHeader();
      SOAPBody body = soapMessage.getSOAPBody();
      SOAPFault fault = body.getFault();
      envelope.removeNamespaceDeclaration(envelope.getPrefix());
      envelope.addNamespaceDeclaration(PREFERRED_PREFIX, SOAP_ENV_NAMESPACE);
      envelope.setPrefix(PREFERRED_PREFIX);
      header.setPrefix(PREFERRED_PREFIX);
      body.setPrefix(PREFERRED_PREFIX);
      if (fault != null) {
        fault.setPrefix(PREFERRED_PREFIX);
      }
    } catch (SOAPException e) {
      e.printStackTrace();
    }
  }

It's much faster now.

attomos
  • 1,112
  • 3
  • 16
  • 30
  • Is there an example how to use it? I have an `@Endpoint` that implements a generated interface/"PortType" by cxf. Where should I add SOAPMessage API? – user3105453 Jan 10 '17 at 15:46
  • @user3105453 Do you have `EndpointInterceptorAdapter` subclass anywhere in your project? I use `SOAPMessage` API in its `alterSoapEnvelope` method. – attomos Jan 11 '17 at 15:13
  • 1
    yes, I solved it with a class implementing `SoapEndpointInterceptor` and its `public boolean handleResponse(MessageContext messageContext, Object endpoint)` in which I alter the response like this: `SaajSoapMessage response = (SaajSoapMessage) messageContext.getResponse();` `alterSoapEnvelope(response);` `return true;` – user3105453 Jan 16 '17 at 13:22
  • 2
    SaajSoapMessage is spring. in my case I used the standard jdk SOAPMessage class. after all the change listed in the code above, have to call the SOAPMessage.saveChanges() – Junchen Liu Jun 05 '17 at 11:19
  • Anyone knows how to main user defined ns as per WSDL or XSD schema? – PAA Jan 02 '20 at 12:01
  • 1
    @attomos this is a great solution, but in order to not confuse people you had to have add a piece of code with Configuration class, where need to add this new CustomInterceptor. – matthew Oct 14 '20 at 10:39
10

I use SAAJ. Try this.

  1. soapEnvelope.removeNamespaceDeclaration("SOAP-ENV");
  2. soapEnvelope.addNamespaceDeclaration("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
  3. soapEnvelope.setPrefix("soapenv");
  4. soapHeader.setPrefix("soapenv");
  5. soapBody.setPrefix("soapenv");

Don't forget: soapMessage.saveChanges();

Reference:Changing the default XML namespace prefix generated with JAXWS

richard salazar
  • 101
  • 1
  • 3
2

Additional Point :

you have to extend your Webservice config class with WsConfigurationAdapter and add your CustomEndpointInterceptor in the webservice config class as stated below.

Then only the interceptor works.

Refer below link for more details

https://memorynotfound.com/spring-ws-intercept-request-response-soap-messages/