From 47f45ff7435cc9be7a2ad0fbfe6e65312f348599 Mon Sep 17 00:00:00 2001 From: David Syer Date: Tue, 9 Aug 2011 10:17:35 +0000 Subject: [PATCH] SPR-8585: add generic composite filter --- .../web/filter/CompositeFilter.java | 108 ++++++++++++++++++ .../web/filter/CompositeFilterTests.java | 87 ++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 org.springframework.web/src/main/java/org/springframework/web/filter/CompositeFilter.java create mode 100644 org.springframework.web/src/test/java/org/springframework/web/filter/CompositeFilterTests.java diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/CompositeFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/CompositeFilter.java new file mode 100644 index 00000000000..76b6b886b5f --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/CompositeFilter.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-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. + * 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 org.springframework.web.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/** + * A generic composite servlet {@link Filter} that just delegates its behaviour to a chain (list) of user supplied + * filters, achieving the functionality of a {@link FilterChain}, but conveniently using only {@link Filter} instances. + * This is useful for filters that require dependency injection, and can therefore be set up in a Spring application + * context. Typically this composite would be used in conjunction with {@link DelegatingFilterProxy}, so that it can be + * declared in Spring but applied to a servlet context. + * + * @since 3.1 + * + * @author Dave Syer + * + */ +public class CompositeFilter implements Filter { + + private List filters = new ArrayList(); + + public void setFilters(List filters) { + this.filters = new ArrayList(filters); + } + + /** + * Clean up all the filters supplied, calling each one's destroy method in turn, but in reverse order. + * + * @see Filter#init(FilterConfig) + */ + public void destroy() { + for (int i = filters.size(); i-- > 0;) { + Filter filter = filters.get(i); + filter.destroy(); + } + } + + /** + * Initialize all the filters, calling each one's init method in turn in the order supplied. + * + * @see Filter#init(FilterConfig) + */ + public void init(FilterConfig config) throws ServletException { + for (Filter filter : filters) { + filter.init(config); + } + } + + /** + * Forms a temporary chain from the list of delegate filters supplied ({@link #setFilters(List)}) and executes them + * in order. Each filter delegates to the next one in the list, achieving the normal behaviour of a + * {@link FilterChain}, despite the fact that this is a {@link Filter}. + * + * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) + */ + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException { + new VirtualFilterChain(chain, filters).doFilter(request, response); + } + + private static class VirtualFilterChain implements FilterChain { + private final FilterChain originalChain; + private final List additionalFilters; + private int currentPosition = 0; + + private VirtualFilterChain(FilterChain chain, List additionalFilters) { + this.originalChain = chain; + this.additionalFilters = additionalFilters; + } + + public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException, + ServletException { + if (currentPosition == additionalFilters.size()) { + originalChain.doFilter(request, response); + } else { + currentPosition++; + Filter nextFilter = additionalFilters.get(currentPosition - 1); + nextFilter.doFilter(request, response, this); + } + } + + } + +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/filter/CompositeFilterTests.java b/org.springframework.web/src/test/java/org/springframework/web/filter/CompositeFilterTests.java new file mode 100644 index 00000000000..1f654f9c8b2 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/filter/CompositeFilterTests.java @@ -0,0 +1,87 @@ +/* + * 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. + * 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 org.springframework.web.filter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.util.Arrays; + +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 org.junit.Test; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; + +/** + * @author Dave Syer + */ +public class CompositeFilterTests { + + @Test + public void testCompositeFilter() throws ServletException, IOException { + ServletContext sc = new MockServletContext(); + + MockFilter targetFilter = new MockFilter(); + + MockFilterConfig proxyConfig = new MockFilterConfig(sc); + + CompositeFilter filterProxy = new CompositeFilter(); + filterProxy.setFilters(Arrays.asList(targetFilter)); + filterProxy.init(proxyConfig); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + filterProxy.doFilter(request, response, null); + + assertNotNull(targetFilter.filterConfig); + assertEquals(Boolean.TRUE, request.getAttribute("called")); + + filterProxy.destroy(); + assertNull(targetFilter.filterConfig); + } + + + public static class MockFilter implements Filter { + + public FilterConfig filterConfig; + + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { + request.setAttribute("called", Boolean.TRUE); + } + + public void destroy() { + this.filterConfig = null; + } + } + +}