Clover coverage report - Acegi Security System for Spring - 1.0.0-RC1
Coverage timestamp: Mon Dec 5 2005 09:05:15 EST
file stats: LOC: 308   Methods: 12
NCLOC: 143   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
FilterChainProxy.java 79.2% 90% 91.7% 87.2%
coverage coverage
 1    /* Copyright 2004, 2005 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    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    //~ Static fields/initializers =============================================
 112   
 113    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
 114   
 115    //~ Instance fields ========================================================
 116   
 117    private ApplicationContext applicationContext;
 118    private FilterInvocationDefinitionSource filterInvocationDefinitionSource;
 119   
 120    //~ Methods ================================================================
 121   
 122  13 public void setApplicationContext(ApplicationContext applicationContext)
 123    throws BeansException {
 124  13 this.applicationContext = applicationContext;
 125    }
 126   
 127  13 public void setFilterInvocationDefinitionSource(
 128    FilterInvocationDefinitionSource filterInvocationDefinitionSource) {
 129  13 this.filterInvocationDefinitionSource = filterInvocationDefinitionSource;
 130    }
 131   
 132  1 public FilterInvocationDefinitionSource getFilterInvocationDefinitionSource() {
 133  1 return filterInvocationDefinitionSource;
 134    }
 135   
 136  13 public void afterPropertiesSet() throws Exception {
 137  13 Assert.notNull(filterInvocationDefinitionSource, "filterInvocationDefinitionSource must be specified");
 138  12 Assert.notNull(this.filterInvocationDefinitionSource.getConfigAttributeDefinitions(), "FilterChainProxy requires the FilterInvocationDefinitionSource to return a non-null response to getConfigAttributeDefinitions()");
 139    }
 140   
 141  1 public void destroy() {
 142  1 Filter[] filters = obtainAllDefinedFilters();
 143   
 144  1 for (int i = 0; i < filters.length; i++) {
 145  1 if (logger.isDebugEnabled()) {
 146  0 logger.debug(
 147    "Destroying Filter defined in ApplicationContext: '"
 148    + filters[i].toString() + "'");
 149    }
 150   
 151  1 filters[i].destroy();
 152    }
 153    }
 154   
 155  2 public void doFilter(ServletRequest request, ServletResponse response,
 156    FilterChain chain) throws IOException, ServletException {
 157  2 FilterInvocation fi = new FilterInvocation(request, response, chain);
 158   
 159  2 ConfigAttributeDefinition cad = this.filterInvocationDefinitionSource
 160    .getAttributes(fi);
 161   
 162  2 if (cad == null) {
 163  1 if (logger.isDebugEnabled()) {
 164  0 logger.debug(fi.getRequestUrl() + " has no matching filters");
 165    }
 166   
 167  1 chain.doFilter(request, response);
 168    } else {
 169  1 Filter[] filters = obtainAllDefinedFilters(cad);
 170   
 171  1 VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi,
 172    filters);
 173  1 virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
 174    }
 175    }
 176   
 177  2 public void init(FilterConfig filterConfig) throws ServletException {
 178  2 Filter[] filters = obtainAllDefinedFilters();
 179   
 180  1 for (int i = 0; i < filters.length; i++) {
 181  1 if (logger.isDebugEnabled()) {
 182  0 logger.debug(
 183    "Initializing Filter defined in ApplicationContext: '"
 184    + filters[i].toString() + "'");
 185    }
 186   
 187  1 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  3 private Filter[] obtainAllDefinedFilters() {
 209  3 Iterator cads = this.filterInvocationDefinitionSource
 210    .getConfigAttributeDefinitions();
 211  3 Set list = new LinkedHashSet();
 212   
 213  3 while (cads.hasNext()) {
 214  5 ConfigAttributeDefinition attribDef = (ConfigAttributeDefinition) cads
 215    .next();
 216  5 Filter[] filters = obtainAllDefinedFilters(attribDef);
 217   
 218  4 for (int i = 0; i < filters.length; i++) {
 219  4 list.add(filters[i]);
 220    }
 221    }
 222   
 223  2 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  6 private Filter[] obtainAllDefinedFilters(
 241    ConfigAttributeDefinition configAttributeDefinition) {
 242  6 List list = new Vector();
 243  6 Iterator attributes = configAttributeDefinition.getConfigAttributes();
 244   
 245  6 while (attributes.hasNext()) {
 246  6 ConfigAttribute attr = (ConfigAttribute) attributes.next();
 247  6 String filterName = attr.getAttribute();
 248   
 249  6 Assert.notNull(filterName, "Configuration attribute: '"
 250    + attr
 251    + "' returned null to the getAttribute() method, which is invalid when used with FilterChainProxy");
 252   
 253  5 list.add(this.applicationContext.getBean(filterName, Filter.class));
 254    }
 255   
 256  5 return (Filter[]) list.toArray(new Filter[] {null});
 257    }
 258   
 259    //~ Inner Classes ==========================================================
 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  1 public VirtualFilterChain(FilterInvocation filterInvocation,
 277    Filter[] additionalFilters) {
 278  1 this.fi = filterInvocation;
 279  1 this.additionalFilters = additionalFilters;
 280    }
 281   
 282  0 private VirtualFilterChain() {}
 283   
 284  2 public void doFilter(ServletRequest request, ServletResponse response)
 285    throws IOException, ServletException {
 286  2 if (currentPosition == additionalFilters.length) {
 287  1 if (logger.isDebugEnabled()) {
 288  0 logger.debug(fi.getRequestUrl()
 289    + " reached end of additional filter chain; proceeding with original chain");
 290    }
 291   
 292  1 fi.getChain().doFilter(request, response);
 293    } else {
 294  1 currentPosition++;
 295   
 296  1 if (logger.isDebugEnabled()) {
 297  0 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  1 additionalFilters[currentPosition - 1].doFilter(request,
 304    response, this);
 305    }
 306    }
 307    }
 308    }