We have used a filter and synchronizing on the session as others have suggested. Here is the code which we found sometime ago. I believe we made minor modifications to the author's original code but its been so long ...
HTH sean /* * (c) 2004, Kevin Chipalowsky ([EMAIL PROTECTED]) and * Ivelin Ivanov ([EMAIL PROTECTED]) * * Released under terms of the Artistic License * http://www.opensource.org/licenses/artistic-license.php */ import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; 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.HttpSession; import org.apache.log4j.Logger; /** * Use this filter to synchronize requests to your web application and * reduce the maximum load that each individual user can put on your * web application. Requests will be synchronized per session. When more * than one additional requests are made while a request is in process, * only the most recent of the additional requests will actually be * processed. * <p> * If a user makes two requests, A and B, then A will be processed first * while B waits. When A finishes, B will be processed. * <p> * If a user makes three or more requests (e.g. A, B, and C), then the * first will be processed (A), and then after it finishes the last will * be processed (C), and any intermediate requests will be skipped (B). * <p> * There are two additional limitiations: * <ul> * <li>Requests will be excluded from filtering if their URI matches * one of the exclusion patterns. There will be no synchronization * performed if a request matches one of those patterns.</li> * <li>Requests wait a maximum of 5 seconds, which can be overridden * per URI pattern in the filter's configuration.</li> * </ul> * * @author Kevin Chipalowsky and Ivelin Ivanov */ public class RequestControlFilter implements Filter { // initialize logging private static final Logger log = Logger.getLogger(RequestControlFilter.class); /** * Initialize this filter by reading its configuration parameters * * @param config Configuration from web.xml file * @throws ServletException */ public void init( FilterConfig config ) throws ServletException { log.debug("filter initializing"); // parse all of the initialization parameters, collecting the exclude // patterns and the max wait parameters Enumeration enum = config.getInitParameterNames(); excludePatterns = new LinkedList(); maxWaitDurations = new HashMap(); while( enum.hasMoreElements() ) { String paramName = ( String )enum.nextElement(); String paramValue = config.getInitParameter( paramName ); if( paramName.startsWith( "excludePattern" ) ) { // compile the pattern only this once Pattern excludePattern = Pattern.compile( paramValue ); excludePatterns.add( excludePattern ); } else if( paramName.startsWith( "maxWaitMilliseconds." ) ) { // the delay gets parsed from the parameter name String durationString = paramName.substring( "maxWaitMilliseconds.".length() ); int endDuration = durationString.indexOf( '.' ); if( endDuration != -1 ) { durationString = durationString.substring( 0, endDuration ); } Long duration = new Long( durationString ); // compile the corresponding pattern, and store it with this delay in the map Pattern waitPattern = Pattern.compile( paramValue ); maxWaitDurations.put( waitPattern, duration ); } } } /** * Called with the filter is no longer needed. */ public void destroy() { // there is nothing to do } /** * Synchronize the request and then either process it or skip it, * depending on what other requests current exist for this session. * See the description of this class for more details. * * @param request ServletRequest * @param response ServletResponse * @param chain FilterChain * @throws IOException * @throws ServletException */ public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest)request; HttpSession session = httpRequest.getSession(); // if this request is excluded from the filter, then just process it if( !isFilteredRequest( httpRequest ) ) { chain.doFilter( request, response ); return; } synchronized( getSynchronizationObject( session ) ) { // if another request is being processed, then wait if( isRequestInProcess( session ) ) { // Put this request in the queue and wait enqueueRequest( httpRequest ); if( !waitForRelease( httpRequest ) ) { // this request was replaced in the queue by another request, // so it need not be processed return; } } // lock the session, so that no other requests are processed until this one finishes setRequestInProgress( httpRequest ); } // process this request, and then release the session lock regardless of // any exceptions thrown farther down the chain. try { chain.doFilter( request, response ); } finally { releaseQueuedRequest( httpRequest ); } } /** * Get a synchronization object for this session * * @param session HttpSession - * @return Object */ private static synchronized Object getSynchronizationObject(HttpSession session) { // get the object from the session. If it does not yet exist, // then create one. Object syncObj = session.getAttribute( SYNC_OBJECT_KEY ); if( syncObj == null ) { syncObj = new Object(); session.setAttribute( SYNC_OBJECT_KEY, syncObj ); } return syncObj; } /** * Record that a request is in process so that the filter blocks additional * requests until this one finishes. * * @param request HttpServletRequest */ private void setRequestInProgress(HttpServletRequest request) { HttpSession session = request.getSession(); session.setAttribute( REQUEST_IN_PROCESS, request ); } /** * Release the next waiting request, because the current request * has just finished. * * @param request The request that just finished */ private void releaseQueuedRequest( HttpServletRequest request ) { HttpSession session = request.getSession(); synchronized( getSynchronizationObject( session ) ) { // if this request is still the current one (i.e., it didn't run for too // long and result in another request being processed), then clear it // and thus release the lock if( session.getAttribute( REQUEST_IN_PROCESS ) == request ) { session.removeAttribute( REQUEST_IN_PROCESS ); getSynchronizationObject( session ).notify(); } } } /** * Is this server currently processing another request for this session? * * @param session The request's session * @return true if the server is handling another request for this session */ private boolean isRequestInProcess( HttpSession session ) { return session.getAttribute( REQUEST_IN_PROCESS ) != null; } /** * Wait for this server to finish with its current request so that * it can begin processing our next request. This method also detects if * its request is replaced by another request in the queue. * * @param request Wait for this request to be ready to run * @return true if this request may be processed, or false if this * request was replaced by another in the queue. */ private boolean waitForRelease( HttpServletRequest request ) { HttpSession session = request.getSession(); // wait for the currently running request to finish, or until this // thread has waited the maximum amount of time try { getSynchronizationObject( session ).wait( getMaxWaitTime( request ) ); } catch( InterruptedException ie ) { return false; } // This request can be processed now if it hasn't been replaced // in the queue return request == session.getAttribute( REQUEST_QUEUE ); } /** * Put a new request in the queue. This new request will replace * any other requests that were waiting. * * @param request The request to queue */ private void enqueueRequest( HttpServletRequest request ) { HttpSession session = request.getSession(); // Put this request in the queue, replacing whoever was there before session.setAttribute( REQUEST_QUEUE, request ); // if another request was waiting, notify it so it can discover that // it was replaced getSynchronizationObject( session ).notify(); } /** * What is the maximum wait time (in milliseconds) for this request * * @param request HttpServletRequest - * @return Maximum number of milliseconds to hold this request in the queue */ private long getMaxWaitTime( HttpServletRequest request ) { // look for a Pattern that matches the request's path String path = request.getRequestURI(); Iterator patternIter = maxWaitDurations.keySet().iterator(); while( patternIter.hasNext() ) { Pattern p = (Pattern)patternIter.next(); Matcher m = p.matcher( path ); if( m.matches() ) { // this pattern matches. At most, how long can this request wait? Long maxDuration = (Long)maxWaitDurations.get( p ); return maxDuration.longValue(); } } // If no pattern matches the path, return the default value return DEFAULT_DURATION; } /** * Look through the filter's configuration, and determine whether or not it * should synchronize this request with others. * * @param request HttpServletRequest * @return boolean */ private boolean isFilteredRequest(HttpServletRequest request) { // iterate through the exclude patterns. If one matches this path, // then the request is excluded. String path = request.getRequestURI(); Iterator patternIter = excludePatterns.iterator(); while( patternIter.hasNext() ) { Pattern p = (Pattern)patternIter.next(); Matcher m = p.matcher( path ); if( m.matches() ) { // at least one of the patterns excludes this request return false; } } // this path is not excluded return true; } /** A list of Pattern objects that match paths to exclude */ private LinkedList excludePatterns; /** A map from Pattern to max wait duration (Long objects) */ private HashMap maxWaitDurations; /** The session attribute key for the request currently being processed */ private final static String REQUEST_IN_PROCESS = "RequestControlFilter.requestInProcess"; /** The session attribute key for the request currently waiting in the queue */ private final static String REQUEST_QUEUE = "RequestControlFilter.requestQueue"; /** The session attribute key for the synchronization object */ private final static String SYNC_OBJECT_KEY = "RequestControlFilter.sessionSync"; /** The default maximum number of milliseconds to wait for a request */ private final static long DEFAULT_DURATION = 5000; }

