CXFでサーバー側のインターセプターをいじっています。しかし、SOAP XMLを含むプレーンな文字列を提供する単純な着信および発信インターセプターを実装することは簡単な仕事ではないようです。
特定のロギングタスクに使用できるように、インターセプターにプレーンXMLが必要です。標準のログインおよびログアウトインターセプターは、タスクに対応していません。着信SOAP XMLを取得できる単純な着信インターセプターと、再度SOAP XMLを取得できる発信インターセプターを実装する方法について、誰かが喜んで例を挙げてくれませんか?
着信インターセプターのコードをここに見つけました: Apache CXFをXMLとして要求/応答をログに記録する
私の発信インターセプター:
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) {
}
}
}
}
上記の解決策がうまく機能しませんでした。これは私が開発したものであり、他の人に役立つことを願っています:
私の「着信」インターセプター:
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);
}
}
私の「発信」インターセプター:
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);
}
}
Spring FrameworkのアプリケーションコンテキストXMLに追加したもの(XMLファイルで2つのインターセプターも定義することを忘れないでください)...
...
<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>
...
注:特定のSOAPサービスのみをインターセプトできるようにするアノテーションなどを使用してインターセプターを追加する方法は他にもあります。上記の方法で「バス」にインターセプターを追加すると、すべてのSOAPサービスがインターセプトされます。
もう1つのオプションを共有したいと思います。たとえば、ログ要求やデータベースへの対応する応答など、ログ目的で着信メッセージと発信メッセージを同時に取得する方法です。
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();
}
}
次に、そのハンドラをWebサービスに接続します
<jaxws:endpoint id="wsEndpoint" implementor="#myWS" address="/myWS" >
<jaxws:handlers>
<bean class="com.package.handlers.CxfLoggingHandler"/>
</jaxws:handlers>
</jaxws:endpoint>
いくつかのカスタムプロパティをキャプチャしてリクエストXMLをフィルタリングするためのフックを使用して、テキストをStringBufferに書き込む例:
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;
}
}
出力インターセプターを使用した完全に機能する例については、githubのmy CXF module を参照してください。