J'ai tripoté des intercepteurs côté serveur sur CXF. Mais il semble que ce ne soit pas une tâche triviale d'implémenter de simples intercepteurs entrants et sortants qui me donnent une chaîne simple contenant le SOAP XML.
J'ai besoin d'avoir le XML brut dans l'intercepteur pour pouvoir les utiliser pour des tâches de journalisation spécifiques. Les intercepteurs LogIn & LogOut standard ne sont pas à la hauteur de la tâche. Quelqu'un souhaite-t-il partager un exemple sur la façon dont je pourrais implémenter un simple intercepteur entrant capable d'obtenir le SOAP XML entrant et un intercepteur sortant pour obtenir à nouveau le SOAP XML?
Trouvé le code d'un intercepteur entrant ici: demande/réponse de journalisation avec Apache CXF en XML
Mon intercepteur sortant:
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) {
}
}
}
}
Je n'ai pas pu faire fonctionner la solution ci-dessus pour moi. C'est ce que j'ai développé et j'espère que cela pourra aider les autres:
Mon intercepteur "entrant":
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);
}
}
Mon intercepteur "sortant":
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);
}
}
Quelque chose que j'ai ajouté à mon contexte XML d'application de framework Spring (n'oubliez pas de définir les deux intercepteurs dans le fichier XML aussi) ...
...
<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>
...
Remarque, il existe d'autres façons d'ajouter les intercepteurs, par exemple via des annotations, qui vous permettront d'intercepter uniquement des services soap spécifiques. La manière ci-dessus d'ajouter des intercepteurs au "bus" intercepterait tous vos services de savon.
Je veux juste partager une option de plus, comment rassembler les messages entrants et sortants en même temps à des fins de journalisation, par exemple les demandes de journal et les réponses correspondantes à la base de données.
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();
}
}
puis connectez ce gestionnaire avec le service Web
<jaxws:endpoint id="wsEndpoint" implementor="#myWS" address="/myWS" >
<jaxws:handlers>
<bean class="com.package.handlers.CxfLoggingHandler"/>
</jaxws:handlers>
</jaxws:endpoint>
Exemple pour écrire le texte dans un StringBuffer, avec des crochets pour capturer certaines propriétés personnalisées et filtrer le XML de la requête:
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;
}
}
Pour un exemple pleinement fonctionnel, avec des intercepteurs de sortie, voir my module CXF sur github.