Search code examples
javaxmlapacheweb-servicescxf

Unescape Xml Special Characters In Apache Cxf Endpoint


I have a requirement to return an unescaped xml string in my apache cxf endpoint. Below I paste sections of my code, the current xml string being returned and the desired xml string:

@WebService(endpointInterface = "net.system.soapservice.result.ResultMgrPortType", serviceName = "ResultMgrPortType")
public class ResultServiceImpl implements ResultMgrPortType {

@Override
public String genericAPIResult(String resultMsg) {

    Map<String, String> processedResultMsg = DataFormatUtil.parseResultMsg(resultMsg);

    String response = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">"
            + "<soapenv:Body>"
            + "<req:ResponseMsg"
            + " xmlns:req=\"http://api-v1.gen.mm.vodafone.com/mminterface/request\"><![CDATA[<?xml"
            + " version=\"1.0\""
            + "encoding=\"UTF-8\"?><response"
            + " xmlns=\"http://api-"
            + "v1.gen.mm.vodafone.com/mminterface/response\">"
            + "<ResponseCode>"+processedResultMsg.get("ResultCode")+"</ResponseCode>"
            + "<ConversationID>"+processedResultMsg.get("ConversationID")+"</ConversationID>"
            + "<ResponseDesc>Service result processed successfully.</ResponseDesc>"
            + "<OriginatorConversationID>"+processedResultMsg.get("OriginatorConversationID")+"</OriginatorConversationID>"
            + "<ServiceStatus>0</ServiceStatus>"
            + "</response>]]></req:ResponseMsg>"
            + "</soapenv:Body>"
            + "</soapenv:Envelope>";
    return response;
}

}

The xml response from the above endpoint as seen in my logs:

ID: 4
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ResponseMsg xmlns="http://api-v1.gen.mm.vodafone.com/mminterface/result">&lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"&gt;
&lt;soapenv:Body&gt;
&lt;req:ResponseMsg
 xmlns:req="http://api-v1.gen.mm.vodafone.com/mminterface/request"&gt;&lt;![CDATA[&lt;?xml
 version="1.0"
encoding="UTF-8"?&gt;&lt;response
 xmlns="http://api-
v1.gen.mm.vodafone.com/mminterface/response"&gt;&lt;ResponseCode&gt;0&lt;/ResponseCode&gt;&lt;ConversationID&gt;AG_20170411_000059047aba809d6631&lt;/ConversationID&gt;&lt;ResponseDesc&gt;Service result processed successfully.&lt;/ResponseDesc&gt;&lt;OriginatorConversationID&gt;902004_fhltd_60575.0&lt;/OriginatorConversationID&gt;&lt;ServiceStatus&gt;0&lt;/ServiceStatus&gt;&lt;/response&gt;]]&gt;&lt;/req:ResponseMsg&gt;
&lt;/soapenv:Body&gt;
&lt;/soapenv:Envelope&gt;</ResponseMsg></soap:Body></soap:Envelope>  
--------------------------------------

The expected xml by the client:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ResponseMsg xmlns="http://api-v1.gen.mm.vodafone.com/mminterface/result">
         <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
            <soapenv:Body>
               <req:ResponseMsg xmlns:req="http://api-v1.gen.mm.vodafone.com/mminterface/request"><![CDATA[<?xml version="1.0"encoding="UTF-8"?><response xmlns="http://api-v1.gen.mm.vodafone.com/mminterface/response"><ResponseCode>0</ResponseCode><ConversationID>AG_20170406_00004ddc3d70f6ba93f0</ConversationID><ResponseDesc>Service result processed successfully.</ResponseDesc><OriginatorConversationID>902004_fhltd_94802.0</OriginatorConversationID><ServiceStatus>0</ServiceStatus></response>]]></req:ResponseMsg>
            </soapenv:Body>
         </soapenv:Envelope>
      </ResponseMsg>
   </soap:Body>
</soap:Envelope>

NB: The response should not have &lt and &gt. May be I should add some configuration in apache-cxf-services.xml file or create an interceptor to change this behavior. Thanks in advance for your time and effort.


Solution

  • I answer my own question here. The solution is to create an interceptor to force jaxb in cxf to encode the response string in CDATA. So I removed the CDATA from the immediate response (this should now be handled by the interceptor) and the resultant response should look something like:

    String response = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">"
                + "<soapenv:Body>"
                + "<req:ResponseMsg"
                + " xmlns:req=\"http://api-v1.gen.mm.vodafone.com/mminterface/request\"><?xml"
                + " version=\"1.0\""
                + "encoding=\"UTF-8\"?><response"
                + " xmlns=\"http://api-"
                + "v1.gen.mm.vodafone.com/mminterface/response\">"
                + "<ResponseCode>"+processedResultMsg.get("ResultCode")+"</ResponseCode>"
                + "<ConversationID>"+processedResultMsg.get("ConversationID")+"</ConversationID>"
                + "<ResponseDesc>Service result processed successfully.</ResponseDesc>"
                + "<OriginatorConversationID>"+processedResultMsg.get("OriginatorConversationID")+"</OriginatorConversationID>"
                + "<ServiceStatus>0</ServiceStatus>"
                + "</response></req:ResponseMsg>"
                + "</soapenv:Body>"
                + "</soapenv:Envelope>";
    

    Then create an interceptor to add the CDATA back at the PRE_STREAM phase:

    import java.io.OutputStream;
    import javax.xml.stream.XMLStreamWriter;
    import org.apache.cxf.interceptor.AttachmentOutInterceptor;
    import org.apache.cxf.message.Message;
    import org.apache.cxf.phase.AbstractPhaseInterceptor;
    import org.apache.cxf.phase.Phase;
    import org.apache.cxf.staxutils.StaxUtils;
    
    
    public class CdataWriterInterceptor extends AbstractPhaseInterceptor<Message> {
    
        public CdataWriterInterceptor() {
            super(Phase.PRE_STREAM);
            addAfter(AttachmentOutInterceptor.class.getName());
        }
    
        @Override
        public void handleMessage(Message message) {
            System.out.println("############# CdataWriterInterceptor Executed #######################");
            message.put("disable.outputstream.optimization", Boolean.TRUE);
            XMLStreamWriter writer
                    = StaxUtils.createXMLStreamWriter(message.getContent(OutputStream.class));
            message.setContent(XMLStreamWriter.class, new CDataXMLStreamWriter(writer));
        }
    }
    

    Finally add the CDATA as shown below in the CDataXMLStreamWriter:

    import javax.xml.stream.XMLStreamException;
    import javax.xml.stream.XMLStreamWriter;
    import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;
    
    public class CDataXMLStreamWriter extends DelegatingXMLStreamWriter {
    
        private String currentElementName;
    
        public CDataXMLStreamWriter(XMLStreamWriter del) {
            super(del);
        }
    
        @Override
        public void writeCharacters(String text) throws XMLStreamException {
                System.out.println("WritingCData" + text);
                super.writeCData(text);
        }
    
        private boolean checkIfCDATAneededForCurrentElement() {
            if ("MessageBody".equals(currentElementName)) {
                return true;
            }
            return false;
        }
    
        @Override
        public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
            currentElementName = local;
            super.writeStartElement(prefix, local, uri);
        }
    }
    

    The idea is that if the xml string is sent within CDATA section, it won't get parsed by the cxf internal xml parser and therefore special characters won't be escaped. This worked for me and I hope it will work for some one else. More details here: CXF jaxb send string as CData