Java tutorial
/* * 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 } }