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