View Javadoc

1   package com.atlassian.johnson.spring.web.filter;
2   
3   import org.slf4j.Logger;
4   import org.slf4j.LoggerFactory;
5   import org.springframework.web.context.WebApplicationContext;
6   import org.springframework.web.filter.DelegatingFilterProxy;
7   
8   import javax.servlet.*;
9   import java.io.IOException;
10  
11  /**
12   * A more permissive subclass of Spring's {@code DelegatingFilterProxy} which bypasses the filter if there is no
13   * Spring {@code WebApplicationContext} available.
14   * <p/>
15   * The default behaviour of the base class is to throw an {@code IllegalStateException} if, by the time the filter
16   * is invoked for the first time, no context is available. This subclass bypasses that behaviour and invokes the
17   * filter chain instead. This is necessary to allow redirects to the configured Johnson error page to make it through
18   * a filter chain which includes delegating filters.
19   * <p/>
20   * Note: This class preserves the behaviour from the base class where the proxy will fail if it cannot find a bean
21   * with the correct name. It only suppresses failing if Spring is completely unavailable.
22   */
23  public class BypassableDelegatingFilterProxy extends DelegatingFilterProxy
24  {
25      private static final Logger LOG = LoggerFactory.getLogger(BypassableDelegatingFilterProxy.class);
26  
27      private volatile boolean bypassable = true;
28  
29      /**
30       * Bypasses execution of the filter if no {@code WebApplicationContext} is available, delegating directly to the
31       * filter chain, or performs normal filtering if a context is available.
32       * <p/>
33       * Once a {@code WebApplicationContext} is available once, this filter will never bypass again (even if, for some
34       * reason, the context later disappears).
35       *
36       * @param request     the servlet request to filter
37       * @param response    the servlet response
38       * @param filterChain the filter chain
39       * @throws ServletException May be thrown by the base class or the chain; never thrown locally.
40       * @throws IOException      May be thrown by the base class or the chain; never thrown locally.
41       */
42      @Override
43      public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
44              throws ServletException, IOException
45      {
46          if (bypassable && findWebApplicationContext() == null)
47          {
48              LOG.warn("Bypassing [{}]; no Spring WebApplicationContext is available", getFilterName());
49              filterChain.doFilter(request, response);
50          }
51          else
52          {
53              LOG.trace("Found Spring WebApplicationContext; attempting to invoke delegate");
54              super.doFilter(request, response, filterChain);
55          }
56      }
57  
58      /**
59       * As an optimisation to this approach, once the delegate is initialised we set a flag indicating the filter is
60       * no longer bypassable. That way we don't continue to resolve the {@code WebApplicationContext} unnecessarily
61       * on every request.
62       *
63       * @param wac the resolved {@code WebApplicationContext}
64       * @return the targeted filter
65       * @throws ServletException See documentation for the base class.
66       * @see DelegatingFilterProxy#initDelegate(org.springframework.web.context.WebApplicationContext)
67       */
68      @Override
69      protected Filter initDelegate(WebApplicationContext wac) throws ServletException
70      {
71          LOG.debug("Filter [{}] is no longer bypassable; the WebApplicationContext is available", getFilterName());
72          bypassable = false;
73  
74          return super.initDelegate(wac);
75      }
76  }