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();