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