com.mtgi.analytics.servlet.BehaviorTrackingFilter.java Source code

Java tutorial

Introduction

Here is the source code for com.mtgi.analytics.servlet.BehaviorTrackingFilter.java

Source

/* 
 * Copyright 2008-2009 the original author or authors.
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 */

package com.mtgi.analytics.servlet;

import static org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext;

import java.io.IOException;
import java.util.Map;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.springframework.web.context.WebApplicationContext;

import com.mtgi.analytics.BehaviorEvent;
import com.mtgi.analytics.BehaviorTrackingManager;
import com.mtgi.analytics.EventDataElement;

/**
 * <p>A servlet filter which logs all activity to an instance of {@link BehaviorTrackingManager}
 * in the application's Spring context.  All request parameters and any specific
 * response status code is included in the event data.</p>
 * 
 * <p>If there is only one BehaviorTrackingManager in the Spring context, that
 * instance is used automatically.  If there is more than one, which manager the
 * filter should use is configured using the init parameter <code>com.mtgi.analytics.manager</code>.</p>
 * 
 * <p>By default all events generated by this filter will have a type of <code>http-request</code>.
 * An alternate type value can be specified using the filter parameter
 * <code>com.mtgi.analytics.servlet.event</code>.</p>
 */
public class BehaviorTrackingFilter implements Filter {

    private static final Pattern LIST_SEPARATOR = Pattern.compile("[\\r\\n\\s,;]+");

    /** filter parameter specifying the bean name of the BehaviorTrackingManager instance to use in the application spring context. */
    public static final String PARAM_MANAGER_NAME = "com.mtgi.analytics.manager";
    /** filter parameter specifying the eventType value to use when logging behavior tracking events. */
    public static final String PARAM_EVENT_TYPE = "com.mtgi.analytics.servlet.event";
    /** filter parameter specifying a list of parameters to include in logging; defaults to all if unspecified */
    public static final String PARAM_PARAMETERS_INCLUDE = "com.mtgi.analytics.parameters.include";
    /** filter parameter specifying a list of parameters to include in the event name; defaults to none if unspecified */
    public static final String PARAM_PARAMETERS_NAME = "com.mtgi.analytics.parameters.name";

    public static final String ATT_FILTER_REGISTERED = BehaviorTrackingFilter.class.getName() + ".count";

    public static boolean isFiltered(ServletContext context) {
        Integer count = (Integer) context.getAttribute(ATT_FILTER_REGISTERED);
        return count != null && count > 0;
    }

    private ServletContext servletContext;
    private ServletRequestBehaviorTrackingAdapter delegate;

    public void destroy() {
        delegate = null;
        Integer count = (Integer) servletContext.getAttribute(ATT_FILTER_REGISTERED);
        if (count == null || count == 1)
            servletContext.removeAttribute(ATT_FILTER_REGISTERED);
        else
            servletContext.setAttribute(ATT_FILTER_REGISTERED, count - 1);
    }

    public void init(FilterConfig config) throws ServletException {
        servletContext = config.getServletContext();
        WebApplicationContext context = getRequiredWebApplicationContext(servletContext);
        String managerName = config.getInitParameter(PARAM_MANAGER_NAME);

        BehaviorTrackingManager manager;
        if (managerName == null) {
            //if there is no bean name configured, we assume there
            //must be exactly one such bean in the application context.
            Map<?, ?> managers = context.getBeansOfType(BehaviorTrackingManager.class);
            if (managers.isEmpty())
                throw new ServletException(
                        "Unable to find a bean of class " + BehaviorTrackingManager.class.getName()
                                + " in the Spring application context; perhaps it has not been configured?");
            if (managers.size() > 1)
                throw new ServletException("More than one instance of " + BehaviorTrackingManager.class.getName()
                        + " in Spring application context; you must specify which to use with the filter parameter "
                        + PARAM_MANAGER_NAME);

            manager = (BehaviorTrackingManager) managers.values().iterator().next();
        } else {
            //lookup the specified bean name.
            manager = (BehaviorTrackingManager) context.getBean(managerName, BehaviorTrackingManager.class);
        }

        //see if there is an event type name configured.
        String eventType = config.getInitParameter(PARAM_EVENT_TYPE);

        //parameters included in event data
        String params = config.getInitParameter(PARAM_PARAMETERS_INCLUDE);
        String[] parameters = params == null ? null : LIST_SEPARATOR.split(params);

        //parameters included in event name
        String nameParams = config.getInitParameter(PARAM_PARAMETERS_NAME);
        String[] nameParameters = nameParams == null ? null : LIST_SEPARATOR.split(nameParams);

        delegate = new ServletRequestBehaviorTrackingAdapter(eventType, manager, parameters, nameParameters, null);

        //increment count of tracking filters registered in the servlet context.  the filter
        //and alternative request listener check this attribute to make sure both are not registered at once.
        Integer count = (Integer) servletContext.getAttribute(ATT_FILTER_REGISTERED);
        servletContext.setAttribute(ATT_FILTER_REGISTERED, count == null ? 1 : count + 1);
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        //wrap the response so that we can intercept response status if the application
        //sets it.
        BehaviorTrackingResponse btr = new BehaviorTrackingResponse((HttpServletResponse) response);
        BehaviorEvent event = delegate.start(request);
        try {
            chain.doFilter(request, btr);

            //log response codes.
            EventDataElement data = event.getData();
            data.add("response-status", btr.status);
            data.add("response-message", btr.message);

            //if an error code is being sent back, populate the 'error' field of the event with relevant info.
            if (btr.status != null && btr.status >= 400)
                event.setError(btr.status + ": " + btr.message);

        } catch (Throwable error) {
            //log exception messages to event data.
            handleServerError(event, error);
        } finally {
            delegate.stop(event);
        }
    }

    private static final void handleServerError(BehaviorEvent event, Throwable e)
            throws ServletException, IOException {

        event.addData().add("response-status", 500);

        if (e instanceof ServletException) {
            ServletException se = (ServletException) e;
            Throwable cause = se.getRootCause();
            if (cause != null)
                event.setError(cause);
            else
                event.setError(se);
            throw se;
        } else {
            event.setError(e);
        }

        //propagate exception
        if (e instanceof IOException)
            throw (IOException) e;
        if (e instanceof RuntimeException)
            throw (RuntimeException) e;
        //should not get this far in normal execution, but cover this case anyway..
        throw new ServletException(e);
    }

    private static class BehaviorTrackingResponse extends HttpServletResponseWrapper {

        Integer status;
        String message;

        protected BehaviorTrackingResponse(HttpServletResponse response) {
            super(response);
        }

        @Override
        public void sendError(int status, String message) throws IOException {
            this.status = status;
            this.message = message;
            super.sendError(status, message);
        }

        @Override
        public void sendError(int status) throws IOException {
            this.status = status;
            super.sendError(status);
        }

        @Override
        public void setStatus(int status, String message) {
            this.status = status;
            this.message = message;
            super.setStatus(status, message);
        }

        @Override
        public void setStatus(int status) {
            this.status = status;
            super.setStatus(status);
        }

    }
}