Friday, October 19, 2012

Writing Accounting Logs

While designing an order processing application which is used as some sort of an backend for different web shops, our business department formulated the requirement to have sufficient information extracted from the exposed services which helps them to do accounting towards the application users. These users identify themselves by providing a unique key as part of the HTTP header. 

As the application is developed in a mid-size team and it is required to have the accounting information exposed for all services, it is hard to ensure that all service implementations stick to that requirement - well, a review could help, but after all we are humans :-)

Therefore we decided to work on a more generic approach which is independent from any specifc service implementation but can be easily activated with a minimum amount of code. The first idea was to use an interceptor which is added to the relevant service classes.

Although the interceptor approach worked as expected, the available information were not sufficient to do accounting: there is no way to access the request payload and headers from the interceptor implementation. Injecting a WebServiceContext using the resource annotation works for the service instance which is currently executed but not for the interceptor called prior the service method execution.

It was then we came up with the approach to implement a soap handler which extracts all relevant accounting information and exports them as required. The implementation looks similar as follows:
 public class SOAPLoggingHandler implements SOAPHandler<SOAPMessageContext> {  
      /**  
       * @see javax.xml.ws.handler.Handler#close(javax.xml.ws.handler.MessageContext)  
       */  
      public void close(MessageContext ctx) {  
      }  
      /**  
       * @see javax.xml.ws.handler.Handler#handleFault(javax.xml.ws.handler.MessageContext)  
       */  
      public boolean handleFault(SOAPMessageContext ctx) {  
           return false;  
      }  
      /**  
       * @see javax.xml.ws.handler.Handler#handleMessage(javax.xml.ws.handler.MessageContext)  
       */  
      public boolean handleMessage(SOAPMessageContext ctx) {            
           boolean outboundMessage = (Boolean)ctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);  
           if(!outboundMessage) {  
                // extract relevant information here and write them to log  
           }                 
           return false;  
      }  
      /**  
       * @see javax.xml.ws.handler.soap.SOAPHandler#getHeaders()  
       */  
      public Set<QName> getHeaders() {  
           return null;  
      }  
 }  

Next, the handler must be registered with the application. Therefore a file named, eg. handlers.xml, is created and added to the classpath having the following contents:
 <?xml version="1.0" encoding="UTF-8"?>   
 <jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee">   
      <jws:handler-chain>   
           <jws:handler>  
                <jws:handler-class>com.mnxfst.logging.SOAPLoggingHandler</jws:handler-class>   
           </jws:handler>   
      </jws:handler-chain>   
 </jws:handler-chains>  
Last but not least, the handler needs to be added to a web service implementation:
 @WebService  
 @HandlerChain ( file = "handlers.xml" )  
 public class TestWS {  
      @WebMethod  
      public boolean returnTrue() {  
      }  
 }  





Depending on the business needs the required information can be extracted from the root source of a SOAP request.