Coverage Report - org.kuali.rice.kew.web.BootstrapFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
BootstrapFilter
0%
0/103
0%
0/74
2.929
BootstrapFilterChain
0%
0/16
0%
0/4
2.929
BootstrapFilterConfig
0%
0/17
N/A
2.929
FilterMapping
0%
0/10
N/A
2.929
 
 1  
 /*
 2  
  * Copyright 2006-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 package org.kuali.rice.kew.web;
 18  
 
 19  
 import org.apache.log4j.Logger;
 20  
 import org.kuali.rice.core.api.config.property.Config;
 21  
 import org.kuali.rice.core.api.config.property.ConfigContext;
 22  
 import org.kuali.rice.core.api.reflect.ObjectDefinition;
 23  
 import org.kuali.rice.core.api.reflect.ObjectDefinition;
 24  
 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
 25  
 import org.kuali.rice.core.util.ClassLoaderUtils;
 26  
 
 27  
 import javax.servlet.Filter;
 28  
 import javax.servlet.FilterChain;
 29  
 import javax.servlet.FilterConfig;
 30  
 import javax.servlet.ServletContext;
 31  
 import javax.servlet.ServletException;
 32  
 import javax.servlet.ServletRequest;
 33  
 import javax.servlet.ServletResponse;
 34  
 import javax.servlet.http.HttpServletRequest;
 35  
 import java.io.IOException;
 36  
 import java.util.Collections;
 37  
 import java.util.Enumeration;
 38  
 import java.util.HashMap;
 39  
 import java.util.Iterator;
 40  
 import java.util.LinkedList;
 41  
 import java.util.List;
 42  
 import java.util.Map;
 43  
 import java.util.SortedSet;
 44  
 import java.util.TreeSet;
 45  
 
 46  
 /**
 47  
  * A filter which at runtime reads a series of filter configurations, constructs
 48  
  * and initializes those filters, and invokes them when it is invoked. This
 49  
  * allows runtime user configuration of arbitrary filters in the webapp context.
 50  
  *
 51  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 52  
  */
 53  0
 public class BootstrapFilter implements Filter {
 54  0
         private static final Logger LOG = Logger.getLogger(BootstrapFilter.class);
 55  
 
 56  
         private static final String FILTER_PREFIX = "filter.";
 57  
 
 58  
         private static final String CLASS_SUFFIX = ".class";
 59  
 
 60  
         private static final String FILTER_MAPPING_PREFIX = "filtermapping.";
 61  
 
 62  
         private FilterConfig config;
 63  
 
 64  0
         private final Map<String, Filter> filters = new HashMap<String, Filter>();
 65  
 
 66  0
         private final SortedSet<FilterMapping> filterMappings = new TreeSet<FilterMapping>();
 67  
 
 68  0
         private boolean initted = false;
 69  
 
 70  
         public void init(FilterConfig cfg) throws ServletException {
 71  0
                 this.config = cfg;
 72  0
         }
 73  
 
 74  
         private void addFilter(String name, String classname, Map<String, String> props) throws ServletException {
 75  0
                 LOG.debug("Adding filter: " + name + "=" + classname);
 76  0
                 Object filterObject = GlobalResourceLoader.getResourceLoader().getObject(new ObjectDefinition(classname));
 77  0
                 if (filterObject == null) {
 78  0
                         throw new ServletException("Filter '" + name + "' class not found: " + classname);
 79  
 
 80  
                 }
 81  0
                 if (!(filterObject instanceof Filter)) {
 82  0
                         LOG.error("Class '" + filterObject.getClass() + "' does not implement servlet javax.servlet.Filter");
 83  0
                         return;
 84  
                 }
 85  0
                 Filter filter = (Filter) filterObject;
 86  0
                 BootstrapFilterConfig fc = new BootstrapFilterConfig(config.getServletContext(), name);
 87  0
                 for (Map.Entry<String, String> entry : props.entrySet()) {
 88  0
                         String key = entry.getKey().toString();
 89  0
                         final String prefix = FILTER_PREFIX + name + ".";
 90  0
                         if (!key.startsWith(prefix) || key.equals(FILTER_PREFIX + name + CLASS_SUFFIX)) {
 91  0
                                 continue;
 92  
                         }
 93  0
                         String paramName = key.substring(prefix.length());
 94  0
                         fc.addInitParameter(paramName, entry.getValue());
 95  0
                 }
 96  
                 try {
 97  0
                         filter.init(fc);
 98  0
                         filters.put(name, filter);
 99  0
                 } catch (ServletException se) {
 100  0
                         LOG.error("Error initializing filter: " + name + " [" + classname + "]", se);
 101  0
                 }
 102  0
         }
 103  
 
 104  
         private void addFilterMapping(String filterName, String orderNumber, String value) {
 105  0
                 filterMappings.add(new FilterMapping(filterName, orderNumber, value));
 106  0
         }
 107  
 
 108  
         private synchronized void init() throws ServletException {
 109  0
                 if (initted) {
 110  0
                         return;
 111  
                 }
 112  0
                 LOG.debug("initializing...");
 113  0
                 Config cfg = ConfigContext.getCurrentContextConfig();
 114  
                 
 115  
                 @SuppressWarnings({ "unchecked", "rawtypes" })
 116  0
                 final Map<String, String> p = new HashMap<String, String>((Map) cfg.getProperties());
 117  
                 
 118  0
                 for (Map.Entry<String, String> entry : p.entrySet()) {
 119  0
                         String key = entry.getKey().toString();
 120  0
                         if (key.startsWith(FILTER_MAPPING_PREFIX)) {
 121  0
                                 String[] values = key.split("\\.");
 122  0
                                 if (values.length != 2 && values.length != 3) {
 123  0
                                         throw new ServletException("Invalid filter mapping defined.  Should contain 2 or 3 pieces in the form of filtermapping.<<filter name>>.<<order number>> with the last piece optional.");
 124  
                                 }
 125  0
                                 String filterName = values[1];
 126  0
                                 String orderNumber = (values.length == 2 ? "0" : values[2]);
 127  0
                                 String value = entry.getValue();
 128  0
                                 addFilterMapping(filterName, orderNumber, value);
 129  0
                         } else if (key.startsWith(FILTER_PREFIX) && key.endsWith(CLASS_SUFFIX)) {
 130  0
                                 String name = key.substring(FILTER_PREFIX.length(), key.length() - CLASS_SUFFIX.length());
 131  0
                                 String value = entry.getValue();
 132  
                                 // ClassLoader cl =
 133  
                                 // SpringServiceLocator.getPluginRegistry().getInstitutionPlugin().getClassLoader();
 134  
                                 // addFilter(name, value, cl, p);
 135  0
                                 addFilter(name, value, p);
 136  
                         }
 137  0
                 }
 138  
                 // do a diff log a warn if any filter has no mappings
 139  0
                 for (String filterName : filters.keySet()) {
 140  0
                         if (!hasFilterMapping(filterName)) {
 141  0
                                 LOG.warn("NO FILTER MAPPING DETECTED.  Filter " + filterName + " has no mapping and will not be called.");
 142  
                         }
 143  
                 }
 144  0
                 initted = true;
 145  0
         }
 146  
 
 147  
         private boolean hasFilterMapping(String filterName) {
 148  0
                 for (FilterMapping filterMapping : filterMappings) {
 149  0
                         if (filterMapping.getFilterName().equals(filterName)) {
 150  0
                                 return true;
 151  
                         }
 152  
                 }
 153  0
                 return false;
 154  
         }
 155  
 
 156  
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 157  0
             LOG.debug("Begin BootstrapFilter...");
 158  0
                 init();
 159  
                 // build the filter chain and execute it
 160  0
                 if (!filterMappings.isEmpty() && request instanceof HttpServletRequest) {
 161  0
                         chain = buildChain((HttpServletRequest) request, chain);
 162  
                 }
 163  0
                 LOG.debug("...ending BootstrapFilter preperation, executing BootstrapFilter Chain.");
 164  0
                 chain.doFilter(request, response);
 165  
 
 166  0
         }
 167  
 
 168  
         private FilterChain buildChain(HttpServletRequest request, FilterChain targetChain) {
 169  0
                 BootstrapFilterChain chain = new BootstrapFilterChain(targetChain, ClassLoaderUtils.getDefaultClassLoader());
 170  0
                 String requestPath = request.getServletPath();
 171  0
                 for (FilterMapping mapping : filterMappings) {
 172  0
                         Filter filter = filters.get(mapping.getFilterName());
 173  0
                         if (!chain.containsFilter(filter) && matchFiltersURL(mapping.getUrlPattern(), requestPath)) {
 174  0
                                 chain.addFilter(filter);
 175  
                         }
 176  0
                 }
 177  0
                 return chain;
 178  
         }
 179  
 
 180  
         public void destroy() {
 181  0
                 for (Filter filter : filters.values()) {
 182  
                         try {
 183  0
                                 filter.destroy();
 184  0
                         } catch (Exception e) {
 185  0
                                 LOG.error("Error destroying filter: " + filter, e);
 186  0
                         }
 187  
                 }
 188  0
         }
 189  
 
 190  
         /**
 191  
          * This method was borrowed from the Tomcat codebase.
 192  
          */
 193  
         private boolean matchFiltersURL(String urlPattern, String requestPath) {
 194  
 
 195  0
                 if (requestPath == null) {
 196  0
                         return (false);
 197  
                 }
 198  
 
 199  
                 // Match on context relative request path
 200  0
                 if (urlPattern == null) {
 201  0
                         return (false);
 202  
                 }
 203  
 
 204  
                 // Case 1 - Exact Match
 205  0
                 if (urlPattern.equals(requestPath)) {
 206  0
                         return (true);
 207  
                 }
 208  
 
 209  
                 // Case 2 - Path Match ("/.../*")
 210  0
                 if (urlPattern.equals("/*") || urlPattern.equals("*")) {
 211  0
                         return (true);
 212  
                 }
 213  0
                 if (urlPattern.endsWith("/*")) {
 214  0
                         if (urlPattern.regionMatches(0, requestPath, 0, urlPattern.length() - 2)) {
 215  0
                                 if (requestPath.length() == (urlPattern.length() - 2)) {
 216  0
                                         return (true);
 217  0
                                 } else if ('/' == requestPath.charAt(urlPattern.length() - 2)) {
 218  0
                                         return (true);
 219  
                                 }
 220  
                         }
 221  0
                         return (false);
 222  
                 }
 223  
 
 224  
                 // Case 3 - Extension Match
 225  0
                 if (urlPattern.startsWith("*.")) {
 226  0
                         int slash = requestPath.lastIndexOf('/');
 227  0
                         int period = requestPath.lastIndexOf('.');
 228  0
                         if ((slash >= 0) && (period > slash) && (period != requestPath.length() - 1) && ((requestPath.length() - period) == (urlPattern.length() - 1))) {
 229  0
                                 return (urlPattern.regionMatches(2, requestPath, period + 1, urlPattern.length() - 2));
 230  
                         }
 231  
                 }
 232  
 
 233  
                 // Case 4 - "Default" Match
 234  0
                 return (false); // NOTE - Not relevant for selecting filters
 235  
 
 236  
         }
 237  
 
 238  
 }
 239  
 
 240  
 /**
 241  
  * A filter chain that invokes a series of filters with which it was
 242  
  * initialized, and then delegates to a target filterchain.
 243  
  *
 244  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 245  
  */
 246  
 class BootstrapFilterChain implements FilterChain {
 247  
 
 248  0
         private final List<Filter> filters = new LinkedList<Filter>();
 249  
 
 250  
         private final FilterChain target;
 251  
 
 252  
         private Iterator<Filter> filterIterator;
 253  
 
 254  
         private ClassLoader originalClassLoader;
 255  
 
 256  0
         public BootstrapFilterChain(FilterChain target, ClassLoader originalClassLoader) {
 257  0
                 this.target = target;
 258  0
                 this.originalClassLoader = originalClassLoader;
 259  0
         }
 260  
 
 261  
         public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
 262  0
                 if (filterIterator == null) {
 263  0
                         filterIterator = filters.iterator();
 264  
                 }
 265  0
                 if (filterIterator.hasNext()) {
 266  0
                         (filterIterator.next()).doFilter(request, response, this);
 267  
                 } else {
 268  
                         // reset the CCL to the original classloader before calling the non
 269  
                         // workflow configured filter - this makes it so our
 270  
                         // CCL is the webapp classloader in workflow action classes and the
 271  
                         // code they call
 272  0
                         Thread.currentThread().setContextClassLoader(originalClassLoader);
 273  0
                         target.doFilter(request, response);
 274  
                 }
 275  0
         }
 276  
 
 277  
         public void addFilter(Filter filter) {
 278  0
                 filters.add(filter);
 279  0
         }
 280  
 
 281  
         public boolean containsFilter(Filter filter) {
 282  0
                 return filters.contains(filter);
 283  
         }
 284  
 
 285  
         public boolean isEmpty() {
 286  0
                 return filters.isEmpty();
 287  
         }
 288  
 
 289  
 }
 290  
 
 291  
 /**
 292  
  * Borrowed from spring-mock.
 293  
  *
 294  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 295  
  */
 296  
 class BootstrapFilterConfig implements FilterConfig {
 297  
 
 298  
         private final ServletContext servletContext;
 299  
 
 300  
         private final String filterName;
 301  
 
 302  0
         private final Map<String, String> initParameters = new HashMap<String, String>();
 303  
 
 304  
         public BootstrapFilterConfig() {
 305  0
                 this(null, "");
 306  0
         }
 307  
 
 308  
         public BootstrapFilterConfig(String filterName) {
 309  0
                 this(null, filterName);
 310  0
         }
 311  
 
 312  
         public BootstrapFilterConfig(ServletContext servletContext) {
 313  0
                 this(servletContext, "");
 314  0
         }
 315  
 
 316  0
         public BootstrapFilterConfig(ServletContext servletContext, String filterName) {
 317  0
                 this.servletContext = servletContext;
 318  0
                 this.filterName = filterName;
 319  0
         }
 320  
 
 321  
         public String getFilterName() {
 322  0
                 return filterName;
 323  
         }
 324  
 
 325  
         public ServletContext getServletContext() {
 326  0
                 return servletContext;
 327  
         }
 328  
 
 329  
         public void addInitParameter(String name, String value) {
 330  0
                 this.initParameters.put(name, value);
 331  0
         }
 332  
 
 333  
         public String getInitParameter(String name) {
 334  0
                 return this.initParameters.get(name);
 335  
         }
 336  
 
 337  
         public Enumeration<String> getInitParameterNames() {
 338  0
                 return Collections.enumeration(this.initParameters.keySet());
 339  
         }
 340  
 
 341  
 }
 342  
 
 343  0
 class FilterMapping implements Comparable<FilterMapping> {
 344  
 
 345  
         private String filterName;
 346  
 
 347  
         private String orderValue;
 348  
 
 349  
         private String urlPattern;
 350  
 
 351  0
         public FilterMapping(String filterName, String orderValue, String urlPattern) {
 352  0
                 this.filterName = filterName;
 353  0
                 this.orderValue = orderValue;
 354  0
                 this.urlPattern = urlPattern;
 355  0
         }
 356  
 
 357  
         public int compareTo(FilterMapping object) {
 358  0
                 return orderValue.compareTo(object.orderValue);
 359  
         }
 360  
 
 361  
         public String getFilterName() {
 362  0
                 return filterName;
 363  
         }
 364  
 
 365  
         public String getOrderValue() {
 366  0
                 return orderValue;
 367  
         }
 368  
 
 369  
         public String getUrlPattern() {
 370  0
                 return urlPattern;
 371  
         }
 372  
 
 373  
 }