com.anite.ocelot.MultipleRequestFilter.java Source code

Java tutorial

Introduction

Here is the source code for com.anite.ocelot.MultipleRequestFilter.java

Source

/*
 * Copyright 2004 Anite - Central Government Division
 *    http://www.anite.com/publicsector
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.anite.ocelot;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Matthew.Norris
 * 
 * This filter handles multiple requests on a single session
 * 
 * Only the first request will be honoured (sent down the filter chain) - all
 * other requests will be ignored EXCEPT for the final one, which will receive
 * the response generated by the first request
 * 
 * It is worth noting this will NOT work in a cluster unless sticky sessions are
 * used as there is not way of running synchronized safely over such as cluster.
 * 
 * For some screens (specifically downloads using the response writer) this does not
 * work. So simple add a parameter called 'skipocelot' to the request. This will prevent 
 * the issue.
 */
public class MultipleRequestFilter implements Filter {

    public static final String SKIP_OCELOT = "skipocelot";

    /** interval to sleep this thread for whilst waiting for first response to
     /* complete
     */
    private static final int SLEEP_INTERVAL = 100;

    /**
     * name of attribute in HttpSession that contains the first request's
     * response
     * constant for "MultipleRequestFilter._FirstResponse"
     */
    private static final String SESSION_FIRSTRESPONSE = "MultipleRequestFilter._FirstResponse";

    /**
     * name of attribute in HttpSession that contains the reverse stack of
    * requests made
     * constant for "MultipleRequestFilter._RequestStack"
     */
    private static final String SESSION_REQUESTSTACK = "MultipleRequestFilter._RequestStack";

    /** logging */
    private static Log log = LogFactory.getLog(MultipleRequestFilter.class);

    /**
     * {@inheritDoc}
     */
    public void init(FilterConfig arg0) throws ServletException {
        // does nothing
    }

    /**
     * {@inheritDoc}
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {

            HttpServletRequest httpRequest = (HttpServletRequest) request;

            if (checkIfShouldHandle(httpRequest)) {

                handleRequest(request, response, filterChain, httpRequest);
                return;
            }
        }

        // just call the next filter in the chain
        filterChain.doFilter(request, response);

    }

    /**
     *  
     * ensure we only get activated for requests that were kicked off
     * from submits, etc
     * 
     * Note: a request can also be for images, JavaScript, CSS files,
     * etc, so we have to be careful to only deal with appropriate
     * multiple requests
     *       
     * @param httpRequest
     * @return
     */
    private boolean checkIfShouldHandle(HttpServletRequest httpRequest) {

        if (httpRequest.getPathInfo() != null) {
            if (httpRequest.getPathInfo().startsWith("/action/")) {
                return false;
            } else if (httpRequest.getPathInfo().startsWith("/template/")) {
                return false;
            }
        } else {
            return false;
        }

        if (httpRequest.getParameter(SKIP_OCELOT) != null) {
            return false;
        }

        return true;
    }

    /**
     * 
     * starts the multiple request handling system
     * 
     * @param request
     * @param response
     * @param filterChain
     * @param httpRequest
     * @throws IOException
     * @throws ServletException
     */
    private void handleRequest(ServletRequest request, ServletResponse response, FilterChain filterChain,
            HttpServletRequest httpRequest) throws IOException, ServletException {
        HttpSession httpSession = httpRequest.getSession(true);

        boolean isFirst = false;
        isFirst = addToStack(httpRequest, httpSession, isFirst);

        if (isFirst) {
            /*
             * this request is the first received for the session, so save the
             * response given by the filterChain for later use
             */
            saveResponse(request, response, filterChain, httpSession);
        } else {
            /*
             * we're not the first request
             * 
             * this section waits for this request to be the next to be
             * processed off the bottom of the stack
             *  
             */
            boolean ignoreRequest = false;

            ignoreRequest = waitForRequest(httpRequest, httpSession, ignoreRequest);

            if (ignoreRequest) { /*
                                 * this request has been invalidated by a
                                 * later request, so we just return
                                 */
                return;
            }

            /*
             * this must be the final request, so we serve back the first
             * response that we saved
             */
            serveFirstResponse(request, response, filterChain, httpSession);
        }
        /*
         * tidy up and mess we've left behind in the HttpSession
         */
        tidySession(httpSession);
    }

    /**
     * tidys up the session
     * 
     * @param httpSession
     */
    private void tidySession(HttpSession httpSession) {
        /*
         * final sync'd tidy up on things we hold in the session
         *  
         */
        synchronized (httpSession) {
            List list = (List) httpSession.getAttribute(SESSION_REQUESTSTACK);
            if (log.isInfoEnabled()) {
                log.info("Removing self from list");
            }
            /*
             * at this stage the current request should ALWAYS be at position
             * ZERO in the stack
             */
            list.remove(0);
            if (list.size() == 0) {
                /*
                 * no more items in the stack, so remove everything we may have
                 * left in here
                 * 
                 * (the stack and the page that was built on the first request)
                 *  
                 */
                if (log.isInfoEnabled()) {
                    log.info("List now empty");
                }
                httpSession.removeAttribute(SESSION_REQUESTSTACK);
                httpSession.removeAttribute(SESSION_FIRSTRESPONSE);
            }
        }
    }

    /**
     * 
     * serves back the first response (stored in the session) to the current
     * request
     * 
     * @param request
     * @param response
     * @param filterChain
     * @param httpSession
     * @throws IOException
     * @throws ServletException
     */
    private void serveFirstResponse(ServletRequest request, ServletResponse response, FilterChain filterChain,
            HttpSession httpSession) throws IOException, ServletException {
        /*
         * check to see if the first request's page has been stored for us
         * 
         * At this stage, we should ALWAYS have the page, but I guess in a
         * multi-thread environment all sorts of weirdness is possible. So,
         * just to be safe, we dont assume there is a page, we check for it.
         *  
         */
        String realPage = (String) httpSession.getAttribute(SESSION_FIRSTRESPONSE);
        if (realPage == null) {
            /*
             * "Coping with Weirdness"
             * 
             * (a) something went wrong with the thread syncing or (b) we got
             * an InterruptedException
             * 
             * Either way, we call down on the filter chain as if we were the
             * first request.
             *  
             */
            filterChain.doFilter(request, response);
        } else {
            /*
             * We have the first request page, so send it back to the browser
             *  
             */
            if (log.isInfoEnabled()) {
                log.info("Forwarding RealPage response");
            }
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.getWriter().write(realPage);
        }
    }

    /**
     * 
     * saves the response to the current request in the HttpSession for use by
     * later requests
     * 
     * @param request
     * @param response
     * @param filterChain
     * @param httpSession
     * @throws IOException
     * @throws ServletException
     */
    private void saveResponse(ServletRequest request, ServletResponse response, FilterChain filterChain,
            HttpSession httpSession) throws IOException, ServletException {
        /*
         * 
         * this is the first request made, so we honour it
         *  
         */

        // wrap the response with our cached implementation
        CachedHttpResponse httpResponse = new CachedHttpResponse((HttpServletResponse) response);

        // call the next filter in the chain
        filterChain.doFilter(request, httpResponse);

        // cast response writer back into our own cached writer
        CachedPrintWriter cpw = (CachedPrintWriter) httpResponse.getWriter();

        // save the page that would go to the browser into the session for
        // later use
        if (log.isInfoEnabled()) {
            log.info("Set RealPage Contents");
        }
        httpSession.setAttribute(SESSION_FIRSTRESPONSE, cpw.getBuffer());
    }

    /**
     * 
     * waits for the current request to become the active request in the
     * request Stack
     * 
     * Note : The request stack works on a first in first out principle
     * 
     * @param httpRequest
     * @param httpSession
     * @param ignoreRequest
     * @return
     */
    private boolean waitForRequest(HttpServletRequest httpRequest, HttpSession httpSession, boolean ignoreRequest) {
        while (true) {

            /*
             * check to see if this request is the next in the chain
             * 
             * This is done in a sync block around the session to avoid clashes
             * on add/remove from the stack
             *  
             */
            synchronized (httpSession) {
                List list = (List) httpSession.getAttribute(SESSION_REQUESTSTACK);

                if (list.get(0).equals(httpRequest)) {
                    if (list.size() > 1) {
                        /*
                         * more than one request left in the stack, so dont
                         * bother sending back the saved page - the browser
                         * would ignore it anyway
                         */
                        list.remove(0);
                        ignoreRequest = true;
                        if (log.isInfoEnabled()) {
                            log.info("Ignored Request: List size == " + list.size());
                        }
                    }
                    break;
                }
            }
            // we're not next, so go to sleep for a while
            // NOTE: I know this isnt the best method, I should probably lock
            // the thread or something
            try {
                Thread.sleep(SLEEP_INTERVAL);
            } catch (InterruptedException e) {
                // I guess we should exit if this happens...
                break;
            }

        }
        return ignoreRequest;
    }

    /**
     * 
     * adds the request to the Request Stack held in the HttpSession
     * 
     * If the request is the first item in the stack the return value will be
     * true
     * 
     * @param httpRequest
     * @param httpSession
     * @param isFirst
     * @return
     */
    private boolean addToStack(HttpServletRequest httpRequest, HttpSession httpSession, boolean isFirst) {
        /*
         * sync around the session -
         * 
         * we store a stack of requests as well as the page generated by the
         * first request in here so to ensure multiple threads dont get their
         * kecks in a twist, this block ensure only one thread at a time is
         * modifying this info
         *  
         */
        synchronized (httpSession) {
            /*
             * attempt to get the stack of requests out of the session
             */
            List list = (List) httpSession.getAttribute(SESSION_REQUESTSTACK);
            if (list == null) {
                /*
                 * 
                 * no stack, so we must be the first
                 *  
                 */
                if (log.isInfoEnabled()) {
                    log.info("First Request");
                }
                list = new ArrayList();
                isFirst = true;
                httpSession.setAttribute(SESSION_REQUESTSTACK, list);
            }
            /*
             * 
             * add this request to the stack - it should get added to the end
             * of the list
             *  
             */
            list.add(httpRequest);
            if (log.isInfoEnabled()) {
                log.info("List size == " + list.size());
            }
        }
        return isFirst;
    }

    /**
     * {@inheritDoc}
     */
    public void destroy() {
        // does nothing
    }

}