1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.util;
17
18 import org.acegisecurity.ConfigAttribute;
19 import org.acegisecurity.ConfigAttributeDefinition;
20 import org.acegisecurity.intercept.web.FilterInvocation;
21 import org.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26 import org.springframework.beans.BeansException;
27 import org.springframework.beans.factory.InitializingBean;
28
29 import org.springframework.context.ApplicationContext;
30 import org.springframework.context.ApplicationContextAware;
31 import org.springframework.util.Assert;
32
33 import java.io.IOException;
34
35 import java.util.Iterator;
36 import java.util.LinkedHashSet;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.Vector;
40
41 import javax.servlet.Filter;
42 import javax.servlet.FilterChain;
43 import javax.servlet.FilterConfig;
44 import javax.servlet.ServletException;
45 import javax.servlet.ServletRequest;
46 import javax.servlet.ServletResponse;
47
48
49 /***
50 * Delegates <code>Filter</code> requests to a list of Spring-managed beans.
51 *
52 * <p>
53 * The <code>FilterChainProxy</code> is loaded via a standard {@link
54 * org.acegisecurity.util.FilterToBeanProxy} declaration in
55 * <code>web.xml</code>. <code>FilterChainProxy</code> will then pass {@link
56 * #init(FilterConfig)}, {@link #destroy()}, {@link #doInit()} and {@link
57 * #doFilter(ServletRequest, ServletResponse, FilterChain)} invocations
58 * through to each <code>Filter</code> defined against
59 * <code>FilterChainProxy</code>.
60 * </p>
61 *
62 * <p>
63 * <code>FilterChainProxy</code> is configured using a standard {@link
64 * org.acegisecurity.intercept.web.FilterInvocationDefinitionSource}. Each
65 * possible URI pattern that <code>FilterChainProxy</code> should service must
66 * be entered. The first matching URI pattern located by
67 * <code>FilterInvocationDefinitionSource</code> for a given request will be
68 * used to define all of the <code>Filter</code>s that apply to that request.
69 * NB: This means you must put most specific URI patterns at the top of the
70 * list, and ensure all <code>Filter</code>s that should apply for a given URI
71 * pattern are entered against the respective entry. The
72 * <code>FilterChainProxy</code> will not iterate the remainder of the URI
73 * patterns to locate additional <code>Filter</code>s. The
74 * <code>FilterInvocationDefinitionSource</code> described the applicable URI
75 * pattern to fire the filter chain, followed by a list of configuration
76 * attributes. Each configuration attribute's {@link
77 * org.acegisecurity.ConfigAttribute#getAttribute()} corresponds to a bean
78 * name that is available from the application context.
79 * </p>
80 *
81 * <p>
82 * <code>FilterChainProxy</code> respects normal handling of
83 * <code>Filter</code>s that elect not to call {@link
84 * javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
85 * javax.servlet.ServletResponse, javax.servlet.FilterChain)}, in that the
86 * remainder of the origial or <code>FilterChainProxy</code>-declared filter
87 * chain will not be called.
88 * </p>
89 *
90 * <p>
91 * It is particularly noted the <code>Filter</code> lifecycle mismatch between
92 * the servlet container and IoC container. As per {@link
93 * org.acegisecurity.util.FilterToBeanProxy} JavaDocs, we recommend you
94 * allow the IoC container to manage lifecycle instead of the servlet
95 * container. By default the <code>FilterToBeanProxy</code> will never call
96 * this class' {@link #init(FilterConfig)} and {@link #destroy()} methods,
97 * meaning each of the filters defined against
98 * <code>FilterInvocationDefinitionSource</code> will not be called. If you do
99 * need your filters to be initialized and destroyed, please set the
100 * <code>lifecycle</code> initialization parameter against the
101 * <code>FilterToBeanProxy</code> to specify servlet container lifecycle
102 * management.
103 * </p>
104 *
105 * @author Carlos Sanchez
106 * @author Ben Alex
107 * @version $Id: FilterChainProxy.java,v 1.6 2005/11/17 00:56:09 benalex Exp $
108 */
109 public class FilterChainProxy implements Filter, InitializingBean,
110 ApplicationContextAware {
111
112
113 private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
114
115
116
117 private ApplicationContext applicationContext;
118 private FilterInvocationDefinitionSource filterInvocationDefinitionSource;
119
120
121
122 public void setApplicationContext(ApplicationContext applicationContext)
123 throws BeansException {
124 this.applicationContext = applicationContext;
125 }
126
127 public void setFilterInvocationDefinitionSource(
128 FilterInvocationDefinitionSource filterInvocationDefinitionSource) {
129 this.filterInvocationDefinitionSource = filterInvocationDefinitionSource;
130 }
131
132 public FilterInvocationDefinitionSource getFilterInvocationDefinitionSource() {
133 return filterInvocationDefinitionSource;
134 }
135
136 public void afterPropertiesSet() throws Exception {
137 Assert.notNull(filterInvocationDefinitionSource, "filterInvocationDefinitionSource must be specified");
138 Assert.notNull(this.filterInvocationDefinitionSource.getConfigAttributeDefinitions(), "FilterChainProxy requires the FilterInvocationDefinitionSource to return a non-null response to getConfigAttributeDefinitions()");
139 }
140
141 public void destroy() {
142 Filter[] filters = obtainAllDefinedFilters();
143
144 for (int i = 0; i < filters.length; i++) {
145 if (logger.isDebugEnabled()) {
146 logger.debug(
147 "Destroying Filter defined in ApplicationContext: '"
148 + filters[i].toString() + "'");
149 }
150
151 filters[i].destroy();
152 }
153 }
154
155 public void doFilter(ServletRequest request, ServletResponse response,
156 FilterChain chain) throws IOException, ServletException {
157 FilterInvocation fi = new FilterInvocation(request, response, chain);
158
159 ConfigAttributeDefinition cad = this.filterInvocationDefinitionSource
160 .getAttributes(fi);
161
162 if (cad == null) {
163 if (logger.isDebugEnabled()) {
164 logger.debug(fi.getRequestUrl() + " has no matching filters");
165 }
166
167 chain.doFilter(request, response);
168 } else {
169 Filter[] filters = obtainAllDefinedFilters(cad);
170
171 VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi,
172 filters);
173 virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
174 }
175 }
176
177 public void init(FilterConfig filterConfig) throws ServletException {
178 Filter[] filters = obtainAllDefinedFilters();
179
180 for (int i = 0; i < filters.length; i++) {
181 if (logger.isDebugEnabled()) {
182 logger.debug(
183 "Initializing Filter defined in ApplicationContext: '"
184 + filters[i].toString() + "'");
185 }
186
187 filters[i].init(filterConfig);
188 }
189 }
190
191 /***
192 * Obtains all of the <b>unique</b><code>Filter</code> instances registered
193 * against the <code>FilterInvocationDefinitionSource</code>.
194 *
195 * <p>
196 * This is useful in ensuring a <code>Filter</code> is not initialized or
197 * destroyed twice.
198 * </p>
199 *
200 * @return all of the <code>Filter</code> instances in the application
201 * context for which there has been an entry against the
202 * <code>FilterInvocationDefinitionSource</code> (only one entry
203 * is included in the array for each <code>Filter</code> that
204 * actually exists in application context, even if a given
205 * <code>Filter</code> is defined multiples times by the
206 * <code>FilterInvocationDefinitionSource</code>)
207 */
208 private Filter[] obtainAllDefinedFilters() {
209 Iterator cads = this.filterInvocationDefinitionSource
210 .getConfigAttributeDefinitions();
211 Set list = new LinkedHashSet();
212
213 while (cads.hasNext()) {
214 ConfigAttributeDefinition attribDef = (ConfigAttributeDefinition) cads
215 .next();
216 Filter[] filters = obtainAllDefinedFilters(attribDef);
217
218 for (int i = 0; i < filters.length; i++) {
219 list.add(filters[i]);
220 }
221 }
222
223 return (Filter[]) list.toArray(new Filter[] {null});
224 }
225
226 /***
227 * Obtains all of the <code>Filter</code> instances registered against the
228 * specified <code>ConfigAttributeDefinition</code>.
229 *
230 * @param configAttributeDefinition for which we want to obtain associated
231 * <code>Filter</code>s
232 *
233 * @return the <code>Filter</code>s against the specified
234 * <code>ConfigAttributeDefinition</code>
235 *
236 * @throws IllegalArgumentException if a configuration attribute provides a
237 * <code>null</code> return value from the {@link
238 * ConfigAttribute#getAttribute()} method
239 */
240 private Filter[] obtainAllDefinedFilters(
241 ConfigAttributeDefinition configAttributeDefinition) {
242 List list = new Vector();
243 Iterator attributes = configAttributeDefinition.getConfigAttributes();
244
245 while (attributes.hasNext()) {
246 ConfigAttribute attr = (ConfigAttribute) attributes.next();
247 String filterName = attr.getAttribute();
248
249 Assert.notNull(filterName, "Configuration attribute: '"
250 + attr
251 + "' returned null to the getAttribute() method, which is invalid when used with FilterChainProxy");
252
253 list.add(this.applicationContext.getBean(filterName, Filter.class));
254 }
255
256 return (Filter[]) list.toArray(new Filter[] {null});
257 }
258
259
260
261 /***
262 * A <code>FilterChain</code> that records whether or not {@link
263 * FilterChain#doFilter(javax.servlet.ServletRequest,
264 * javax.servlet.ServletResponse)} is called.
265 *
266 * <p>
267 * This <code>FilterChain</code> is used by <code>FilterChainProxy</code>
268 * to determine if the next <code>Filter</code> should be called or not.
269 * </p>
270 */
271 private class VirtualFilterChain implements FilterChain {
272 private FilterInvocation fi;
273 private Filter[] additionalFilters;
274 private int currentPosition = 0;
275
276 public VirtualFilterChain(FilterInvocation filterInvocation,
277 Filter[] additionalFilters) {
278 this.fi = filterInvocation;
279 this.additionalFilters = additionalFilters;
280 }
281
282 private VirtualFilterChain() {}
283
284 public void doFilter(ServletRequest request, ServletResponse response)
285 throws IOException, ServletException {
286 if (currentPosition == additionalFilters.length) {
287 if (logger.isDebugEnabled()) {
288 logger.debug(fi.getRequestUrl()
289 + " reached end of additional filter chain; proceeding with original chain");
290 }
291
292 fi.getChain().doFilter(request, response);
293 } else {
294 currentPosition++;
295
296 if (logger.isDebugEnabled()) {
297 logger.debug(fi.getRequestUrl() + " at position "
298 + currentPosition + " of " + additionalFilters.length
299 + " in additional filter chain; firing Filter: '"
300 + additionalFilters[currentPosition - 1] + "'");
301 }
302
303 additionalFilters[currentPosition - 1].doFilter(request,
304 response, this);
305 }
306 }
307 }
308 }