How to Modify the Raw Xml Message of an Outbound Cxf Request

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

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.

Put modified xml back into the message?

Finally found an answer. I used a FilterWriter.

public void handleMessage(Message message) throws Fault {
final Writer writer = message.getContent(Writer.class);
message.setContent(Writer.class, new OutWriter(message, writer));
}

class OutWriter extends FilterWriter {
@Override
public void close() throws IOException {
// Modify String (in xml form).
message.setContent(Writer.class, out);
}
}

Saving outbound messages in CXF interceptor

The way I understand your question:

  1. If you just want to log SOAP request response envelops, you shouldnt even be using StreamInterceptor. its better to use LoggingInterceptor. To answer your first question
    message.setContent(OutputStream.class, cwos); intercepts the outgoing soap message and changes the content within it.

  2. If you want to continue with StreamInterceptor, you would end up using the same method to get the content of OutputStream. An alternative would be to use LoggingInterceptor.
    for more information on this
    How to log Apache CXF Soap Request and Soap Response using Log4j

  3. you can continue using pre-stream. but if the purpose is to just read the message content, you can go with pre_invoke. there is no advantage of using one over another.
    here is a link which will give you an idea about how could you extend a simple class to implement LoggingInterceptor.
    http://cxf.apache.org/docs/interceptors.html

    How to get incoming & outgoing soap xml in a simple way using Apache CXF?

Is it possible to send a pretty printed SOAP request with apache cxf?

I've found another question which helped me to find a solution for my needs.

Going from that post I was able to adopt the following code which can do what I need

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
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 PrettyPrintingOutInterceptor extends AbstractPhaseInterceptor<Message> {

public PrettyPrintingOutInterceptor(int indent) {
super(Phase.PRE_STREAM);
addBefore(SoapPreProtocolOutInterceptor.class.getName());
}

@Override
public void handleMessage(Message message) throws Fault {
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();
CachedOutputStream csnew = (CachedOutputStream) message
.getContent(OutputStream.class);

// get current payload
String soapMessage = IOUtils.toString(csnew.getInputStream());
// manipulate payload
soapMessage = prettyPrint(soapMessage, 3);

// Write new data into the OutputStream from above
ByteArrayInputStream bin = new ByteArrayInputStream(soapMessage.getBytes());
CachedOutputStream.copyStream(bin, os, 1024);

os.flush();
} catch (IOException | TransformerException e) {
// error handling
} finally {
// Important! Close streams!
try {
cs.close();
} catch (IOException e) {
}
try {
os.close();
} catch (IOException e) {
}
}
}
}

private String prettyPrint(String xml, int indent) throws TransformerException {
Source xmlInput = new StreamSource(new StringReader(xml));
StringWriter stringWriter = new StringWriter();
StreamResult xmlOutput = new StreamResult(stringWriter);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "" + indent);
transformer.transform(xmlInput, xmlOutput);
return xmlOutput.getWriter().toString();
}

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

and adding an object from this class to the client with client.getOutInterceptors().add(new PrettyPrintingOutInterceptor());

How to get incoming & outgoing soap xml in a simple way using Apache CXF?

Found the code for an incoming interceptor here:
Logging request/response with Apache CXF as XML

My outgoing interceptor:

import java.io.OutputStream;

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.io.CacheAndWriteOutputStream;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.io.CachedOutputStreamCallback;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.Phase;

public class MyLogInterceptor extends LoggingOutInterceptor {

public MyLogInterceptor() {
super(Phase.PRE_STREAM);
}

@Override
public void handleMessage(Message message) throws Fault {
OutputStream out = message.getContent(OutputStream.class);
final CacheAndWriteOutputStream newOut = new CacheAndWriteOutputStream(out);
message.setContent(OutputStream.class, newOut);
newOut.registerCallback(new LoggingCallback());
}

public class LoggingCallback implements CachedOutputStreamCallback {
public void onFlush(CachedOutputStream cos) {
}

public void onClose(CachedOutputStream cos) {
try {
StringBuilder builder = new StringBuilder();
cos.writeCacheTo(builder, limit);
// here comes my xml:
String soapXml = builder.toString();
} catch (Exception e) {
}
}
}
}

How to modify the generated SOAP request?

one way could be to get the document and run it through XSLT transform.

You can get at the document in the handleMessage of your interceptor by calling

@Override
public void handleMessage(SoapMessage message) throws Fault{
SOAPMessage saaj = message.getContent(SOAPMessage.class);
Document doc = saaj.getSOAPPart(); // This actually returns a SOAPPart instance but it does implement the w3c Document interface

//play around with the document, doc is a reference so any changes made to that instance
//will be forwarded to the rest of the chain
}

careful though that if you have security such as XML signature that must be performed on the soap content you must ensure that your interceptor occurs BEFORE the signature are applied otherwise you will invalidate them.

To play around with the timing of the interceptor you can specify the phase at which it will run. CXF should also honor the order in which you will configure them should they be performed at the same phase.

but don't take my word for it... check these for more info

  • http://cxf.apache.org/docs/interceptors.html
  • http://fusesource.com/docs/esb/4.2/cxf_interceptors/CXFInterceptorIntro.html

debugging through the CXF source code also helped me a great deal in understanding how it worked

---- EDIT ----

(thanks Daniel :-)

For this to work you need to have SAAJOutInterceptor configured in your stack. You can either add it manually or simply make it part of your interceptor. Here is an example of an interceptor that pretty much does what you want.



Related Topics



Leave a reply



Submit