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