Coverage Report - org.acegisecurity.util.FilterChainProxy
 
Classes in this File Line Coverage Branch Coverage Complexity
FilterChainProxy
87% 
95% 
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  
 }