Home | History | Annotate | Download | only in servlet
      1 /**
      2  * Copyright (C) 2008 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 package com.google.inject.servlet;
     17 
     18 import com.google.common.collect.Lists;
     19 import com.google.common.collect.Sets;
     20 import com.google.inject.Binding;
     21 import com.google.inject.Inject;
     22 import com.google.inject.Injector;
     23 import com.google.inject.Provider;
     24 import com.google.inject.Singleton;
     25 import com.google.inject.TypeLiteral;
     26 
     27 import java.io.IOException;
     28 import java.util.List;
     29 import java.util.Set;
     30 
     31 import javax.servlet.Filter;
     32 import javax.servlet.FilterChain;
     33 import javax.servlet.RequestDispatcher;
     34 import javax.servlet.ServletContext;
     35 import javax.servlet.ServletException;
     36 import javax.servlet.ServletRequest;
     37 import javax.servlet.ServletResponse;
     38 import javax.servlet.http.HttpServletRequest;
     39 import javax.servlet.http.HttpServletRequestWrapper;
     40 
     41 /**
     42  * Central routing/dispatch class handles lifecycle of managed filters, and delegates to the servlet
     43  * pipeline.
     44  *
     45  * @author dhanji (at) gmail.com (Dhanji R. Prasanna)
     46  */
     47 @Singleton
     48 class ManagedFilterPipeline implements FilterPipeline{
     49   private final FilterDefinition[] filterDefinitions;
     50   private final ManagedServletPipeline servletPipeline;
     51   private final Provider<ServletContext> servletContext;
     52 
     53   //Unfortunately, we need the injector itself in order to create filters + servlets
     54   private final Injector injector;
     55 
     56   //Guards a DCL, so needs to be volatile
     57   private volatile boolean initialized = false;
     58   private static final TypeLiteral<FilterDefinition> FILTER_DEFS =
     59       TypeLiteral.get(FilterDefinition.class);
     60 
     61   @Inject
     62   public ManagedFilterPipeline(Injector injector, ManagedServletPipeline servletPipeline,
     63       Provider<ServletContext> servletContext) {
     64     this.injector = injector;
     65     this.servletPipeline = servletPipeline;
     66     this.servletContext = servletContext;
     67 
     68     this.filterDefinitions = collectFilterDefinitions(injector);
     69   }
     70 
     71   /**
     72    * Introspects the injector and collects all instances of bound {@code List<FilterDefinition>}
     73    * into a master list.
     74    *
     75    * We have a guarantee that {@link com.google.inject.Injector#getBindings()} returns a map
     76    * that preserves insertion order in entry-set iterators.
     77    */
     78   private FilterDefinition[] collectFilterDefinitions(Injector injector) {
     79     List<FilterDefinition> filterDefinitions = Lists.newArrayList();
     80     for (Binding<FilterDefinition> entry : injector.findBindingsByType(FILTER_DEFS)) {
     81       filterDefinitions.add(entry.getProvider().get());
     82     }
     83 
     84     // Copy to a fixed-size array for speed of iteration.
     85     return filterDefinitions.toArray(new FilterDefinition[filterDefinitions.size()]);
     86   }
     87 
     88   public synchronized void initPipeline(ServletContext servletContext)
     89       throws ServletException {
     90 
     91     //double-checked lock, prevents duplicate initialization
     92     if (initialized)
     93       return;
     94 
     95     // Used to prevent duplicate initialization.
     96     Set<Filter> initializedSoFar = Sets.newIdentityHashSet();
     97 
     98     for (FilterDefinition filterDefinition : filterDefinitions) {
     99       filterDefinition.init(servletContext, injector, initializedSoFar);
    100     }
    101 
    102     //next, initialize servlets...
    103     servletPipeline.init(servletContext, injector);
    104 
    105     //everything was ok...
    106     initialized = true;
    107   }
    108 
    109   public void dispatch(ServletRequest request, ServletResponse response,
    110       FilterChain proceedingFilterChain) throws IOException, ServletException {
    111 
    112     //lazy init of filter pipeline (OK by the servlet specification). This is needed
    113     //in order for us not to force users to create a GuiceServletContextListener subclass.
    114     if (!initialized) {
    115       initPipeline(servletContext.get());
    116     }
    117 
    118     //obtain the servlet pipeline to dispatch against
    119     new FilterChainInvocation(filterDefinitions, servletPipeline, proceedingFilterChain)
    120         .doFilter(withDispatcher(request, servletPipeline), response);
    121 
    122   }
    123 
    124   /**
    125    * Used to create an proxy that dispatches either to the guice-servlet pipeline or the regular
    126    * pipeline based on uri-path match. This proxy also provides minimal forwarding support.
    127    *
    128    * We cannot forward from a web.xml Servlet/JSP to a guice-servlet (because the filter pipeline
    129    * is not called again). However, we can wrap requests with our own dispatcher to forward the
    130    * *other* way. web.xml Servlets/JSPs can forward to themselves as per normal.
    131    *
    132    * This is not a problem cuz we intend for people to migrate from web.xml to guice-servlet,
    133    * incrementally, but not the other way around (which, we should actively discourage).
    134    */
    135   @SuppressWarnings({ "JavaDoc", "deprecation" })
    136   private ServletRequest withDispatcher(ServletRequest servletRequest,
    137       final ManagedServletPipeline servletPipeline) {
    138 
    139     // don't wrap the request if there are no servlets mapped. This prevents us from inserting our
    140     // wrapper unless it's actually going to be used. This is necessary for compatibility for apps
    141     // that downcast their HttpServletRequests to a concrete implementation.
    142     if (!servletPipeline.hasServletsMapped()) {
    143       return servletRequest;
    144     }
    145 
    146     HttpServletRequest request = (HttpServletRequest) servletRequest;
    147     //noinspection OverlyComplexAnonymousInnerClass
    148     return new HttpServletRequestWrapper(request) {
    149 
    150       @Override
    151       public RequestDispatcher getRequestDispatcher(String path) {
    152         final RequestDispatcher dispatcher = servletPipeline.getRequestDispatcher(path);
    153 
    154         return (null != dispatcher) ? dispatcher : super.getRequestDispatcher(path);
    155       }
    156     };
    157   }
    158 
    159   public void destroyPipeline() {
    160     //destroy servlets first
    161     servletPipeline.destroy();
    162 
    163     //go down chain and destroy all our filters
    164     Set<Filter> destroyedSoFar = Sets.newIdentityHashSet();
    165     for (FilterDefinition filterDefinition : filterDefinitions) {
    166       filterDefinition.destroy(destroyedSoFar);
    167     }
    168   }
    169 }
    170