Version 2

    This article discusses using interceptors to provide transparent (to the coder) support for page view and user tracking via the Interceptor mechanism. If you use JAAS authentication (e.g., Tomcat form-based authentication) you can also use this technique to provide audit trails.

    Briefly, we want to exploit the fact that MVC frameworks usually route all pages through a single controller. In struts, this is the ActionServlet or ActionFilter.  Hibernated frameworks will usually have code similar to

    public void action(HttpServletRequest req, HttpServletResponse resp) {
       Session s = sf.openSession();
       // do stuff
       s.close();
    }
    

    We can make request and user information transparently available to all database requests by changing a single line:

    public void action(HttpServletRequest req, HttpServletResponse resp) {
       Session s = sf.openSession(new ServletInterceptor(req, resp));
       // do stuff
       s.close();
    }
    

    and with a bit more work we can ensure that all page views, errors, and performance statistics are gathered:

    public class PageView implements TrackableRequest, TrackableSession {
       private Long id = null;
       private RequestInfo requestInfo = null;
       private SessionInfo sessionInfo = null;
       private Long duration; // processing time in milliseconds
       private ExceptionInfo exceptionInfo; // if anything went wrong
    
       // constructors and accessors elided....
    }
    
    public void action(HttpServletRequest req, HttpServletResponse resp) {
       Session s = sf.openSession(new ServletInterceptor(req, resp));
       Date start = new Date();
       PageView pv = new PageView();
       s.save(pv);
       try {
          // do stuff
       } catch (Exception e) {
          pv.setException(e);
       } finally {
          pv.setDuration((new Date()).getTime() - start.getTime());
          s.update(pv);
          s.close();
       }
       // rethrow captured exception
    }
    

    where we have saved the object so it can be used as part of an audit trail system.

    First we need to create three new interfaces: TrackableRequest, TrackableResponse and TrackableSession.  These interfaces define accessor functions to RequestInfo, ResponseInfo and SessionInfo, respectively, that contain the actual information.

    public interface TrackableRequest {
       public RequestInfo getRequestInfo();
       public void setRequestInfo(RequestInfo info);
    }
    

    Second, we must create each of the Info classes to hold whatever information we wish to record about each request. While defining these classes it's important to keep in mind that all of the different types of servlets our system may support - will your application support FTP? IMAP? NNTP?

    public class RequestInfo {
       private Date timestamp;
       private String requestURI;
       private String referer;
       private String remoteAddr;
       private String remoteHost;
       private String remoteUser;
       private String userAgent;
    
       // constructors and accessor functions elided...
    }
    
    public class SessionInfo {
       private String sessionId;
    
       // constructors and accessor functions elided...
    }
    

    The ResponseInfo class isn't shown because I don't currently use it but it is an obvious extension of this idea.

    We now need to write user-defined Hibernate types SessionInfoType, RequestInfoType and ResponseInfoType to store the fields in our public classes. At this point we also identify the columns that must be specified in our Hibernate mapping for all classes that include session, request or response information.

    Finally we need to create a ServletInterceptor class:

    public class ServletInterceptor implements Interceptor, Serializable {
       private Date timestamp;
       private HttpSession session;
       private ServletRequest request;
       private ServletResponse response;
    
       /**
        * Default constructor, e.g., for standalone access.
        */
       private ServletInterceptor() {
          this.timestamp = new Date();
          session = null;
          request = null;
          response = null;
       }
    
       /**
        * Standard constructor that takes request & response,
        * but not session.
        */
       private ServletInterceptor(
           ServletRequest request,
           ServletResponse response) {
           this();
           this.request = request;
           this.response = response;
           if (request instanceof HttpServletRequest) {
              session = ((HttpServletRequest) request).getSession(false);
           }
       }
    
       // other constructors elided....
    
       /**
        * Write session tracking information
        */
       private void trackSession(TrackableSession entity) {
          SessionInfo si = entity.getSessionInfo();
          if (session != null) {
             if (si == null) {
                si = new SessionInfo();
                entity.setSessionInfo(si)
             }
             try {
                si.setSessionId(session.getId());
             } catch (Exception e) {
             }
          }
       }
    
       /**
        * Write request tracking information.
        */
       private void trackRequest(TrackableRequest entity) {
          RequestInfo si = entity.getRequestjjInfo();
          if (request != null) {
             if (ri == null) {
                ri = new RequestInfo();
                entity.setRequestInfo(ri)
             }
             try {
                ri.setTimestamp(timestamp);
                ri.setRemoteAddr(request.getRemoteAddr());
                ri.setRemoteHost(request.getRemoteHost());
                if (request instanceof HttpServletRequest) {
                    HttpServletRequest hsr = (HttpServletRequest) request;
                    ri.setRequestURI(hsr.getRequestURI());
                    ri.setReferer(hsr.getHeader("Referer"));
                    ri.setRemoteUser(hsr.getRemoteUser());
                    ri.setUserAction(hsr.getHeader("User-Agent"));
                }
             } catch (Exception e) {
             }
          }
       }
    
       /**
        * Method called when an existing record is updated.
        */
       public boolean onFlushDirty (
          Object entity,
          Serializable id,
          Object[] currentState,
          Object[] previousState,
          Object[] propertyNames,
          Types[] types {
    
          if (entity instanceof TrackableRequest) {
             trackRequest((TrackableRequest) entity);
          }
    
          if (entity instanceof TrackableResponse) {
             trackResponse((TrackableResponse) entity);
          }
    
          if (entity instanceof TrackableSession) {
             trackSession((TrackableSession) entity);
          }
    
          return false;
    
       }
    
       /**
        * Method called when a new record is saved.
        */
       public boolean onSave (
          Object entity,
          Serializable id,
          Object[] state,
          Object[] propertyNames,
          Types[] types {
     
          if (entity instanceof TrackableRequest) {
             trackRequest((TrackableRequest) entity);
          }
    
          if (entity instanceof TrackableResponse) {
             trackResponse((TrackableResponse) entity);
          }
    
          if (entity instanceof TrackableSession) {
             trackSession((TrackableSession) entity);
          }
    
          return false;
       }
    
       // rest of methods elided as they follow default behavior
    }