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 }