View Javadoc

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