001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.util;
017:
018: import org.springframework.beans.factory.BeanFactoryUtils;
019:
020: import org.springframework.context.ApplicationContext;
021:
022: import org.springframework.web.context.support.WebApplicationContextUtils;
023:
024: import java.io.IOException;
025:
026: import java.util.Map;
027:
028: import javax.servlet.Filter;
029: import javax.servlet.FilterChain;
030: import javax.servlet.FilterConfig;
031: import javax.servlet.ServletContext;
032: import javax.servlet.ServletException;
033: import javax.servlet.ServletRequest;
034: import javax.servlet.ServletResponse;
035:
036: /**
037: * <p>Delegates <code>Filter</code> requests to a Spring-managed bean.</p>
038: *
039: * <p>This class acts as a proxy on behalf of a
040: * target <code>Filter</code> that is defined in the Spring bean context. It is necessary to specify which target
041: * <code>Filter</code> should be proxied as a filter initialization parameter.</p>
042: *
043: * <p>On filter initialisation, the class will use Spring's {@link
044: * WebApplicationContextUtils#getWebApplicationContext(ServletContext sc)} method to obtain an
045: * <code>ApplicationContext</code> instance. It will expect to find the target <code>Filter</code> in this
046: * <code>ApplicationContext</code>.</p>
047: *
048: * <p>To use this filter, it is necessary to specify <b>one</b> of the following filter initialization parameters:
049: * <ul>
050: * <li><code>targetClass</code> indicates the class of the target <code>Filter</code> defined in the bean
051: * context. The only requirements are that this target class implements the <code>javax.servlet.Filter</code>
052: * interface and at least one instance is available in the <code>ApplicationContext</code>.</li>
053: * <li><code>targetBean</code> indicates the bean name of the target class.</li>
054: * </ul>
055: * If both initialization parameters are specified, <code>targetBean</code> takes priority.</p>
056: *
057: * <p>An additional
058: * initialization parameter, <code>init</code>, is also supported. If set to "<code>lazy</code>" the initialization
059: * will take place on the first HTTP request, rather than at filter creation time. This makes it possible to use
060: * <code>FilterToBeanProxy</code> with the Spring <code>ContextLoaderServlet</code>. Where possible you should not use
061: * this initialization parameter, instead using <code>ContextLoaderListener</code>.</p>
062: *
063: * <p>A final optional initialization parameter, <code>lifecycle</code>, determines whether the servlet container
064: * or the IoC container manages the lifecycle of the proxied filter. When possible you should write your filters to be
065: * managed via the IoC container interfaces such as {@link org.springframework.beans.factory.InitializingBean} and
066: * {@link org.springframework.beans.factory.DisposableBean}. If you cannot control the filters you wish to proxy (eg
067: * you do not have their source code) you might need to allow the servlet container to manage lifecycle via the {@link
068: * javax.servlet.Filter#init(javax.servlet.FilterConfig)} and {@link javax.servlet.Filter#destroy()} methods. If this
069: * case, set the <code>lifecycle</code> initialization parameter to <code>servlet-container-managed</code>. If the
070: * parameter is any other value, servlet container lifecycle methods will not be delegated through to the proxy.</p>
071: *
072: * @author Ben Alex
073: * @version $Id: FilterToBeanProxy.java 1784 2007-02-24 21:00:24Z luke_t $
074: */
075: public class FilterToBeanProxy implements Filter {
076: //~ Instance fields ================================================================================================
077:
078: private Filter delegate;
079: private FilterConfig filterConfig;
080: private boolean initialized = false;
081: private boolean servletContainerManaged = false;
082:
083: //~ Methods ========================================================================================================
084:
085: public void destroy() {
086: if ((delegate != null) && servletContainerManaged) {
087: delegate.destroy();
088: }
089: }
090:
091: public void doFilter(ServletRequest request,
092: ServletResponse response, FilterChain chain)
093: throws IOException, ServletException {
094: if (!initialized) {
095: doInit();
096: }
097:
098: delegate.doFilter(request, response, chain);
099: }
100:
101: private synchronized void doInit() throws ServletException {
102: if (initialized) {
103: // already initialized, so don't re-initialize
104: return;
105: }
106:
107: String targetBean = filterConfig.getInitParameter("targetBean");
108:
109: if ("".equals(targetBean)) {
110: targetBean = null;
111: }
112:
113: String lifecycle = filterConfig.getInitParameter("lifecycle");
114:
115: if ("servlet-container-managed".equals(lifecycle)) {
116: servletContainerManaged = true;
117: }
118:
119: ApplicationContext ctx = this .getContext(filterConfig);
120:
121: String beanName = null;
122:
123: if ((targetBean != null) && ctx.containsBean(targetBean)) {
124: beanName = targetBean;
125: } else if (targetBean != null) {
126: throw new ServletException("targetBean '" + targetBean
127: + "' not found in context");
128: } else {
129: String targetClassString = filterConfig
130: .getInitParameter("targetClass");
131:
132: if ((targetClassString == null)
133: || "".equals(targetClassString)) {
134: throw new ServletException(
135: "targetClass or targetBean must be specified");
136: }
137:
138: Class targetClass;
139:
140: try {
141: targetClass = Thread.currentThread()
142: .getContextClassLoader().loadClass(
143: targetClassString);
144: } catch (ClassNotFoundException ex) {
145: throw new ServletException("Class of type "
146: + targetClassString
147: + " not found in classloader");
148: }
149:
150: Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
151: ctx, targetClass, true, true);
152:
153: if (beans.size() == 0) {
154: throw new ServletException(
155: "Bean context must contain at least one bean of type "
156: + targetClassString);
157: }
158:
159: beanName = (String) beans.keySet().iterator().next();
160: }
161:
162: Object object = ctx.getBean(beanName);
163:
164: if (!(object instanceof Filter)) {
165: throw new ServletException("Bean '" + beanName
166: + "' does not implement javax.servlet.Filter");
167: }
168:
169: delegate = (Filter) object;
170:
171: if (servletContainerManaged) {
172: delegate.init(filterConfig);
173: }
174:
175: // Set initialized to true at the end of the synchronized method, so
176: // that invocations of doFilter() before this method has completed will not
177: // cause NullPointerException
178: initialized = true;
179: }
180:
181: /**
182: * Allows test cases to override where application context obtained from.
183: *
184: * @param filterConfig which can be used to find the <code>ServletContext</code>
185: *
186: * @return the Spring application context
187: */
188: protected ApplicationContext getContext(FilterConfig filterConfig) {
189: return WebApplicationContextUtils
190: .getRequiredWebApplicationContext(filterConfig
191: .getServletContext());
192: }
193:
194: public void init(FilterConfig filterConfig) throws ServletException {
195: this .filterConfig = filterConfig;
196:
197: String strategy = filterConfig.getInitParameter("init");
198:
199: if ((strategy != null) && strategy.toLowerCase().equals("lazy")) {
200: return;
201: }
202:
203: doInit();
204: }
205: }
|