/* * Copyright 2002-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; /** * Proxy for a standard Servlet 2.3 Filter, delegating to a Spring-managed * bean that implements the Filter interface. Supports a "targetBeanName" * filter init-param in <code>web.xml</code>, specifying the name of the * target bean in the Spring application context. * * <p><code>web.xml</code> will usually contain a DelegatingFilterProxy definition, * with the specified <code>filter-name</code> corresponding to a bean name in * Spring's root application context. All calls to the filter proxy will then * be delegated to that bean in the Spring context, which is required to implement * the standard Servlet 2.3 Filter interface. * * <p>This approach is particularly useful for Filter implementation with complex * setup needs, allowing to apply the full Spring bean definition machinery to * Filter instances. Alternatively, consider standard Filter setup in combination * with looking up service beans from the Spring root application context. * * <p><b>NOTE:</b> The lifecycle methods defined by the Servlet Filter interface * will by default <i>not</i> be delegated to the target bean, relying on the * Spring application context to manage the lifecycle of that bean. Specifying * the "targetFilterLifecycle" filter init-param as "true" will enforce invocation * of the <code>Filter.init</code> and <code>Filter.destroy</code> lifecycle methods * on the target bean, letting the servlet container manage the filter lifecycle. * * <p>This class is inspired by Acegi Security's FilterToBeanProxy class, * written by Ben Alex. * * @author Juergen Hoeller * @author Sam Brannen * @since 1.2 * @see #setTargetBeanName * @see #setTargetFilterLifecycle * @see javax.servlet.Filter#doFilter * @see javax.servlet.Filter#init * @see javax.servlet.Filter#destroy */ public class DelegatingFilterProxy extends GenericFilterBean { private String contextAttribute; private String targetBeanName; private boolean targetFilterLifecycle = false; private Filter delegate; private final Object delegateMonitor = new Object(); /** * Set the name of the ServletContext attribute which should be used to retrieve the * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean. */ public void setContextAttribute(String contextAttribute) { this.contextAttribute = contextAttribute; } /** * Return the name of the ServletContext attribute which should be used to retrieve the * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean. */ public String getContextAttribute() { return this.contextAttribute; } /** * Set the name of the target bean in the Spring application context. * The target bean must implement the standard Servlet 2.3 Filter interface. * <p>By default, the <code>filter-name</code> as specified for the * DelegatingFilterProxy in <code>web.xml</code> will be used. */ public void setTargetBeanName(String targetBeanName) { this.targetBeanName = targetBeanName; } /** * Return the name of the target bean in the Spring application context. */ protected String getTargetBeanName() { return this.targetBeanName; } /** * Set whether to invoke the <code>Filter.init</code> and * <code>Filter.destroy</code> lifecycle methods on the target bean. * <p>Default is "false"; target beans usually rely on the Spring application * context for managing their lifecycle. Setting this flag to "true" means * that the servlet container will control the lifecycle of the target * Filter, with this proxy delegating the corresponding calls. */ public void setTargetFilterLifecycle(boolean targetFilterLifecycle) { this.targetFilterLifecycle = targetFilterLifecycle; } /** * Return whether to invoke the <code>Filter.init</code> and * <code>Filter.destroy</code> lifecycle methods on the target bean. */ protected boolean isTargetFilterLifecycle() { return this.targetFilterLifecycle; } protected void initFilterBean() throws ServletException { // If no target bean name specified, use filter name. if (this.targetBeanName == null) { this.targetBeanName = getFilterName(); } // Fetch Spring root application context and initialize the delegate early, // if possible. If the root application context will be started after this // filter proxy, we'll have to resort to lazy initialization. synchronized (this.delegateMonitor) { WebApplicationContext wac = findWebApplicationContext(); if (wac != null) { this.delegate = initDelegate(wac); } } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Lazily initialize the delegate if necessary. Filter delegateToUse = null; synchronized (this.delegateMonitor) { if (this.delegate == null) { WebApplicationContext wac = findWebApplicationContext(); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); } this.delegate = initDelegate(wac); } delegateToUse = this.delegate; } // Let the delegate perform the actual doFilter operation. invokeDelegate(delegateToUse, request, response, filterChain); } public void destroy() { Filter delegateToUse = null; synchronized (this.delegateMonitor) { delegateToUse = this.delegate; } if (delegateToUse != null) { destroyDelegate(delegateToUse); } } /** * Retrieve a <code>WebApplicationContext</code> from the <code>ServletContext</code> * attribute with the {@link #setContextAttribute configured name}. The * <code>WebApplicationContext</code> must have already been loaded and stored in the * <code>ServletContext</code> before this filter gets initialized (or invoked). * <p>Subclasses may override this method to provide a different * <code>WebApplicationContext</code> retrieval strategy. * @return the WebApplicationContext for this proxy, or <code>null</code> if not found * @see #getContextAttribute() */ protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName != null) { return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); } else { return WebApplicationContextUtils.getWebApplicationContext(getServletContext()); } } /** * Initialize the Filter delegate, defined as bean the given Spring * application context. * <p>Default implementation fetches the bean from the application context * and calls the standard <code>Filter.init</code> method on it, passing * in the FilterConfig of this Filter proxy. * @param wac the root application context * @return the initialized delegate Filter * @throws ServletException if thrown by the Filter * @see #getTargetBeanName() * @see #isTargetFilterLifecycle() * @see #getFilterConfig() * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ protected Filter initDelegate(WebApplicationContext wac) throws ServletException { Filter delegate = (Filter) wac.getBean(getTargetBeanName(), Filter.class); if (isTargetFilterLifecycle()) { delegate.init(getFilterConfig()); } return delegate; } /** * Actually invoke the delegate Filter with the given request and response. * @param delegate the delegate Filter * @param request the current HTTP request * @param response the current HTTP response * @param filterChain the current FilterChain * @throws ServletException if thrown by the Filter * @throws IOException if thrown by the Filter */ protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { delegate.doFilter(request, response, filterChain); } /** * Destroy the Filter delegate. * Default implementation simply calls <code>Filter.destroy</code> on it. * @param delegate the Filter delegate (never <code>null</code>) * @see #isTargetFilterLifecycle() * @see javax.servlet.Filter#destroy() */ protected void destroyDelegate(Filter delegate) { if (isTargetFilterLifecycle()) { delegate.destroy(); } } }