Source code: net/sf/acegisecurity/util/FilterToBeanProxy.java
1 /* Copyright 2004, 2005 Acegi Technology Pty Limited
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 package net.sf.acegisecurity.util;
17
18 import org.springframework.beans.factory.BeanFactoryUtils;
19
20 import org.springframework.context.ApplicationContext;
21
22 import org.springframework.web.context.support.WebApplicationContextUtils;
23
24 import java.io.IOException;
25
26 import java.util.Map;
27
28 import javax.servlet.Filter;
29 import javax.servlet.FilterChain;
30 import javax.servlet.FilterConfig;
31 import javax.servlet.ServletException;
32 import javax.servlet.ServletRequest;
33 import javax.servlet.ServletResponse;
34
35
36 /**
37 * Delegates <code>Filter</code> requests to a Spring-managed bean.
38 *
39 * <p>
40 * This class acts as a proxy on behalf of a target <code>Filter</code> that is
41 * defined in the Spring bean context. It is necessary to specify which target
42 * <code>Filter</code> should be proxied as a filter initialization parameter.
43 * </p>
44 *
45 * <p>
46 * On filter initialisation, the class will use Spring's {@link
47 * WebApplicationContextUtils#getWebApplicationContext(ServletContext sc)}
48 * method to obtain an <code>ApplicationContext</code> instance. It will
49 * expect to find the target <code>Filter</code> in this
50 * <code>ApplicationContext</code>.
51 * </p>
52 *
53 * <p>
54 * To use this filter, it is necessary to specify <b>one</b> of the following
55 * filter initialization parameters:
56 * </p>
57 *
58 * <ul>
59 * <li>
60 * <code>targetClass</code> indicates the class of the target
61 * <code>Filter</code> defined in the bean context. The only requirements are
62 * that this target class implements the <code>javax.servlet.Filter</code>
63 * interface and at least one instance is available in the
64 * <code>ApplicationContext</code>.
65 * </li>
66 * <li>
67 * <code>targetBean</code> indicates the bean name of the target class.
68 * </li>
69 * </ul>
70 *
71 * If both initialization parameters are specified, <code>targetBean</code>
72 * takes priority.
73 *
74 * <P>
75 * An additional initialization parameter, <code>init</code>, is also
76 * supported. If set to "<code>lazy</code>" the initialization will take place
77 * on the first HTTP request, rather than at filter creation time. This makes
78 * it possible to use <code>FilterToBeanProxy</code> with the Spring
79 * <code>ContextLoaderServlet</code>. Where possible you should not use this
80 * initialization parameter, instead using <code>ContextLoaderListener</code>.
81 * </p>
82 *
83 * <p>
84 * A final optional initialization parameter, <code>lifecycle</code>,
85 * determines whether the servlet container or the IoC container manages the
86 * lifecycle of the proxied filter. When possible you should write your
87 * filters to be managed via the IoC container interfaces such as {@link
88 * org.springframework.beans.factory.InitializingBean} and {@link
89 * org.springframework.beans.factory.DisposableBean}. If you cannot control
90 * the filters you wish to proxy (eg you do not have their source code) you
91 * might need to allow the servlet container to manage lifecycle via the
92 * {@link javax.servlet.Filter#init(javax.servlet.FilterConfig)} and {@link
93 * javax.servlet.Filter#destroy()} methods. If this case, set the
94 * <code>lifecycle</code> initialization parameter to
95 * <code>servlet-container-managed</code>. If the parameter is any other
96 * value, servlet container lifecycle methods will not be delegated through to
97 * the proxy.
98 * </p>
99 *
100 * @author Ben Alex
101 * @version $Id: FilterToBeanProxy.java,v 1.6 2005/02/20 05:38:57 benalex Exp $
102 */
103 public class FilterToBeanProxy implements Filter {
104 //~ Instance fields ========================================================
105
106 private Filter delegate;
107 private FilterConfig filterConfig;
108 private boolean initialized = false;
109 private boolean servletContainerManaged = false;
110
111 //~ Methods ================================================================
112
113 public void destroy() {
114 if ((delegate != null) && servletContainerManaged) {
115 delegate.destroy();
116 }
117 }
118
119 public void doFilter(ServletRequest request, ServletResponse response,
120 FilterChain chain) throws IOException, ServletException {
121 if (!initialized) {
122 doInit();
123 }
124
125 delegate.doFilter(request, response, chain);
126 }
127
128 public void init(FilterConfig filterConfig) throws ServletException {
129 this.filterConfig = filterConfig;
130
131 String strategy = filterConfig.getInitParameter("init");
132
133 if ((strategy != null) && strategy.toLowerCase().equals("lazy")) {
134 return;
135 }
136
137 doInit();
138 }
139
140 /**
141 * Allows test cases to override where application context obtained from.
142 *
143 * @param filterConfig which can be used to find the
144 * <code>ServletContext</code>
145 *
146 * @return the Spring application context
147 */
148 protected ApplicationContext getContext(FilterConfig filterConfig) {
149 return WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig
150 .getServletContext());
151 }
152
153 private void doInit() throws ServletException {
154 initialized = true;
155
156 String targetBean = filterConfig.getInitParameter("targetBean");
157
158 if ("".equals(targetBean)) {
159 targetBean = null;
160 }
161
162 String lifecycle = filterConfig.getInitParameter("lifecycle");
163
164 if ("servlet-container-managed".equals(lifecycle)) {
165 servletContainerManaged = true;
166 }
167
168 ApplicationContext ctx = this.getContext(filterConfig);
169
170 String beanName = null;
171
172 if ((targetBean != null) && ctx.containsBean(targetBean)) {
173 beanName = targetBean;
174 } else if (targetBean != null) {
175 throw new ServletException("targetBean '" + targetBean
176 + "' not found in context");
177 } else {
178 String targetClassString = filterConfig.getInitParameter(
179 "targetClass");
180
181 if ((targetClassString == null) || "".equals(targetClassString)) {
182 throw new ServletException(
183 "targetClass or targetBean must be specified");
184 }
185
186 Class targetClass;
187
188 try {
189 targetClass = Thread.currentThread().getContextClassLoader()
190 .loadClass(targetClassString);
191 } catch (ClassNotFoundException ex) {
192 throw new ServletException("Class of type " + targetClassString
193 + " not found in classloader");
194 }
195
196 Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx,
197 targetClass, true, true);
198
199 if (beans.size() == 0) {
200 throw new ServletException(
201 "Bean context must contain at least one bean of type "
202 + targetClassString);
203 }
204
205 beanName = (String) beans.keySet().iterator().next();
206 }
207
208 Object object = ctx.getBean(beanName);
209
210 if (!(object instanceof Filter)) {
211 throw new ServletException("Bean '" + beanName
212 + "' does not implement javax.servlet.Filter");
213 }
214
215 delegate = (Filter) object;
216
217 if (servletContainerManaged) {
218 delegate.init(filterConfig);
219 }
220 }
221 }