CXF – Persistieren der Requests

Nachdem auch das zentrale Logging mittels log4j passt, wäre es doch auch schön, die einzelnen Request bzw. Responses in einer Datenbank bzw. externe Datei zu schreiben. Das ganze könnte man beispielsweise über einen eigenen Interceptor machen, dem LoggingOutToFileInterceptor :

package server;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.LoggingMessage;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.interceptor.StaxOutInterceptor;
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.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.mortbay.log.Log;

/**
 * 
 * modded original {@link org.apache.cxf.interceptor.LoggingOutInterceptor} by
 * Christoph Burmeister to log into a file
 */
public class LoggingOutToFileInterceptor extends AbstractPhaseInterceptor {

	private static final Logger LOG = LogUtils
			.getL7dLogger(LoggingOutInterceptor.class);

	private PrintWriter writer;

	public LoggingOutToFileInterceptor(String phase) {
		super(phase);
		addBefore(StaxOutInterceptor.class.getName());
	}

	public LoggingOutToFileInterceptor() {
		this(Phase.PRE_STREAM);
	}

	public LoggingOutToFileInterceptor(PrintWriter w) {
		this();
		this.writer = w;
	}

	public void handleMessage(Message message) throws Fault {
		final OutputStream os = message.getContent(OutputStream.class);
		if (os == null) {
			return;
		}

		if (LOG.isLoggable(Level.INFO) || writer != null) {
			// Write the output while caching it for the log message
			final CacheAndWriteOutputStream newOut = new CacheAndWriteOutputStream(
					os);
			message.setContent(OutputStream.class, newOut);
			newOut.registerCallback(new LoggingCallback(message, os));
		}
	}

	/**
	 * Transform the string before display. The implementation in this class
	 * does nothing. Override this method if you want to change the contents of
	 * the logged message before it is delivered to the output. For example, you
	 * can use this to masking out sensitive information.
	 * 
	 * @param originalLogString
	 *            the raw log message.
	 * @return transformed data
	 */
	protected String transform(String originalLogString) {
		return originalLogString;
	}

	class LoggingCallback implements CachedOutputStreamCallback {

		private final Message message;
		private final OutputStream origStream;
		private static final String filename_root = "D:\\";
		private static final String DATE_FORMAT_NOW = "yyyy-MM-dd_HH-mm-ss";
		private static final String filename_ext = ".xml";

		public LoggingCallback(final Message msg, final OutputStream os) {
			this.message = msg;
			this.origStream = os;
		}

		public void onFlush(CachedOutputStream cos) {

		}

		public void onClose(CachedOutputStream cos) {
			String id = (String) message.getExchange().get(
					LoggingMessage.ID_KEY);
			if (id == null) {
				id = LoggingMessage.nextId();
				message.getExchange().put(LoggingMessage.ID_KEY, id);
			}

			final LoggingMessage buffer = new LoggingMessage("", id);

			String encoding = (String) message.get(Message.ENCODING);
			if (encoding != null) {
				buffer.getEncoding().append(encoding);
			}

			String address = (String) message.get(Message.ENDPOINT_ADDRESS);
			if (address != null) {
				buffer.getAddress().append(address);
			}

			String ct = (String) message.get(Message.CONTENT_TYPE);
			if (ct != null) {
				buffer.getContentType().append(ct);
			}

			Object headers = message.get(Message.PROTOCOL_HEADERS);
			if (headers != null) {
				buffer.getHeader().append(headers);
			}

			if (cos.getTempFile() == null) {
				// buffer.append("Outbound Message:\n");
			} else {
				buffer.getMessage().append(
						"Outbound Message (saved to tmp file):\n");
				buffer.getMessage().append(
						"Filename: " + cos.getTempFile().getAbsolutePath()
								+ "\n");
			}
			try {
				cos.writeCacheTo(buffer.getPayload());
			} catch (Exception ex) {
				// ignore
			}

			writePayLoadToFile(buffer.getPayload().toString());

			try {
				cos.lockOutputStream();
				cos.resetOut(null, false);
			} catch (Exception ex) {
				Log.warn(ex.getMessage().toString());
			}
			message.setContent(OutputStream.class, origStream);
		}

		/**
		 * gets the payload from message and puts it into a file
		 * 
		 * @param payload
		 */
		private void writePayLoadToFile(String payload) {
			try {
				Calendar cal = Calendar.getInstance();
				SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW);
				String filename = filename_root + sdf.format(cal.getTime())
						+ filename_ext;
				BufferedWriter out = new BufferedWriter(
						new FileWriter(filename));
				Log.info("logging into " + filename);
				out.write(payload); // we just need the soap-message from the
									// payload
				out.close();
			} catch (IOException e) {
				Log.warn(e.getMessage().toString());
			}
		}
	}
}

Und dieser wird analog zum bewährten LoggingoutInterceptor in die InterceptorChain eingehängt :

package client;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;

import server.IService;
import server.LoggingOutToFileInterceptor;

public final class ClientApp {

	/**
	 * executes the webservice-call within ClientApp.
	 * 
	 * @param text
	 *            the message that is send via xml-request
	 * @param target
	 *            the serverside which gets the request
	 */
	public void executeOperation(String text, String target) {
		System.setProperty("org.apache.cxf.Logger",
				"org.apache.cxf.common.logging.Log4jLogger");
		JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
		factory.setAddress(target);
		// log all on incoming-chain
		factory.getInInterceptors().add(new LoggingInInterceptor());
		// log all on outgoing-chain
		// factory.getOutInterceptors().add(new LoggingOutInterceptor());
		factory.getOutInterceptors().add(new LoggingOutToFileInterceptor());
		// get the right Interface
		factory.setServiceClass(IService.class);
		// create the Client Service
		IService service = (IService) factory.create();
		Client client = ClientProxy.getClient(service);

		HTTPConduit http = (HTTPConduit) client.getConduit();
		HTTPClientPolicy policy = new HTTPClientPolicy();
		policy.setReceiveTimeout(1000);
		http.setClient(policy);
		service.sayHi(text);
	}
}

Damit werden nun alle ausgehenden (Outbound) Requests (vom Client!) schön säuberlich mit einem Zeitstempel versehen als XML unter D:/ abgelegt. Analog dazu könnte im Callback-Handler auch ein DB-Zugriff stattfinden, um den Payload in einer Tabelle abzulegen.