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 }