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.io.IOException;
020import javax.servlet.Filter;
021import javax.servlet.FilterChain;
022import javax.servlet.ServletException;
023import javax.servlet.ServletRequest;
024import javax.servlet.ServletResponse;
025
026import org.springframework.context.ConfigurableApplicationContext;
027import org.springframework.util.Assert;
028import org.springframework.web.context.WebApplicationContext;
029import org.springframework.web.context.support.WebApplicationContextUtils;
030
031/**
032 * Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that
033 * implements the Filter interface. Supports a "targetBeanName" filter init-param
034 * in {@code web.xml}, specifying the name of the target bean in the Spring
035 * application context.
036 *
037 * <p>{@code web.xml} will usually contain a {@code DelegatingFilterProxy} definition,
038 * with the specified {@code filter-name} corresponding to a bean name in
039 * Spring's root application context. All calls to the filter proxy will then
040 * be delegated to that bean in the Spring context, which is required to implement
041 * the standard Servlet Filter interface.
042 *
043 * <p>This approach is particularly useful for Filter implementation with complex
044 * setup needs, allowing to apply the full Spring bean definition machinery to
045 * Filter instances. Alternatively, consider standard Filter setup in combination
046 * with looking up service beans from the Spring root application context.
047 *
048 * <p><b>NOTE:</b> The lifecycle methods defined by the Servlet Filter interface
049 * will by default <i>not</i> be delegated to the target bean, relying on the
050 * Spring application context to manage the lifecycle of that bean. Specifying
051 * the "targetFilterLifecycle" filter init-param as "true" will enforce invocation
052 * of the {@code Filter.init} and {@code Filter.destroy} lifecycle methods
053 * on the target bean, letting the servlet container manage the filter lifecycle.
054 *
055 * <p>As of Spring 3.1, {@code DelegatingFilterProxy} has been updated to optionally accept
056 * constructor parameters when using Servlet 3.0's instance-based filter registration
057 * methods, usually in conjunction with Spring 3.1's
058 * {@link org.springframework.web.WebApplicationInitializer} SPI. These constructors allow
059 * for providing the delegate Filter bean directly, or providing the application context
060 * and bean name to fetch, avoiding the need to look up the application context from the
061 * ServletContext.
062 *
063 * <p>This class was originally inspired by Spring Security's {@code FilterToBeanProxy}
064 * class, written by Ben Alex.
065 *
066 * @author Juergen Hoeller
067 * @author Sam Brannen
068 * @author Chris Beams
069 * @since 1.2
070 * @see #setTargetBeanName
071 * @see #setTargetFilterLifecycle
072 * @see javax.servlet.Filter#doFilter
073 * @see javax.servlet.Filter#init
074 * @see javax.servlet.Filter#destroy
075 * @see #DelegatingFilterProxy(Filter)
076 * @see #DelegatingFilterProxy(String)
077 * @see #DelegatingFilterProxy(String, WebApplicationContext)
078 * @see javax.servlet.ServletContext#addFilter(String, Filter)
079 * @see org.springframework.web.WebApplicationInitializer
080 */
081public class DelegatingFilterProxy extends GenericFilterBean {
082
083        private String contextAttribute;
084
085        private WebApplicationContext webApplicationContext;
086
087        private String targetBeanName;
088
089        private boolean targetFilterLifecycle = false;
090
091        private volatile Filter delegate;
092
093        private final Object delegateMonitor = new Object();
094
095
096        /**
097         * Create a new {@code DelegatingFilterProxy}. For traditional (pre-Servlet 3.0) use
098         * in {@code web.xml}.
099         * @see #setTargetBeanName(String)
100         */
101        public DelegatingFilterProxy() {
102        }
103
104        /**
105         * Create a new {@code DelegatingFilterProxy} with the given {@link Filter} delegate.
106         * Bypasses entirely the need for interacting with a Spring application context,
107         * specifying the {@linkplain #setTargetBeanName target bean name}, etc.
108         * <p>For use in Servlet 3.0+ environments where instance-based registration of
109         * filters is supported.
110         * @param delegate the {@code Filter} instance that this proxy will delegate to and
111         * manage the lifecycle for (must not be {@code null}).
112         * @see #doFilter(ServletRequest, ServletResponse, FilterChain)
113         * @see #invokeDelegate(Filter, ServletRequest, ServletResponse, FilterChain)
114         * @see #destroy()
115         * @see #setEnvironment(org.springframework.core.env.Environment)
116         */
117        public DelegatingFilterProxy(Filter delegate) {
118                Assert.notNull(delegate, "Delegate Filter must not be null");
119                this.delegate = delegate;
120        }
121
122        /**
123         * Create a new {@code DelegatingFilterProxy} that will retrieve the named target
124         * bean from the Spring {@code WebApplicationContext} found in the {@code ServletContext}
125         * (either the 'root' application context or the context named by
126         * {@link #setContextAttribute}).
127         * <p>For use in Servlet 3.0+ environments where instance-based registration of
128         * filters is supported.
129         * <p>The target bean must implement the standard Servlet Filter.
130         * @param targetBeanName name of the target filter bean to look up in the Spring
131         * application context (must not be {@code null}).
132         * @see #findWebApplicationContext()
133         * @see #setEnvironment(org.springframework.core.env.Environment)
134         */
135        public DelegatingFilterProxy(String targetBeanName) {
136                this(targetBeanName, null);
137        }
138
139        /**
140         * Create a new {@code DelegatingFilterProxy} that will retrieve the named target
141         * bean from the given Spring {@code WebApplicationContext}.
142         * <p>For use in Servlet 3.0+ environments where instance-based registration of
143         * filters is supported.
144         * <p>The target bean must implement the standard Servlet Filter interface.
145         * <p>The given {@code WebApplicationContext} may or may not be refreshed when passed
146         * in. If it has not, and if the context implements {@link ConfigurableApplicationContext},
147         * a {@link ConfigurableApplicationContext#refresh() refresh()} will be attempted before
148         * retrieving the named target bean.
149         * <p>This proxy's {@code Environment} will be inherited from the given
150         * {@code WebApplicationContext}.
151         * @param targetBeanName name of the target filter bean in the Spring application
152         * context (must not be {@code null}).
153         * @param wac the application context from which the target filter will be retrieved;
154         * if {@code null}, an application context will be looked up from {@code ServletContext}
155         * as a fallback.
156         * @see #findWebApplicationContext()
157         * @see #setEnvironment(org.springframework.core.env.Environment)
158         */
159        public DelegatingFilterProxy(String targetBeanName, WebApplicationContext wac) {
160                Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
161                this.setTargetBeanName(targetBeanName);
162                this.webApplicationContext = wac;
163                if (wac != null) {
164                        this.setEnvironment(wac.getEnvironment());
165                }
166        }
167
168        /**
169         * Set the name of the ServletContext attribute which should be used to retrieve the
170         * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean.
171         */
172        public void setContextAttribute(String contextAttribute) {
173                this.contextAttribute = contextAttribute;
174        }
175
176        /**
177         * Return the name of the ServletContext attribute which should be used to retrieve the
178         * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean.
179         */
180        public String getContextAttribute() {
181                return this.contextAttribute;
182        }
183
184        /**
185         * Set the name of the target bean in the Spring application context.
186         * The target bean must implement the standard Servlet Filter interface.
187         * <p>By default, the {@code filter-name} as specified for the
188         * DelegatingFilterProxy in {@code web.xml} will be used.
189         */
190        public void setTargetBeanName(String targetBeanName) {
191                this.targetBeanName = targetBeanName;
192        }
193
194        /**
195         * Return the name of the target bean in the Spring application context.
196         */
197        protected String getTargetBeanName() {
198                return this.targetBeanName;
199        }
200
201        /**
202         * Set whether to invoke the {@code Filter.init} and
203         * {@code Filter.destroy} lifecycle methods on the target bean.
204         * <p>Default is "false"; target beans usually rely on the Spring application
205         * context for managing their lifecycle. Setting this flag to "true" means
206         * that the servlet container will control the lifecycle of the target
207         * Filter, with this proxy delegating the corresponding calls.
208         */
209        public void setTargetFilterLifecycle(boolean targetFilterLifecycle) {
210                this.targetFilterLifecycle = targetFilterLifecycle;
211        }
212
213        /**
214         * Return whether to invoke the {@code Filter.init} and
215         * {@code Filter.destroy} lifecycle methods on the target bean.
216         */
217        protected boolean isTargetFilterLifecycle() {
218                return this.targetFilterLifecycle;
219        }
220
221
222        @Override
223        protected void initFilterBean() throws ServletException {
224                synchronized (this.delegateMonitor) {
225                        if (this.delegate == null) {
226                                // If no target bean name specified, use filter name.
227                                if (this.targetBeanName == null) {
228                                        this.targetBeanName = getFilterName();
229                                }
230                                // Fetch Spring root application context and initialize the delegate early,
231                                // if possible. If the root application context will be started after this
232                                // filter proxy, we'll have to resort to lazy initialization.
233                                WebApplicationContext wac = findWebApplicationContext();
234                                if (wac != null) {
235                                        this.delegate = initDelegate(wac);
236                                }
237                        }
238                }
239        }
240
241        @Override
242        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
243                        throws ServletException, IOException {
244
245                // Lazily initialize the delegate if necessary.
246                Filter delegateToUse = this.delegate;
247                if (delegateToUse == null) {
248                        synchronized (this.delegateMonitor) {
249                                delegateToUse = this.delegate;
250                                if (delegateToUse == null) {
251                                        WebApplicationContext wac = findWebApplicationContext();
252                                        if (wac == null) {
253                                                throw new IllegalStateException("No WebApplicationContext found: " +
254                                                                "no ContextLoaderListener or DispatcherServlet registered?");
255                                        }
256                                        delegateToUse = initDelegate(wac);
257                                }
258                                this.delegate = delegateToUse;
259                        }
260                }
261
262                // Let the delegate perform the actual doFilter operation.
263                invokeDelegate(delegateToUse, request, response, filterChain);
264        }
265
266        @Override
267        public void destroy() {
268                Filter delegateToUse = this.delegate;
269                if (delegateToUse != null) {
270                        destroyDelegate(delegateToUse);
271                }
272        }
273
274
275        /**
276         * Return the {@code WebApplicationContext} passed in at construction time, if available.
277         * Otherwise, attempt to retrieve a {@code WebApplicationContext} from the
278         * {@code ServletContext} attribute with the {@linkplain #setContextAttribute
279         * configured name} if set. Otherwise look up a {@code WebApplicationContext} under
280         * the well-known "root" application context attribute. The
281         * {@code WebApplicationContext} must have already been loaded and stored in the
282         * {@code ServletContext} before this filter gets initialized (or invoked).
283         * <p>Subclasses may override this method to provide a different
284         * {@code WebApplicationContext} retrieval strategy.
285         * @return the {@code WebApplicationContext} for this proxy, or {@code null} if not found
286         * @see #DelegatingFilterProxy(String, WebApplicationContext)
287         * @see #getContextAttribute()
288         * @see WebApplicationContextUtils#getWebApplicationContext(javax.servlet.ServletContext)
289         * @see WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
290         */
291        protected WebApplicationContext findWebApplicationContext() {
292                if (this.webApplicationContext != null) {
293                        // The user has injected a context at construction time -> use it...
294                        if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
295                                ConfigurableApplicationContext cac = (ConfigurableApplicationContext) this.webApplicationContext;
296                                if (!cac.isActive()) {
297                                        // The context has not yet been refreshed -> do so before returning it...
298                                        cac.refresh();
299                                }
300                        }
301                        return this.webApplicationContext;
302                }
303                String attrName = getContextAttribute();
304                if (attrName != null) {
305                        return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
306                }
307                else {
308                        return WebApplicationContextUtils.findWebApplicationContext(getServletContext());
309                }
310        }
311
312        /**
313         * Initialize the Filter delegate, defined as bean the given Spring
314         * application context.
315         * <p>The default implementation fetches the bean from the application context
316         * and calls the standard {@code Filter.init} method on it, passing
317         * in the FilterConfig of this Filter proxy.
318         * @param wac the root application context
319         * @return the initialized delegate Filter
320         * @throws ServletException if thrown by the Filter
321         * @see #getTargetBeanName()
322         * @see #isTargetFilterLifecycle()
323         * @see #getFilterConfig()
324         * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
325         */
326        protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
327                Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
328                if (isTargetFilterLifecycle()) {
329                        delegate.init(getFilterConfig());
330                }
331                return delegate;
332        }
333
334        /**
335         * Actually invoke the delegate Filter with the given request and response.
336         * @param delegate the delegate Filter
337         * @param request the current HTTP request
338         * @param response the current HTTP response
339         * @param filterChain the current FilterChain
340         * @throws ServletException if thrown by the Filter
341         * @throws IOException if thrown by the Filter
342         */
343        protected void invokeDelegate(
344                        Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
345                        throws ServletException, IOException {
346
347                delegate.doFilter(request, response, filterChain);
348        }
349
350        /**
351         * Destroy the Filter delegate.
352         * Default implementation simply calls {@code Filter.destroy} on it.
353         * @param delegate the Filter delegate (never {@code null})
354         * @see #isTargetFilterLifecycle()
355         * @see javax.servlet.Filter#destroy()
356         */
357        protected void destroyDelegate(Filter delegate) {
358                if (isTargetFilterLifecycle()) {
359                        delegate.destroy();
360                }
361        }
362
363}