Marco
Marco

Reputation: 15909

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

I have been fiddling around with server side interceptors on CXF. But is seems that it is not a trivial task to implement simple incoming and outgoing interceptors that give me a plain string containing the SOAP XML.

I need to have the plain XML in the interceptor so that I can use them for specific logging tasks. The standard LogIn & LogOut interceptors are not up to the task. Is anyone willing to share some example on how I could implement a simple incoming interceptor that is able to get the incoming SOAP XML and a outgoing interceptor to again get the SOAP XML?

Upvotes: 16

Views: 64714

Answers (4)

error1009
error1009

Reputation: 151

I just want to share one more option, how to get incoming and outgoing messages together at the same time for some logging purpose, for example log requests and corresponding responses to database.

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Set;

public class CxfLoggingHandler implements SOAPHandler<SOAPMessageContext> {

private static final String SOAP_REQUEST_MSG_KEY = "REQ_MSG";

public Set<QName> getHeaders() {
    return Collections.EMPTY_SET;
}

public boolean handleMessage(SOAPMessageContext context) {
    Boolean outgoingMessage = (Boolean) context.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
    if (outgoingMessage) {
        // it is outgoing message. let's work
        SOAPPart request = (SOAPPart)context.get(SOAP_REQUEST_MSG_KEY);
        String requestString = convertDomToString(request);
        String responseString = convertDomToString(context.getMessage().getSOAPPart());
        String soapActionURI = ((QName)context.get(MessageContext.WSDL_OPERATION)).getLocalPart();
        // now you can output your request, response, and ws-operation    
    } else {
        // it is incoming message, saving it for future
        context.put(SOAP_REQUEST_MSG_KEY, context.getMessage().getSOAPPart());
    }
    return true;
}

public boolean handleFault(SOAPMessageContext context) {        
    return handleMessage(context);
}

private String convertDomToString(SOAPPart soap){
    final StringWriter sw = new StringWriter();
    try {
        TransformerFactory.newInstance().newTransformer().transform(
                new DOMSource(soap),
                new StreamResult(sw));
    } catch (TransformerException e) {
        // do something
    }
    return sw.toString();
}
}

and then connect that handler with webservice

<jaxws:endpoint id="wsEndpoint" implementor="#myWS" address="/myWS" >
    <jaxws:handlers>
        <bean class="com.package.handlers.CxfLoggingHandler"/>
    </jaxws:handlers>
</jaxws:endpoint>

Upvotes: 5

ThomasRS
ThomasRS

Reputation: 8287

Example for writing the text to a StringBuffer, with hooks for capturing some custom properties and filtering of the request XML:

public class XMLLoggingInInterceptor extends AbstractPhaseInterceptor<Message> {

    private static final String LOCAL_NAME = "MessageID";

    private static final int PROPERTIES_SIZE = 128;

    private String name = "<interceptor name not set>";

    protected PrettyPrinter prettyPrinter = null;
    protected Logger logger;
    protected Level reformatSuccessLevel;
    protected Level reformatFailureLevel;

    public XMLLoggingInInterceptor() {
        this(LogUtils.getLogger(XMLLoggingInInterceptor.class), Level.INFO,  Level.WARNING);
    }

    public XMLLoggingInInterceptor(PrettyPrinter prettyPrinter) {
        this(LogUtils.getLogger(XMLLoggingInInterceptor.class), Level.INFO,  Level.WARNING);

        this.prettyPrinter = prettyPrinter;
    }

    public XMLLoggingInInterceptor(Logger logger, Level reformatSuccessLevel, Level reformatFailureLevel) {
        super(Phase.RECEIVE);
        this.logger = logger;
        this.reformatSuccessLevel = reformatSuccessLevel;
        this.reformatFailureLevel = reformatFailureLevel;
    }

    public XMLLoggingInInterceptor(PrettyPrinter prettyPrinter, Logger logger, Level reformatSuccessLevel, Level reformatFailureLevel) {
        this(logger, reformatSuccessLevel, reformatFailureLevel);
        this.prettyPrinter = prettyPrinter;
        this.logger = logger;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void handleMessage(Message message) throws Fault {

        if (!logger.isLoggable(reformatSuccessLevel)) {
             return;
        }

        InputStream in = message.getContent(InputStream.class);
        if (in == null) {
            return;
        }

        StringBuilder buffer;

        CachedOutputStream cache = new CachedOutputStream();
        try {
            InputStream origIn = in;
            IOUtils.copy(in, cache);

            if (cache.size() > 0) {
                in = cache.getInputStream();
            } else {
                in = new ByteArrayInputStream(new byte[0]);
            }

            // set the inputstream back as message payload
            message.setContent(InputStream.class, in);

            cache.close();
            origIn.close();

            int contentSize = (int) cache.size();

            buffer = new StringBuilder(contentSize + PROPERTIES_SIZE);

            cache.writeCacheTo(buffer, "UTF-8");
        } catch (IOException e) {
            throw new Fault(e);
        }

        // decode chars from bytes
        char[] chars = new char[buffer.length()];
        buffer.getChars(0, chars.length, chars, 0);

        // reuse buffer
        buffer.setLength(0);

        // perform local logging - to the buffer 
        buffer.append(name);

        logProperties(buffer, message);

        // pretty print XML
        if(prettyPrinter.process(chars, 0, chars.length, buffer)) {
            // log as normal
            logger.log(reformatSuccessLevel, buffer.toString());
        } else {
            // something unexpected - log as exception
            buffer.append(" was unable to format XML:\n");
            buffer.append(chars); // unmodified XML

            logger.log(reformatFailureLevel, buffer.toString());
        }
    }


    /**
     * Gets theMessageID header in the list of headers.
     *
     */
    protected String getIdHeader(Message message) {
        return getHeader(message, LOCAL_NAME);
    }

    protected String getHeader(Message message, String name) {
        List<Header> headers = (List<Header>) message.get(Header.HEADER_LIST);

        if(headers != null) {
            for(Header header:headers) {
                if(header.getName().getLocalPart().equalsIgnoreCase(name)) {
                    return header.getObject().toString();
                }
            }
        }
        return null;
    }        

    /**
     * Method intended for use within subclasses. Log custom field here.
     * 
     * @param message message
    */

    protected void logProperties(StringBuilder buffer, Message message) {
        final String messageId = getIdHeader(message);
        if(messageId != null) {
            buffer.append(" MessageId=");
            buffer.append(messageId);
        }
    }

    public void setPrettyPrinter(PrettyPrinter prettyPrinter) {
        this.prettyPrinter = prettyPrinter;
    }

    public PrettyPrinter getPrettyPrinter() {
        return prettyPrinter;
    }

    public Logger getLogger() {
        return logger;
    }

    public String getName() {
        return name;
    }

    public Level getReformatFailureLevel() {
        return reformatFailureLevel;
    }

    public Level getReformatSuccessLevel() {
        return reformatSuccessLevel;
    }

    public void setReformatFailureLevel(Level reformatFailureLevel) {
        this.reformatFailureLevel = reformatFailureLevel;
    }

    public void setReformatSuccessLevel(Level reformatSuccessLevel) {
        this.reformatSuccessLevel = reformatSuccessLevel;
    }

    public void setLogger(Logger logger) {
        this.logger = logger;
    }
}

For a fully working example, with output interceptors, see my CXF module on github.

Upvotes: 3

dulon
dulon

Reputation: 754

I could not get the above solution to work for me. This is what I developed and hope it can help others:

My "incoming" interceptor:

import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingMessage;

public class MyCxfSoapInInterceptor extends LoggingInInterceptor {


    public MyCxfSoapInInterceptor() {
        super();
    }

    @Override
    protected String formatLoggingMessage(LoggingMessage loggingMessage) {
        String soapXmlPayload = loggingMessage.getPayload() != null ? loggingMessage.getPayload().toString() : null;

        // do what you want with the payload... in my case, I stuck it in a JMS Queue

        return super.formatLoggingMessage(loggingMessage);
    }
}

My "outgoing" interceptor:

import org.apache.cxf.interceptor.LoggingMessage;
import org.apache.cxf.interceptor.LoggingOutInterceptor;

public class MyCxfSoapOutInterceptor extends LoggingOutInterceptor {

    public MyCxfSoapOutInterceptor() {
        super();
    }

    @Override
    protected String formatLoggingMessage(LoggingMessage loggingMessage) {
        String soapXmlPayload = loggingMessage.getPayload() != null ? loggingMessage.getPayload().toString() : null;

        // do what you want with the payload... in my case, I stuck it in a JMS Queue

        return super.formatLoggingMessage(loggingMessage);
    }
}

Something I added to my spring framework application context XML (remember to define the two interceptors in the XML file too)...

    ...

    <cxf:bus>
        <cxf:inInterceptors>
            <ref bean="myCxfSoapInInterceptor"/>
        </cxf:inInterceptors>
        <cxf:inFaultInterceptors>
            <ref bean="myCxfSoapInInterceptor"/>
        </cxf:inFaultInterceptors>
        <cxf:outInterceptors>
            <ref bean="myCxfSoapOutInterceptor"/>
        </cxf:outInterceptors>
        <cxf:outFaultInterceptors>
            <ref bean="myCxfSoapOutInterceptor"/>
        </cxf:outFaultInterceptors>
    </cxf:bus>

    ...

Note, there are other ways to add the interceptors such as via annotations which will allow you to only intercept specific soap services. The above way of adding interceptors the the "bus" would intercept all your soap services.

Upvotes: 8

annkatrin
annkatrin

Reputation: 425

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

Upvotes: 23

Related Questions