From 948aa4f589c65581a6d294095670cb83b65297da Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Mon, 30 May 2011 12:57:19 +0000 Subject: [PATCH] Introduce DelegatingFilterProxy constructors Issue: SPR-7672 --- .../web/filter/DelegatingFilterProxy.java | 147 +++++++++++++++--- .../filter/DelegatingFilterProxyTests.java | 126 ++++++++++++++- 2 files changed, 250 insertions(+), 23 deletions(-) diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java b/org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java index a349c7defc..dcebb69937 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java @@ -23,6 +23,8 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; @@ -50,22 +52,38 @@ import org.springframework.web.context.support.WebApplicationContextUtils; * of the {@code Filter.init} and {@code Filter.destroy} lifecycle methods * on the target bean, letting the servlet container manage the filter lifecycle. * - *

This class is inspired by Acegi Security's FilterToBeanProxy class, - * written by Ben Alex. + *

As of Spring 3.1, {@code DelegatingFilterProxy} has been updated to optionally accept + * constructor parameters when using Servlet 3.0's instance-based filter registration + * methods, usually in conjunction with Spring 3.1's + * {@link org.springframework.web.WebApplicationInitializer} SPI. These constructors allow + * for providing the delegate Filter bean directly, or providing the application context + * and bean name to fetch, avoiding the need to look up the application context from the + * ServletContext. + * + *

This class was originally inspired by Spring Security's {@code FilterToBeanProxy} + * class, written by Ben Alex. * * @author Juergen Hoeller * @author Sam Brannen + * @author Chris Beams * @since 1.2 * @see #setTargetBeanName * @see #setTargetFilterLifecycle * @see javax.servlet.Filter#doFilter * @see javax.servlet.Filter#init * @see javax.servlet.Filter#destroy + * @see #DelegatingFilterProxy(Filter) + * @see #DelegatingFilterProxy(String) + * @see #DelegatingFilterProxy(String, WebApplicationContext) + * @see javax.servlet.ServletContext#addFilter(String, Filter) + * @see org.springframework.web.WebApplicationInitializer */ public class DelegatingFilterProxy extends GenericFilterBean { private String contextAttribute; + private WebApplicationContext webApplicationContext; + private String targetBeanName; private boolean targetFilterLifecycle = false; @@ -75,6 +93,78 @@ public class DelegatingFilterProxy extends GenericFilterBean { private final Object delegateMonitor = new Object(); + /** + * Create a new {@code DelegatingFilterProxy}. For traditional (pre-Servlet 3.0) use + * in {@code web.xml}. + * @see #setTargetBeanName(String) + */ + public DelegatingFilterProxy() { + } + + /** + * Create a new {@code DelegatingFilterProxy} with the given {@link Filter} delegate. + * Bypasses entirely the need for interacting with a Spring application context, + * specifying the {@linkplain #setTargetBeanName target bean name}, etc. + *

For use in Servlet 3.0+ environments where instance-based registration of + * filters is supported. + * @param delegate the {@code Filter} instance that this proxy will delegate to and + * manage the lifecycle for (must not be {@code null}). + * @see #doFilter(ServletRequest, ServletResponse, FilterChain) + * @see #invokeDelegate(Filter, ServletRequest, ServletResponse, FilterChain) + * @see #destroy() + * @see #setEnvironment(org.springframework.core.env.Environment) + */ + public DelegatingFilterProxy(Filter delegate) { + Assert.notNull(delegate, "delegate Filter object must not be null"); + this.delegate = delegate; + } + + /** + * Create a new {@code DelegatingFilterProxy} that will retrieve the named target + * bean from the Spring {@code WebApplicationContext} found in the {@code ServletContext} + * (either the 'root' application context or the context named by + * {@link #setContextAttribute}). + *

For use in Servlet 3.0+ environments where instance-based registration of + * filters is supported. + *

The target bean must implement the standard Servlet Filter. + * @param targetBeanName name of the target filter bean to look up in the Spring + * application context (must not be {@code null}). + * @see #findWebApplicationContext() + * @see #setEnvironment(org.springframework.core.env.Environment) + */ + public DelegatingFilterProxy(String targetBeanName) { + this(targetBeanName, null); + } + + /** + * Create a new {@code DelegatingFilterProxy} that will retrieve the named target + * bean from the given Spring {@code WebApplicationContext}. + *

For use in Servlet 3.0+ environments where instance-based registration of + * filters is supported. + *

The target bean must implement the standard Servlet Filter interface. + *

The given {@code WebApplicationContext} may or may not be refreshed when passed + * in. If it has not, and if the context implements {@link ConfigurableApplicationContext}, + * a {@link ConfigurableApplicationContext#refresh() refresh()} will be attempted before + * retrieving the named target bean. + *

This proxy's {@code Environment} will be inherited from the given + * {@code WebApplicationContext}. + * @param targetBeanName name of the target filter bean in the Spring application + * context (must not be {@code null}). + * @param wac the application context from which the target filter will be retrieved; + * if {@code null}, an application context will be looked up from {@code ServletContext} + * as a fallback. + * @see #findWebApplicationContext() + * @see #setEnvironment(org.springframework.core.env.Environment) + */ + public DelegatingFilterProxy(String targetBeanName, WebApplicationContext wac) { + Assert.hasText(targetBeanName, "target Filter bean name must not be null or empty"); + this.setTargetBeanName(targetBeanName); + this.webApplicationContext = wac; + if (wac != null) { + this.setEnvironment(wac.getEnvironment()); + } + } + /** * Set the name of the ServletContext attribute which should be used to retrieve the * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean. @@ -131,18 +221,20 @@ public class DelegatingFilterProxy extends GenericFilterBean { @Override protected void initFilterBean() throws ServletException { - // If no target bean name specified, use filter name. - if (this.targetBeanName == null) { - this.targetBeanName = getFilterName(); - } - - // Fetch Spring root application context and initialize the delegate early, - // if possible. If the root application context will be started after this - // filter proxy, we'll have to resort to lazy initialization. synchronized (this.delegateMonitor) { - WebApplicationContext wac = findWebApplicationContext(); - if (wac != null) { - this.delegate = initDelegate(wac); + if (this.delegate == null) { + // If no target bean name specified, use filter name. + if (this.targetBeanName == null) { + this.targetBeanName = getFilterName(); + } + + // Fetch Spring root application context and initialize the delegate early, + // if possible. If the root application context will be started after this + // filter proxy, we'll have to resort to lazy initialization. + WebApplicationContext wac = findWebApplicationContext(); + if (wac != null) { + this.delegate = initDelegate(wac); + } } } } @@ -180,16 +272,33 @@ public class DelegatingFilterProxy extends GenericFilterBean { /** - * Retrieve a WebApplicationContext from the ServletContext - * attribute with the {@link #setContextAttribute configured name}. The - * WebApplicationContext must have already been loaded and stored in the - * ServletContext before this filter gets initialized (or invoked). + * Return the {@code WebApplicationContext} passed in at construction time, if available. + * Otherwise, attempt to retrieve a {@code WebApplicationContext} from the + * {@code ServletContext} attribute with the {@linkplain #setContextAttribute + * configured name} if set. Otherwise look up a {@code WebApplicationContext} under + * the well-known "root" application context attribute. The + * {@code WebApplicationContext} must have already been loaded and stored in the + * {@code ServletContext} before this filter gets initialized (or invoked). *

Subclasses may override this method to provide a different - * WebApplicationContext retrieval strategy. - * @return the WebApplicationContext for this proxy, or null if not found + * {@code WebApplicationContext} retrieval strategy. + * @return the {@code WebApplicationContext} for this proxy, or {@code null} if not + * found + * @see #DelegatingFilterProxy(String, WebApplicationContext) * @see #getContextAttribute() + * @see WebApplicationContextUtils#getWebApplicationContext(javax.servlet.ServletContext) + * @see WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE */ protected WebApplicationContext findWebApplicationContext() { + if (this.webApplicationContext != null) { + // the user has injected a context at construction time -> use it + if (this.webApplicationContext instanceof ConfigurableApplicationContext) { + if (!((ConfigurableApplicationContext)this.webApplicationContext).isActive()) { + // the context has not yet been refreshed -> do so before returning it + ((ConfigurableApplicationContext)this.webApplicationContext).refresh(); + } + } + return this.webApplicationContext; + } String attrName = getContextAttribute(); if (attrName != null) { return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); diff --git a/org.springframework.web/src/test/java/org/springframework/web/filter/DelegatingFilterProxyTests.java b/org.springframework.web/src/test/java/org/springframework/web/filter/DelegatingFilterProxyTests.java index 54034f8070..25447b2bd7 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/filter/DelegatingFilterProxyTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/filter/DelegatingFilterProxyTests.java @@ -1,4 +1,6 @@ -/* Copyright 2004, 2005 Acegi Technology Pty Limited +/* + * Copyright 2004, 2005 Acegi Technology Pty Limited + * Copyright 2006-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +27,8 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import junit.framework.TestCase; - +import static org.junit.Assert.*; +import org.junit.Test; import org.springframework.mock.web.MockFilterConfig; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -36,10 +38,12 @@ import org.springframework.web.context.support.StaticWebApplicationContext; /** * @author Juergen Hoeller + * @author Chris Beams * @since 08.05.2005 */ -public class DelegatingFilterProxyTests extends TestCase { +public class DelegatingFilterProxyTests { + @Test public void testDelegatingFilterProxy() throws ServletException, IOException { ServletContext sc = new MockServletContext(); @@ -67,6 +71,118 @@ public class DelegatingFilterProxyTests extends TestCase { assertNull(targetFilter.filterConfig); } + @Test + public void testDelegatingFilterProxyAndCustomContextAttribute() throws ServletException, IOException { + ServletContext sc = new MockServletContext(); + + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.setServletContext(sc); + wac.registerSingleton("targetFilter", MockFilter.class); + wac.refresh(); + sc.setAttribute("CUSTOM_ATTR", wac); + + MockFilter targetFilter = (MockFilter) wac.getBean("targetFilter"); + + MockFilterConfig proxyConfig = new MockFilterConfig(sc); + proxyConfig.addInitParameter("targetBeanName", "targetFilter"); + proxyConfig.addInitParameter("contextAttribute", "CUSTOM_ATTR"); + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy(); + filterProxy.init(proxyConfig); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + filterProxy.doFilter(request, response, null); + + assertNull(targetFilter.filterConfig); + assertEquals(Boolean.TRUE, request.getAttribute("called")); + + filterProxy.destroy(); + assertNull(targetFilter.filterConfig); + } + + @Test + public void testDelegatingFilterProxyWithFilterDelegateInstance() throws ServletException, IOException { + MockFilter targetFilter = new MockFilter(); + + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy(targetFilter); + filterProxy.init(new MockFilterConfig(new MockServletContext())); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + filterProxy.doFilter(request, response, null); + + assertNull(targetFilter.filterConfig); + assertEquals(Boolean.TRUE, request.getAttribute("called")); + + filterProxy.destroy(); + assertNull(targetFilter.filterConfig); + } + + @Test + public void testDelegatingFilterProxyWithTargetBeanName() throws ServletException, IOException { + MockServletContext sc = new MockServletContext(); + + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.setServletContext(sc); + wac.registerSingleton("targetFilter", MockFilter.class); + wac.refresh(); + sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + + MockFilter targetFilter = (MockFilter) wac.getBean("targetFilter"); + + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("targetFilter"); + filterProxy.init(new MockFilterConfig(sc)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + filterProxy.doFilter(request, response, null); + + assertNull(targetFilter.filterConfig); + assertEquals(Boolean.TRUE, request.getAttribute("called")); + + filterProxy.destroy(); + assertNull(targetFilter.filterConfig); + } + + @Test + public void testDelegatingFilterProxyWithTargetBeanNameAndNotYetRefreshedApplicationContext() throws ServletException, IOException { + MockServletContext sc = new MockServletContext(); + + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.setServletContext(sc); + wac.registerSingleton("targetFilter", MockFilter.class); + // wac.refresh(); + // note that the context is not set as the ROOT attribute in the ServletContext! + + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("targetFilter", wac); + filterProxy.init(new MockFilterConfig(sc)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + filterProxy.doFilter(request, response, null); + + MockFilter targetFilter = (MockFilter) wac.getBean("targetFilter"); + + assertNull(targetFilter.filterConfig); + assertEquals(Boolean.TRUE, request.getAttribute("called")); + + filterProxy.destroy(); + assertNull(targetFilter.filterConfig); + } + + @Test(expected=IllegalStateException.class) + public void testDelegatingFilterProxyWithTargetBeanNameAndNoApplicationContext() throws ServletException, IOException { + MockServletContext sc = new MockServletContext(); + + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("targetFilter", null); + filterProxy.init(new MockFilterConfig(sc)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + filterProxy.doFilter(request, response, null); // throws + } + + @Test public void testDelegatingFilterProxyWithFilterName() throws ServletException, IOException { ServletContext sc = new MockServletContext(); @@ -93,6 +209,7 @@ public class DelegatingFilterProxyTests extends TestCase { assertNull(targetFilter.filterConfig); } + @Test public void testDelegatingFilterProxyWithLazyContextStartup() throws ServletException, IOException { ServletContext sc = new MockServletContext(); @@ -120,6 +237,7 @@ public class DelegatingFilterProxyTests extends TestCase { assertNull(targetFilter.filterConfig); } + @Test public void testDelegatingFilterProxyWithTargetFilterLifecycle() throws ServletException, IOException { ServletContext sc = new MockServletContext();