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 }