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

6

21

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);
} 
Mandelbaum answered 2/8, 2011 at 16:36 Comment(0)
F
20

I had this problem as well today. After much weeping and gnashing of teeth, I was able to alter the StreamInterceptor class in the configuration_interceptor demo that comes with the CXF source:

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

message.getInterceptorChain().doIntercept(message);

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

    String soapMessage = IOUtils.toString(csnew.getInputStream());
    ...

The soapMessage variable will contain the complete SOAP message. You should be able to manipulate the soap message, flush it to an output stream and do a message.setContent(OutputStream.class... call to put your modifications on the message. This comes with no warranty, since I'm pretty new to CXF myself!

Note: CachedStream is a private class in the StreamInterceptor class. Don't forget to configure your interceptor to run in the PRE_STREAM phase so that the SOAP interceptors have a chance to write the SOAP message.

Fandango answered 14/9, 2011 at 19:1 Comment(2)
Thanks for your input John. Other elements related to that question can be found here: #6906999Mandelbaum
In your example the OutputStream os is declared but not used: how do you need it in this code?Sondrasone
T
43

Based on the first comment, I created an abstract class which can easily be used to change the whole soap envelope.

Just in case someone wants a ready-to-use code part.

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;
import org.apache.log4j.Logger;

/**
 * http://www.mastertheboss.com/jboss-web-services/apache-cxf-interceptors
 * https://mcmap.net/q/593833/-how-to-modify-the-raw-xml-message-of-an-outbound-cxf-request
 * 
 */
public abstract class MessageChangeInterceptor extends AbstractPhaseInterceptor<Message> {

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

    protected abstract Logger getLogger();

    protected abstract String changeOutboundMessage(String currentEnvelope);

    protected abstract String changeInboundMessage(String currentEnvelope);

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

                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("Outbound message: " + currentEnvelopeMessage);
                }

                String res = changeOutboundMessage(currentEnvelopeMessage);
                if (res != null) {
                    if (getLogger().isDebugEnabled()) {
                        getLogger().debug("Outbound message has been changed: " + res);
                    }
                }
                res = res != null ? res : currentEnvelopeMessage;

                InputStream replaceInStream = IOUtils.toInputStream(res, "UTF-8");

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

                os.flush();
                message.setContent(OutputStream.class, os);
                IOUtils.closeQuietly(os);

            } catch (IOException ioe) {
                getLogger().warn("Unable to perform change.", ioe);
                throw new RuntimeException(ioe);
            }
        } else {
            try {
                InputStream is = message.getContent(InputStream.class);
                String currentEnvelopeMessage = IOUtils.toString(is, "UTF-8");
                IOUtils.closeQuietly(is);

                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("Inbound message: " + currentEnvelopeMessage);
                }

                String res = changeInboundMessage(currentEnvelopeMessage);
                if (res != null) {
                    if (getLogger().isDebugEnabled()) {
                        getLogger().debug("Inbound message has been changed: " + res);
                    }
                }
                res = res != null ? res : currentEnvelopeMessage;

                is = IOUtils.toInputStream(res, "UTF-8");
                message.setContent(InputStream.class, is);
                IOUtils.closeQuietly(is);
            } catch (IOException ioe) {
                getLogger().warn("Unable to perform change.", ioe);

                throw new RuntimeException(ioe);
            }
        }
    }

    public void handleFault(Message message) {
    }

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

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

        protected void doClose() throws IOException {
        }

        protected void onWrite() throws IOException {
        }
    }
}
Tolu answered 18/10, 2012 at 6:50 Comment(7)
Where does CachedStream come from? I don't see an import for it and can't find it.Odontalgia
Cached Stream is an inner class of the example in the apache SVN. svn.apache.org/viewvc/cxf/trunk/distribution/src/main/release/… The provided abstract Class works like a charm! :)Scad
why this piece of code fails on bigger messages? Line message.getInterceptorChain().doIntercept(message) leaves message content with nullKrein
If someone is interested how to attach this to a generated Apache Cxf client, one should simply use Client client = ClientProxy.getClient(someServicePort); client.getInInterceptors().add(new MessageChangeInterceptor() { .. }); so it will be possible to modify RAW responsePelisse
I'm sorry... This is a terribly broken code. It may work, but as @Krein experiences, it's out of pure luck. Input and Output streams get created for no reasons, flushed when they are empty, swapped into the message for no reason, closed when the author did not open them (meaning there is probably double closing of streams), and so on. Plus it won't work at all if message writing is done through a writer instead of an output stream (e.g. SOAP over JMS with messageType = TEXT). The basic principle is good (using an interceptor, access the streams ...) but the implementation is really wrong.Opalescent
This worked well for my purposes but the output was chunked, which the client I was dealing with didn't like. I added this change to the copy process: if (os instanceof CopyingOutputStream) ((CopyingOutputStream)os).copyFrom( replaceInStream ); else IOUtils.copy(replaceInStream, os);Palma
you've just saved me hours of trouble ;-) much appreciatedMane
F
20

I had this problem as well today. After much weeping and gnashing of teeth, I was able to alter the StreamInterceptor class in the configuration_interceptor demo that comes with the CXF source:

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

message.getInterceptorChain().doIntercept(message);

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

    String soapMessage = IOUtils.toString(csnew.getInputStream());
    ...

The soapMessage variable will contain the complete SOAP message. You should be able to manipulate the soap message, flush it to an output stream and do a message.setContent(OutputStream.class... call to put your modifications on the message. This comes with no warranty, since I'm pretty new to CXF myself!

Note: CachedStream is a private class in the StreamInterceptor class. Don't forget to configure your interceptor to run in the PRE_STREAM phase so that the SOAP interceptors have a chance to write the SOAP message.

Fandango answered 14/9, 2011 at 19:1 Comment(2)
Thanks for your input John. Other elements related to that question can be found here: #6906999Mandelbaum
In your example the OutputStream os is declared but not used: how do you need it in this code?Sondrasone
W
2

Following is able to bubble up server side exceptions. Use of os.close() instead of IOUtils.closeQuietly(os) in previous solution is also able to bubble up exceptions.

public class OutInterceptor extends AbstractPhaseInterceptor<Message> {     
    public OutInterceptor() { 
        super(Phase.PRE_STREAM); 
        addBefore(StaxOutInterceptor.class.getName()); 
    }   
    public void handleMessage(Message message) { 
        OutputStream os = message.getContent(OutputStream.class); 
        CachedOutputStream cos = new CachedOutputStream(); 
        message.setContent(OutputStream.class, cos); 
        message.getInterceptorChain.aad(new PDWSOutMessageChangingInterceptor(os)); 
    }
} 

public class OutMessageChangingInterceptor extends AbstractPhaseInterceptor<Message> {
    private OutputStream os; 

    public OutMessageChangingInterceptor(OutputStream os){
        super(Phase.PRE_STREAM_ENDING); 
        addAfter(StaxOutEndingInterceptor.class.getName()); 
        this.os = os;
    } 

    public void handleMessage(Message message) { 
        try { 
            CachedOutputStream csnew = (CachedOutputStream) message .getContent(OutputStream.class);
            String currentEnvelopeMessage = IOUtils.toString( csnew.getInputStream(), (String) message.get(Message.ENCODING)); 
            csnew.flush(); 
            IOUtils.closeQuietly(csnew); 
            String res = changeOutboundMessage(currentEnvelopeMessage); 
            res = res != null ? res : currentEnvelopeMessage; 
            InputStream replaceInStream = IOUtils.tolnputStream(res, (String) message.get(Message.ENCODING)); 
            IOUtils.copy(replaceInStream, os); 
            replaceInStream.close(); 
            IOUtils.closeQuietly(replaceInStream);
            message.setContent(OutputStream.class, os);
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);  
        }
    }
} 
Wauters answered 20/6, 2015 at 21:50 Comment(1)
Welcome to Stack Overflow! While this answer is probably correct and useful, it is preferred if you include some explanation along with it to explain how it helps to solve the problem. This becomes especially useful in the future, if there is a change (possibly unrelated) that causes it to stop working and users need to understand how it once worked.Thunderclap
T
1

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 {
        }
    }
}

Throwaway answered 14/2, 2020 at 5:25 Comment(0)
N
1

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);
}
Nyasaland answered 20/4, 2020 at 22:33 Comment(0)
S
1

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)
  }
}

Spireme answered 29/4, 2020 at 8:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.