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