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