001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.filter; 018 019import java.util.Enumeration; 020import java.util.HashSet; 021import java.util.Set; 022import javax.servlet.Filter; 023import javax.servlet.FilterConfig; 024import javax.servlet.ServletContext; 025import javax.servlet.ServletException; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.beans.BeanWrapper; 031import org.springframework.beans.BeansException; 032import org.springframework.beans.MutablePropertyValues; 033import org.springframework.beans.PropertyAccessorFactory; 034import org.springframework.beans.PropertyValue; 035import org.springframework.beans.PropertyValues; 036import org.springframework.beans.factory.BeanNameAware; 037import org.springframework.beans.factory.DisposableBean; 038import org.springframework.beans.factory.InitializingBean; 039import org.springframework.context.EnvironmentAware; 040import org.springframework.core.env.Environment; 041import org.springframework.core.env.EnvironmentCapable; 042import org.springframework.core.io.Resource; 043import org.springframework.core.io.ResourceEditor; 044import org.springframework.core.io.ResourceLoader; 045import org.springframework.util.Assert; 046import org.springframework.util.CollectionUtils; 047import org.springframework.util.StringUtils; 048import org.springframework.web.context.ServletContextAware; 049import org.springframework.web.context.support.ServletContextResourceLoader; 050import org.springframework.web.context.support.StandardServletEnvironment; 051import org.springframework.web.util.NestedServletException; 052 053/** 054 * Simple base implementation of {@link javax.servlet.Filter} which treats 055 * its config parameters ({@code init-param} entries within the 056 * {@code filter} tag in {@code web.xml}) as bean properties. 057 * 058 * <p>A handy superclass for any type of filter. Type conversion of config 059 * parameters is automatic, with the corresponding setter method getting 060 * invoked with the converted value. It is also possible for subclasses to 061 * specify required properties. Parameters without matching bean property 062 * setter will simply be ignored. 063 * 064 * <p>This filter leaves actual filtering to subclasses, which have to 065 * implement the {@link javax.servlet.Filter#doFilter} method. 066 * 067 * <p>This generic filter base class has no dependency on the Spring 068 * {@link org.springframework.context.ApplicationContext} concept. 069 * Filters usually don't load their own context but rather access service 070 * beans from the Spring root application context, accessible via the 071 * filter's {@link #getServletContext() ServletContext} (see 072 * {@link org.springframework.web.context.support.WebApplicationContextUtils}). 073 * 074 * @author Juergen Hoeller 075 * @since 06.12.2003 076 * @see #addRequiredProperty 077 * @see #initFilterBean 078 * @see #doFilter 079 */ 080public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, 081 EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean { 082 083 /** Logger available to subclasses */ 084 protected final Log logger = LogFactory.getLog(getClass()); 085 086 private String beanName; 087 088 private Environment environment; 089 090 private ServletContext servletContext; 091 092 private FilterConfig filterConfig; 093 094 private final Set<String> requiredProperties = new HashSet<String>(4); 095 096 097 /** 098 * Stores the bean name as defined in the Spring bean factory. 099 * <p>Only relevant in case of initialization as bean, to have a name as 100 * fallback to the filter name usually provided by a FilterConfig instance. 101 * @see org.springframework.beans.factory.BeanNameAware 102 * @see #getFilterName() 103 */ 104 @Override 105 public void setBeanName(String beanName) { 106 this.beanName = beanName; 107 } 108 109 /** 110 * Set the {@code Environment} that this filter runs in. 111 * <p>Any environment set here overrides the {@link StandardServletEnvironment} 112 * provided by default. 113 * <p>This {@code Environment} object is used only for resolving placeholders in 114 * resource paths passed into init-parameters for this filter. If no init-params are 115 * used, this {@code Environment} can be essentially ignored. 116 */ 117 @Override 118 public void setEnvironment(Environment environment) { 119 this.environment = environment; 120 } 121 122 /** 123 * Return the {@link Environment} associated with this filter. 124 * <p>If none specified, a default environment will be initialized via 125 * {@link #createEnvironment()}. 126 * @since 4.3.9 127 */ 128 @Override 129 public Environment getEnvironment() { 130 if (this.environment == null) { 131 this.environment = createEnvironment(); 132 } 133 return this.environment; 134 } 135 136 /** 137 * Create and return a new {@link StandardServletEnvironment}. 138 * <p>Subclasses may override this in order to configure the environment or 139 * specialize the environment type returned. 140 * @since 4.3.9 141 */ 142 protected Environment createEnvironment() { 143 return new StandardServletEnvironment(); 144 } 145 146 /** 147 * Stores the ServletContext that the bean factory runs in. 148 * <p>Only relevant in case of initialization as bean, to have a ServletContext 149 * as fallback to the context usually provided by a FilterConfig instance. 150 * @see org.springframework.web.context.ServletContextAware 151 * @see #getServletContext() 152 */ 153 @Override 154 public void setServletContext(ServletContext servletContext) { 155 this.servletContext = servletContext; 156 } 157 158 /** 159 * Calls the {@code initFilterBean()} method that might 160 * contain custom initialization of a subclass. 161 * <p>Only relevant in case of initialization as bean, where the 162 * standard {@code init(FilterConfig)} method won't be called. 163 * @see #initFilterBean() 164 * @see #init(javax.servlet.FilterConfig) 165 */ 166 @Override 167 public void afterPropertiesSet() throws ServletException { 168 initFilterBean(); 169 } 170 171 /** 172 * Subclasses may override this to perform custom filter shutdown. 173 * <p>Note: This method will be called from standard filter destruction 174 * as well as filter bean destruction in a Spring application context. 175 * <p>This default implementation is empty. 176 */ 177 @Override 178 public void destroy() { 179 } 180 181 182 /** 183 * Subclasses can invoke this method to specify that this property 184 * (which must match a JavaBean property they expose) is mandatory, 185 * and must be supplied as a config parameter. This should be called 186 * from the constructor of a subclass. 187 * <p>This method is only relevant in case of traditional initialization 188 * driven by a FilterConfig instance. 189 * @param property name of the required property 190 */ 191 protected final void addRequiredProperty(String property) { 192 this.requiredProperties.add(property); 193 } 194 195 /** 196 * Standard way of initializing this filter. 197 * Map config parameters onto bean properties of this filter, and 198 * invoke subclass initialization. 199 * @param filterConfig the configuration for this filter 200 * @throws ServletException if bean properties are invalid (or required 201 * properties are missing), or if subclass initialization fails. 202 * @see #initFilterBean 203 */ 204 @Override 205 public final void init(FilterConfig filterConfig) throws ServletException { 206 Assert.notNull(filterConfig, "FilterConfig must not be null"); 207 if (logger.isDebugEnabled()) { 208 logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'"); 209 } 210 211 this.filterConfig = filterConfig; 212 213 // Set bean properties from init parameters. 214 PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties); 215 if (!pvs.isEmpty()) { 216 try { 217 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); 218 ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext()); 219 Environment env = this.environment; 220 if (env == null) { 221 env = new StandardServletEnvironment(); 222 } 223 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env)); 224 initBeanWrapper(bw); 225 bw.setPropertyValues(pvs, true); 226 } 227 catch (BeansException ex) { 228 String msg = "Failed to set bean properties on filter '" + 229 filterConfig.getFilterName() + "': " + ex.getMessage(); 230 logger.error(msg, ex); 231 throw new NestedServletException(msg, ex); 232 } 233 } 234 235 // Let subclasses do whatever initialization they like. 236 initFilterBean(); 237 238 if (logger.isDebugEnabled()) { 239 logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully"); 240 } 241 } 242 243 /** 244 * Initialize the BeanWrapper for this GenericFilterBean, 245 * possibly with custom editors. 246 * <p>This default implementation is empty. 247 * @param bw the BeanWrapper to initialize 248 * @throws BeansException if thrown by BeanWrapper methods 249 * @see org.springframework.beans.BeanWrapper#registerCustomEditor 250 */ 251 protected void initBeanWrapper(BeanWrapper bw) throws BeansException { 252 } 253 254 /** 255 * Subclasses may override this to perform custom initialization. 256 * All bean properties of this filter will have been set before this 257 * method is invoked. 258 * <p>Note: This method will be called from standard filter initialization 259 * as well as filter bean initialization in a Spring application context. 260 * Filter name and ServletContext will be available in both cases. 261 * <p>This default implementation is empty. 262 * @throws ServletException if subclass initialization fails 263 * @see #getFilterName() 264 * @see #getServletContext() 265 */ 266 protected void initFilterBean() throws ServletException { 267 } 268 269 /** 270 * Make the FilterConfig of this filter available, if any. 271 * Analogous to GenericServlet's {@code getServletConfig()}. 272 * <p>Public to resemble the {@code getFilterConfig()} method 273 * of the Servlet Filter version that shipped with WebLogic 6.1. 274 * @return the FilterConfig instance, or {@code null} if none available 275 * @see javax.servlet.GenericServlet#getServletConfig() 276 */ 277 public final FilterConfig getFilterConfig() { 278 return this.filterConfig; 279 } 280 281 /** 282 * Make the name of this filter available to subclasses. 283 * Analogous to GenericServlet's {@code getServletName()}. 284 * <p>Takes the FilterConfig's filter name by default. 285 * If initialized as bean in a Spring application context, 286 * it falls back to the bean name as defined in the bean factory. 287 * @return the filter name, or {@code null} if none available 288 * @see javax.servlet.GenericServlet#getServletName() 289 * @see javax.servlet.FilterConfig#getFilterName() 290 * @see #setBeanName 291 */ 292 protected final String getFilterName() { 293 return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName); 294 } 295 296 /** 297 * Make the ServletContext of this filter available to subclasses. 298 * Analogous to GenericServlet's {@code getServletContext()}. 299 * <p>Takes the FilterConfig's ServletContext by default. 300 * If initialized as bean in a Spring application context, 301 * it falls back to the ServletContext that the bean factory runs in. 302 * @return the ServletContext instance, or {@code null} if none available 303 * @see javax.servlet.GenericServlet#getServletContext() 304 * @see javax.servlet.FilterConfig#getServletContext() 305 * @see #setServletContext 306 */ 307 protected final ServletContext getServletContext() { 308 return (this.filterConfig != null ? this.filterConfig.getServletContext() : this.servletContext); 309 } 310 311 312 /** 313 * PropertyValues implementation created from FilterConfig init parameters. 314 */ 315 @SuppressWarnings("serial") 316 private static class FilterConfigPropertyValues extends MutablePropertyValues { 317 318 /** 319 * Create new FilterConfigPropertyValues. 320 * @param config FilterConfig we'll use to take PropertyValues from 321 * @param requiredProperties set of property names we need, where 322 * we can't accept default values 323 * @throws ServletException if any required properties are missing 324 */ 325 public FilterConfigPropertyValues(FilterConfig config, Set<String> requiredProperties) 326 throws ServletException { 327 328 Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? 329 new HashSet<String>(requiredProperties) : null); 330 331 Enumeration<String> paramNames = config.getInitParameterNames(); 332 while (paramNames.hasMoreElements()) { 333 String property = paramNames.nextElement(); 334 Object value = config.getInitParameter(property); 335 addPropertyValue(new PropertyValue(property, value)); 336 if (missingProps != null) { 337 missingProps.remove(property); 338 } 339 } 340 341 // Fail if we are still missing properties. 342 if (!CollectionUtils.isEmpty(missingProps)) { 343 throw new ServletException( 344 "Initialization from FilterConfig for filter '" + config.getFilterName() + 345 "' failed; the following required properties were missing: " + 346 StringUtils.collectionToDelimitedString(missingProps, ", ")); 347 } 348 } 349 } 350 351}