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 }