diff --git a/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java b/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java
new file mode 100644
index 0000000000..4c948e87fe
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java
@@ -0,0 +1,248 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.util;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+/**
+ * Delegates Filter requests to a Spring-managed bean.
+ *
+ * This class acts as a proxy on behalf of a target Filter that
+ * is defined in the Spring bean context. It is necessary to specify which
+ * target Filter should be proxied as a filter initialization
+ * parameter.
+ *
+ * On filter initialisation, the class will use Spring's {@link
+ * WebApplicationContextUtils#getWebApplicationContext(ServletContext sc)}
+ * method to obtain an ApplicationContext instance. It will
+ * expect to find the target Filter in this
+ * ApplicationContext.
+ *
+ * To use this filter, it is necessary to specify one of the following + * filter initialization parameters: + *
+ *targetClass indicates the class of the target
+ * Filter defined in the bean context. The only requirements are
+ * that this target class implements the javax.servlet.Filter
+ * interface and at least one instance is available in the
+ * ApplicationContext.targetBean indicates the bean name of the target class.
+ * targetBean
+ * takes priority.
+ *
+ * An additional initialization parameter, init, is also
+ * supported. If set to "lazy" the initialization will take
+ * place on the first HTTP request, rather than at filter creation time. This
+ * makes it possible to use FilterToBeanProxy with the Spring
+ * ContextLoaderServlet. Where possible you should not use this
+ * initialization parameter, instead using ContextLoaderListener.
+ *
+// * <bean id="filterChain" class="net.sf.acegisecurity.FilterChain"> +// * <property name="filters"> +// * <value> +// * channelProcessingFilter=/* +// * authenticationProcessingFilter=/* +// * basicProcessingFilter=/* +// * sessionIntegrationFilter=/* +// * securityEnforcementFilter=/* +// * </value> +// * </property> +// * </bean> +// *+ * + * @author Carlos Sanchez + * @version $Id$ + */ +public class FilterChainProxy + implements Filter, InitializingBean +{ + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(FilterChainProxy.class); + + //~ Instance fields + // ======================================================== + + private Filter delegate; + + private List filters; + + private FilterConfig filterConfig; + + private boolean initialized = false; + + private FilterInvocationDefinitionSource filterInvocationDefinitionSource; + + //~ Methods + // ================================================================ + + public void setFilterInvocationDefinitionSource( + FilterInvocationDefinitionSource filterInvocationDefinitionSource) { + this.filterInvocationDefinitionSource = filterInvocationDefinitionSource; + } + + public FilterInvocationDefinitionSource getFilterInvocationDefinitionSource() { + return filterInvocationDefinitionSource; + } + + public void destroy() + { + Iterator it = filters.iterator(); + while ( it.hasNext() ) + { + Filter filter = (Filter) it.next(); + if ( filter != null ) + { + filter.destroy(); + } + } + } + + public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, + ServletException + { + if ( !initialized ) + { + doInit(); + } + + Iterator it = filters.iterator(); + while ( it.hasNext() ) + { + Filter filter = (Filter) it.next(); + filter.doFilter( request, response, chain ); + } + } + + public void init( FilterConfig filterConfig ) throws ServletException + { + this.filterConfig = filterConfig; + + String strategy = filterConfig.getInitParameter( "init" ); + + if ( (strategy != null) && strategy.toLowerCase().equals( "lazy" ) ) + { + return; + } + + doInit(); + } + + /** + * Allows test cases to override where application context obtained from. + * + * @param filterConfig + * which can be used to find the
ServletContext
+ * @return the Spring application context
+ */
+ protected ApplicationContext getContext( FilterConfig filterConfig )
+ {
+ return WebApplicationContextUtils.getRequiredWebApplicationContext( filterConfig.getServletContext() );
+ }
+
+ private void doInit() throws ServletException
+ {
+ initialized = true;
+
+ Iterator it = filters.iterator();
+ while ( it.hasNext() )
+ {
+ Filter filter = (Filter) it.next();
+ filter.init( filterConfig );
+ }
+
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ if (filterInvocationDefinitionSource == null) {
+ throw new IllegalArgumentException(
+ "filterInvocationDefinitionSource must be specified");
+ }
+
+ Iterator iter = this.filterInvocationDefinitionSource
+ .getConfigAttributeDefinitions();
+
+ if (iter == null) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(
+ "Could not validate configuration attributes as the FilterInvocationDefinitionSource did not return a ConfigAttributeDefinition Iterator");
+ }
+
+ return;
+ }
+
+ Set set = new HashSet();
+
+ while (iter.hasNext()) {
+ ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
+ .next();
+ Iterator attributes = def.getConfigAttributes();
+
+ while (attributes.hasNext()) {
+ ConfigAttribute attr = (ConfigAttribute) attributes.next();
+ }
+ }
+
+ if (set.size() == 0) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Validated configuration attributes");
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported configuration attributes: " + set.toString());
+ }
+
+ iter = filterInvocationDefinitionSource.getConfigAttributeDefinitions();
+ while ( iter.hasNext() )
+ {
+ ConfigAttributeDefinition element = (ConfigAttributeDefinition) iter.next();
+ Iterator configAttributes = element.getConfigAttributes();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/acegisecurity/util/FilterChainProxyTests.java b/core/src/test/java/org/acegisecurity/util/FilterChainProxyTests.java
new file mode 100644
index 0000000000..90342528ca
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/util/FilterChainProxyTests.java
@@ -0,0 +1,261 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.util;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.MockFilterConfig;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Tests {@link FilterChainProxy}.
+ *
+ * @author Carlos Sanchez
+ * @version $Id$
+ */
+public class FilterChainProxyTests
+ extends TestCase
+{
+ //~ Constructors
+ // ===========================================================
+
+ public FilterChainProxyTests()
+ {
+ super();
+ }
+
+ public FilterChainProxyTests( String arg0 )
+ {
+ super( arg0 );
+ }
+
+ //~ Methods
+ // ================================================================
+
+ public final void setUp() throws Exception
+ {
+ super.setUp();
+// ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
+// "net/sf/acegisecurity/util/filtertest-valid.xml" );
+// FilterChainProxy filterChainProxy = (FilterChainProxy)applicationContext.getBean("filterChain");
+// System.out.println(filterChainProxy);
+ }
+
+ public static void main( String[] args )
+ {
+ junit.textui.TestRunner.run( FilterChainProxyTests.class );
+ }
+
+ public void testDetectsTargetBeanIsNotAFilter() throws Exception
+ {
+ // Setup our filter
+ MockFilterConfig config = new MockFilterConfig();
+ config.setInitParmeter( "targetClass", "net.sf.acegisecurity.util.MockNotAFilter" );
+
+ FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" );
+
+ try
+ {
+ filter.init( config );
+ fail( "Should have thrown ServletException" );
+ }
+ catch ( ServletException expected )
+ {
+ assertEquals( "Bean 'mockNotAFilter' does not implement javax.servlet.Filter", expected.getMessage() );
+ }
+ }
+
+ public void testDetectsTargetBeanNotInBeanContext() throws Exception
+ {
+ // Setup our filter
+ MockFilterConfig config = new MockFilterConfig();
+ config.setInitParmeter( "targetBean", "WRONG_NAME" );
+
+ FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" );
+
+ try
+ {
+ filter.init( config );
+ fail( "Should have thrown ServletException" );
+ }
+ catch ( ServletException expected )
+ {
+ assertEquals( "targetBean 'WRONG_NAME' not found in context", expected.getMessage() );
+ }
+ }
+
+ public void testIgnoresEmptyTargetBean() throws Exception
+ {
+ // Setup our filter
+ MockFilterConfig config = new MockFilterConfig();
+ config.setInitParmeter( "targetClass", "net.sf.acegisecurity.util.FilterChainProxy" );
+ config.setInitParmeter( "targetBean", "" );
+
+ // Setup our expectation that the filter chain will be invoked
+ MockFilterChain chain = new MockFilterChain( true );
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockHttpServletRequest request = new MockHttpServletRequest( "/go" );
+
+ FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" );
+
+ executeFilterInContainerSimulator( config, filter, request, response, chain );
+ }
+
+ public void testNormalOperationWithLazyTrue() throws Exception
+ {
+ // Setup our filter
+ MockFilterConfig config = new MockFilterConfig();
+ config.setInitParmeter( "targetBean", "filterChain" );
+ config.setInitParmeter( "init", "lazy" );
+
+ // Setup our expectation that the filter chain will be invoked
+ MockFilterChain chain = new MockFilterChain( true );
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockHttpServletRequest request = new MockHttpServletRequest( "/go" );
+
+ FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" );
+
+ executeFilterInContainerSimulator( config, filter, request, response, chain );
+ }
+
+ public void testNormalOperationWithSpecificBeanName() throws Exception
+ {
+ // Setup our filter
+ MockFilterConfig config = new MockFilterConfig();
+ config.setInitParmeter( "targetBean", "filterChain" );
+
+ // Setup our expectation that the filter chain will be invoked
+ MockFilterChain chain = new MockFilterChain( true );
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockHttpServletRequest request = new MockHttpServletRequest( "/go" );
+
+ FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" );
+
+ executeFilterInContainerSimulator( config, filter, request, response, chain );
+ }
+
+ public void testNormalOperationWithTargetClass() throws Exception
+ {
+ // Setup our filter
+ MockFilterConfig config = new MockFilterConfig();
+ config.setInitParmeter( "targetClass", "net.sf.acegisecurity.util.FilterChainProxy" );
+
+ // Setup our expectation that the filter chain will be invoked
+ MockFilterChain chain = new MockFilterChain( true );
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockHttpServletRequest request = new MockHttpServletRequest( "/go" );
+
+ FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" );
+
+ executeFilterInContainerSimulator( config, filter, request, response, chain );
+ }
+
+ public void testNullDelegateDoesNotCauseNullPointerException() throws Exception
+ {
+ // Setup our filter
+ MockFilterConfig config = new MockFilterConfig();
+ config.setInitParmeter( "targetBean", "aFilterThatDoesntExist" );
+ config.setInitParmeter( "init", "lazy" );
+
+ // Setup our expectation that the filter chain will be invoked
+ MockFilterChain chain = new MockFilterChain( true );
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockHttpServletRequest request = new MockHttpServletRequest( "/go" );
+
+ FilterToBeanProxy filter = new MockFilterToBeanProxy( "net/sf/acegisecurity/util/filtertest-valid.xml" );
+
+ // do not init (which would hapen if called .doFilter)
+ filter.destroy();
+ }
+
+ private void executeFilterInContainerSimulator( FilterConfig filterConfig, Filter filter, ServletRequest request,
+ ServletResponse response, FilterChain filterChain ) throws ServletException, IOException
+ {
+ filter.init( filterConfig );
+ filter.doFilter( request, response, filterChain );
+ filter.destroy();
+ }
+
+ //~ Inner Classes
+ // ==========================================================
+
+ private class MockFilterChain
+ implements FilterChain
+ {
+ private boolean expectToProceed;
+
+ public MockFilterChain( boolean expectToProceed )
+ {
+ this.expectToProceed = expectToProceed;
+ }
+
+ private MockFilterChain()
+ {
+ super();
+ }
+
+ public void doFilter( ServletRequest request, ServletResponse response ) throws IOException, ServletException
+ {
+ if ( expectToProceed )
+ {
+ assertTrue( true );
+ }
+ else
+ {
+ fail( "Did not expect filter chain to proceed" );
+ }
+ }
+ }
+
+ private class MockFilterToBeanProxy
+ extends FilterToBeanProxy
+ {
+ private String appContextLocation;
+
+ public MockFilterToBeanProxy( String appContextLocation )
+ {
+ this.appContextLocation = appContextLocation;
+ }
+
+ private MockFilterToBeanProxy()
+ {
+ super();
+ }
+
+ protected ApplicationContext getContext( FilterConfig filterConfig )
+ {
+ return new ClassPathXmlApplicationContext( appContextLocation );
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml b/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml
index 78c34b76e4..ccb388a6e1 100644
--- a/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml
+++ b/core/src/test/resources/org/acegisecurity/util/filtertest-valid.xml
@@ -24,4 +24,20 @@