Home | History | Annotate | Download | only in servlet
      1 /*
      2  * Copyright (C) 2006 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.inject.servlet;
     18 
     19 import com.google.common.base.Throwables;
     20 import com.google.inject.Inject;
     21 import com.google.inject.Key;
     22 import com.google.inject.OutOfScopeException;
     23 import com.google.inject.internal.Errors;
     24 import java.io.IOException;
     25 import java.lang.ref.WeakReference;
     26 import java.util.concurrent.locks.Lock;
     27 import java.util.concurrent.locks.ReentrantLock;
     28 import java.util.logging.Logger;
     29 import javax.servlet.Filter;
     30 import javax.servlet.FilterChain;
     31 import javax.servlet.FilterConfig;
     32 import javax.servlet.ServletContext;
     33 import javax.servlet.ServletException;
     34 import javax.servlet.ServletRequest;
     35 import javax.servlet.ServletResponse;
     36 import javax.servlet.http.HttpServletRequest;
     37 import javax.servlet.http.HttpServletResponse;
     38 
     39 /**
     40  * Apply this filter in web.xml above all other filters (typically), to all requests where you plan
     41  * to use servlet scopes. This is also needed in order to dispatch requests to injectable filters
     42  * and servlets:
     43  *
     44  * <pre>
     45  *  &lt;filter&gt;
     46  *    &lt;filter-name&gt;guiceFilter&lt;/filter-name&gt;
     47  *    &lt;filter-class&gt;<b>com.google.inject.servlet.GuiceFilter</b>&lt;/filter-class&gt;
     48  *  &lt;/filter&gt;
     49  *
     50  *  &lt;filter-mapping&gt;
     51  *    &lt;filter-name&gt;guiceFilter&lt;/filter-name&gt;
     52  *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
     53  *  &lt;/filter-mapping&gt;
     54  *  </pre>
     55  *
     56  * This filter must appear before every filter that makes use of Guice injection or servlet scopes
     57  * functionality. Typically, you will only register this filter in web.xml and register any other
     58  * filters (and servlets) using a {@link ServletModule}.
     59  *
     60  * @author crazybob (at) google.com (Bob Lee)
     61  * @author dhanji (at) gmail.com (Dhanji R. Prasanna)
     62  */
     63 public class GuiceFilter implements Filter {
     64   static final ThreadLocal<Context> localContext = new ThreadLocal<>();
     65   static volatile FilterPipeline pipeline = new DefaultFilterPipeline();
     66 
     67   /** We allow both the static and dynamic versions of the pipeline to exist. */
     68   private final FilterPipeline injectedPipeline;
     69 
     70   /** Used to inject the servlets configured via {@link ServletModule} */
     71   static volatile WeakReference<ServletContext> servletContext =
     72       new WeakReference<ServletContext>(null);
     73 
     74   private static final String MULTIPLE_INJECTORS_WARNING =
     75       "Multiple Servlet injectors detected. This is a warning "
     76           + "indicating that you have more than one "
     77           + GuiceFilter.class.getSimpleName()
     78           + " running "
     79           + "in your web application. If this is deliberate, you may safely "
     80           + "ignore this message. If this is NOT deliberate however, "
     81           + "your application may not work as expected.";
     82 
     83   private static final Logger LOGGER = Logger.getLogger(GuiceFilter.class.getName());
     84 
     85   public GuiceFilter() {
     86     // Use the static FilterPipeline
     87     this(null);
     88   }
     89 
     90   @Inject
     91   GuiceFilter(FilterPipeline filterPipeline) {
     92     injectedPipeline = filterPipeline;
     93   }
     94 
     95   //VisibleForTesting
     96   @Inject
     97   static void setPipeline(FilterPipeline pipeline) {
     98 
     99     // This can happen if you create many injectors and they all have their own
    100     // servlet module. This is legal, caveat a small warning.
    101     if (GuiceFilter.pipeline instanceof ManagedFilterPipeline) {
    102       LOGGER.warning(MULTIPLE_INJECTORS_WARNING);
    103     }
    104 
    105     // We overwrite the default pipeline
    106     GuiceFilter.pipeline = pipeline;
    107   }
    108 
    109   //VisibleForTesting
    110   static void reset() {
    111     pipeline = new DefaultFilterPipeline();
    112     localContext.remove();
    113   }
    114 
    115   @Override
    116   public void doFilter(
    117       final ServletRequest servletRequest,
    118       final ServletResponse servletResponse,
    119       final FilterChain filterChain)
    120       throws IOException, ServletException {
    121 
    122     final FilterPipeline filterPipeline = getFilterPipeline();
    123 
    124     Context previous = GuiceFilter.localContext.get();
    125     HttpServletRequest request = (HttpServletRequest) servletRequest;
    126     HttpServletResponse response = (HttpServletResponse) servletResponse;
    127     HttpServletRequest originalRequest =
    128         (previous != null) ? previous.getOriginalRequest() : request;
    129     try {
    130       RequestScoper.CloseableScope scope = new Context(originalRequest, request, response).open();
    131       try {
    132         //dispatch across the servlet pipeline, ensuring web.xml's filterchain is honored
    133         filterPipeline.dispatch(servletRequest, servletResponse, filterChain);
    134       } finally {
    135         scope.close();
    136       }
    137     } catch (IOException e) {
    138       throw e;
    139     } catch (ServletException e) {
    140       throw e;
    141     } catch (Exception e) {
    142       Throwables.propagate(e);
    143     }
    144   }
    145 
    146   static HttpServletRequest getOriginalRequest(Key<?> key) {
    147     return getContext(key).getOriginalRequest();
    148   }
    149 
    150   static HttpServletRequest getRequest(Key<?> key) {
    151     return getContext(key).getRequest();
    152   }
    153 
    154   static HttpServletResponse getResponse(Key<?> key) {
    155     return getContext(key).getResponse();
    156   }
    157 
    158   static ServletContext getServletContext() {
    159     return servletContext.get();
    160   }
    161 
    162   private static Context getContext(Key<?> key) {
    163     Context context = localContext.get();
    164     if (context == null) {
    165       throw new OutOfScopeException(
    166           "Cannot access scoped ["
    167               + Errors.convert(key)
    168               + "]. Either we are not currently inside an HTTP Servlet request, or you may"
    169               + " have forgotten to apply "
    170               + GuiceFilter.class.getName()
    171               + " as a servlet filter for this request.");
    172     }
    173     return context;
    174   }
    175 
    176   static class Context implements RequestScoper {
    177     final HttpServletRequest originalRequest;
    178     final HttpServletRequest request;
    179     final HttpServletResponse response;
    180 
    181     // Synchronized to prevent two threads from using the same request
    182     // scope concurrently.
    183     final Lock lock = new ReentrantLock();
    184 
    185     Context(
    186         HttpServletRequest originalRequest,
    187         HttpServletRequest request,
    188         HttpServletResponse response) {
    189       this.originalRequest = originalRequest;
    190       this.request = request;
    191       this.response = response;
    192     }
    193 
    194     HttpServletRequest getOriginalRequest() {
    195       return originalRequest;
    196     }
    197 
    198     HttpServletRequest getRequest() {
    199       return request;
    200     }
    201 
    202     HttpServletResponse getResponse() {
    203       return response;
    204     }
    205 
    206     @Override
    207     public CloseableScope open() {
    208       lock.lock();
    209       final Context previous = localContext.get();
    210       localContext.set(this);
    211       return new CloseableScope() {
    212         @Override
    213         public void close() {
    214           localContext.set(previous);
    215           lock.unlock();
    216         }
    217       };
    218     }
    219   }
    220 
    221   @Override
    222   public void init(FilterConfig filterConfig) throws ServletException {
    223     final ServletContext servletContext = filterConfig.getServletContext();
    224 
    225     // Store servlet context in a weakreference, for injection
    226     GuiceFilter.servletContext = new WeakReference<>(servletContext);
    227 
    228     // In the default pipeline, this is a noop. However, if replaced
    229     // by a managed pipeline, a lazy init will be triggered the first time
    230     // dispatch occurs.
    231     FilterPipeline filterPipeline = getFilterPipeline();
    232     filterPipeline.initPipeline(servletContext);
    233   }
    234 
    235   @Override
    236   public void destroy() {
    237 
    238     try {
    239       // Destroy all registered filters & servlets in that order
    240       FilterPipeline filterPipeline = getFilterPipeline();
    241       filterPipeline.destroyPipeline();
    242 
    243     } finally {
    244       reset();
    245       servletContext.clear();
    246     }
    247   }
    248 
    249   private FilterPipeline getFilterPipeline() {
    250     // Prefer the injected pipeline, but fall back on the static one for web.xml users.
    251     return (null != injectedPipeline) ? injectedPipeline : pipeline;
    252   }
    253 }
    254