How To Modify The Raw XML message of an Outbound CXF Request?

Viewed 38765

I would like to modify an outgoing SOAP Request. I would like to remove 2 xml nodes from the Envelope's body. I managed to set up an Interceptor and get the generated String value of the message set to the endpoint.

However, the following code does not seem to work as the outgoing message is not edited as expected. Does anyone have some code or ideas on how to do this?

public class MyOutInterceptor extends AbstractSoapInterceptor {

public MyOutInterceptor() {
        super(Phase.SEND); 
}

public void handleMessage(SoapMessage message) throws Fault { 
        // Get message content for dirty editing...
        StringWriter writer = new StringWriter();
        CachedOutputStream cos  = (CachedOutputStream)message.getContent(OutputStream.class); 
        InputStream inputStream = cos.getInputStream();
        IOUtils.copy(inputStream, writer, "UTF-8");
        String content = writer.toString();

        // remove the substrings from envelope...
        content = content.replace("<idJustification>0</idJustification>", "");
        content = content.replace("<indicRdv>false</indicRdv>", "");
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(content.getBytes(Charset.forName("UTF-8")));
        message.setContent(OutputStream.class, outputStream);
} 
6 Answers

Good example for replacing outbound soap content based on this

package kz.bee.bip;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

public class SOAPOutboundInterceptor extends AbstractPhaseInterceptor<Message> {

    public SOAPOutboundInterceptor() {
        super(Phase.PRE_STREAM);
        addBefore(SoapPreProtocolOutInterceptor.class.getName());
    }

    public void handleMessage(Message message) {

        boolean isOutbound = false;
        isOutbound = message == message.getExchange().getOutMessage()
                || message == message.getExchange().getOutFaultMessage();

        if (isOutbound) {
            OutputStream os = message.getContent(OutputStream.class);
            CachedStream cs = new CachedStream();
            message.setContent(OutputStream.class, cs);

            message.getInterceptorChain().doIntercept(message);

            try {
                cs.flush();
                IOUtils.closeQuietly(cs);
                CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);

                String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
                csnew.flush();
                IOUtils.closeQuietly(csnew);

                /* here we can set new data instead of currentEnvelopeMessage*/
                InputStream replaceInStream = IOUtils.toInputStream(currentEnvelopeMessage, "UTF-8");

                IOUtils.copy(replaceInStream, os);
                replaceInStream.close();
                IOUtils.closeQuietly(replaceInStream);

                os.flush();
                message.setContent(OutputStream.class, os);
                IOUtils.closeQuietly(os);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }

    public void handleFault(Message message) {
    }

    private static class CachedStream extends CachedOutputStream {
        public CachedStream() {
            super();
        }

        protected void doFlush() throws IOException {
            currentStream.flush();
        }

        protected void doClose() throws IOException {
        }

        protected void onWrite() throws IOException {
        }
    }
}

a better way would be to modify the message using the DOM interface, you need to add the SAAJOutInterceptor first (this might have a performance hit for big requests) and then your custom interceptor that is executed in phase USER_PROTOCOL

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Node;

import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

abstract public class SoapNodeModifierInterceptor extends AbstractSoapInterceptor {
    SoapNodeModifierInterceptor() { super(Phase.USER_PROTOCOL); }

    @Override public void handleMessage(SoapMessage message) throws Fault {
        try {
            if (message == null) {
                return;
            }
            SOAPMessage sm = message.getContent(SOAPMessage.class);
            if (sm == null) {
                throw new RuntimeException("You must add the SAAJOutInterceptor to the chain");
            }

            modifyNodes(sm.getSOAPBody());

        } catch (SOAPException e) {
            throw new RuntimeException(e);
        }
    }

    abstract void modifyNodes(Node node);
}

this one's working for me. It's based on StreamInterceptor class from configuration_interceptor example in Apache CXF samples.

It's in Scala instead of Java but the conversion is straightforward.

I tried to add comments to explain what's happening (as far as I understand).

import java.io.OutputStream

import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor
import org.apache.cxf.helpers.IOUtils
import org.apache.cxf.io.CachedOutputStream
import org.apache.cxf.message.Message
import org.apache.cxf.phase.AbstractPhaseInterceptor
import org.apache.cxf.phase.Phase

// java note: base constructor call is hidden at the end of class declaration
class StreamInterceptor() extends AbstractPhaseInterceptor[Message](Phase.PRE_STREAM) {

  // java note: put this into the constructor after calling super(Phase.PRE_STREAM);
  addBefore(classOf[SoapPreProtocolOutInterceptor].getName)

  override def handleMessage(message: Message) = {
    // get original output stream
    val osOrig = message.getContent(classOf[OutputStream])
    // our output stream
    val osNew = new CachedOutputStream
    // replace it with ours
    message.setContent(classOf[OutputStream], osNew)
    // fills the osNew instead of osOrig
    message.getInterceptorChain.doIntercept(message)
    // flush before getting content
    osNew.flush()
    // get filled content
    val content = IOUtils.toString(osNew.getInputStream, "UTF-8")
    // we got the content, we may close our output stream now
    osNew.close()
    // modified content
    val modifiedContent = content.replace("a-string", "another-string")
    // fill original output stream
    osOrig.write(modifiedContent.getBytes("UTF-8"))
    // flush before set
    osOrig.flush()
    // replace with original output stream filled with our modified content
    message.setContent(classOf[OutputStream], osOrig)
  }
}

Related