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 static com.google.inject.servlet.ManagedServletPipeline.REQUEST_DISPATCHER_REQUEST;
     19 
     20 import com.google.common.collect.Iterators;
     21 import com.google.inject.Injector;
     22 import com.google.inject.Key;
     23 import com.google.inject.Scopes;
     24 import com.google.inject.spi.BindingTargetVisitor;
     25 import com.google.inject.spi.ProviderInstanceBinding;
     26 import com.google.inject.spi.ProviderWithExtensionVisitor;
     27 
     28 import java.io.IOException;
     29 import java.net.URI;
     30 import java.net.URISyntaxException;
     31 import java.util.Collections;
     32 import java.util.Enumeration;
     33 import java.util.HashMap;
     34 import java.util.Map;
     35 import java.util.Set;
     36 import java.util.concurrent.atomic.AtomicReference;
     37 
     38 import javax.servlet.ServletConfig;
     39 import javax.servlet.ServletContext;
     40 import javax.servlet.ServletException;
     41 import javax.servlet.ServletRequest;
     42 import javax.servlet.ServletResponse;
     43 import javax.servlet.http.HttpServlet;
     44 import javax.servlet.http.HttpServletRequest;
     45 import javax.servlet.http.HttpServletRequestWrapper;
     46 import javax.servlet.http.HttpServletResponse;
     47 
     48 /**
     49  * An internal representation of a servlet definition mapped to a particular URI pattern. Also
     50  * performs the request dispatch to that servlet. How nice and OO =)
     51  *
     52  * @author dhanji (at) gmail.com (Dhanji R. Prasanna)
     53  */
     54 class ServletDefinition implements ProviderWithExtensionVisitor<ServletDefinition> {
     55   private final String pattern;
     56   private final Key<? extends HttpServlet> servletKey;
     57   private final UriPatternMatcher patternMatcher;
     58   private final Map<String, String> initParams;
     59   // set only if this was bound using a servlet instance.
     60   private final HttpServlet servletInstance;
     61 
     62   //always set in init, our servlet is always presumed to be a singleton
     63   private final AtomicReference<HttpServlet> httpServlet = new AtomicReference<HttpServlet>();
     64 
     65   public ServletDefinition(String pattern, Key<? extends HttpServlet> servletKey,
     66       UriPatternMatcher patternMatcher, Map<String, String> initParams, HttpServlet servletInstance) {
     67     this.pattern = pattern;
     68     this.servletKey = servletKey;
     69     this.patternMatcher = patternMatcher;
     70     this.initParams = Collections.unmodifiableMap(new HashMap<String, String>(initParams));
     71     this.servletInstance = servletInstance;
     72   }
     73 
     74   public ServletDefinition get() {
     75     return this;
     76   }
     77 
     78   public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor,
     79       ProviderInstanceBinding<? extends B> binding) {
     80     if(visitor instanceof ServletModuleTargetVisitor) {
     81       if(servletInstance != null) {
     82         return ((ServletModuleTargetVisitor<B, V>)visitor).visit(
     83             new InstanceServletBindingImpl(initParams,
     84                 pattern,
     85                 servletInstance,
     86                 patternMatcher));
     87       } else {
     88         return ((ServletModuleTargetVisitor<B, V>)visitor).visit(
     89             new LinkedServletBindingImpl(initParams,
     90                 pattern,
     91                 servletKey,
     92                 patternMatcher));
     93       }
     94     } else {
     95       return visitor.visit(binding);
     96     }
     97   }
     98 
     99   boolean shouldServe(String uri) {
    100     return uri != null && patternMatcher.matches(uri);
    101   }
    102 
    103   public void init(final ServletContext servletContext, Injector injector,
    104       Set<HttpServlet> initializedSoFar) throws ServletException {
    105 
    106     // This absolutely must be a singleton, and so is only initialized once.
    107     if (!Scopes.isSingleton(injector.getBinding(servletKey))) {
    108       throw new ServletException("Servlets must be bound as singletons. "
    109         + servletKey + " was not bound in singleton scope.");
    110     }
    111 
    112     HttpServlet httpServlet = injector.getInstance(servletKey);
    113     this.httpServlet.set(httpServlet);
    114 
    115     // Only fire init() if we have not appeared before in the filter chain.
    116     if (initializedSoFar.contains(httpServlet)) {
    117       return;
    118     }
    119 
    120     //initialize our servlet with the configured context params and servlet context
    121     httpServlet.init(new ServletConfig() {
    122       public String getServletName() {
    123         return servletKey.toString();
    124       }
    125 
    126       public ServletContext getServletContext() {
    127         return servletContext;
    128       }
    129 
    130       public String getInitParameter(String s) {
    131         return initParams.get(s);
    132       }
    133 
    134       public Enumeration getInitParameterNames() {
    135         return Iterators.asEnumeration(initParams.keySet().iterator());
    136       }
    137     });
    138 
    139     // Mark as initialized.
    140     initializedSoFar.add(httpServlet);
    141   }
    142 
    143   public void destroy(Set<HttpServlet> destroyedSoFar) {
    144     HttpServlet reference = httpServlet.get();
    145 
    146     // Do nothing if this Servlet was invalid (usually due to not being scoped
    147     // properly). According to Servlet Spec: it is "out of service", and does not
    148     // need to be destroyed.
    149     // Also prevent duplicate destroys to the same singleton that may appear
    150     // more than once on the filter chain.
    151     if (null == reference || destroyedSoFar.contains(reference)) {
    152       return;
    153     }
    154 
    155     try {
    156       reference.destroy();
    157     } finally {
    158       destroyedSoFar.add(reference);
    159     }
    160   }
    161 
    162   /**
    163    * Wrapper around the service chain to ensure a servlet is servicing what it must and provides it
    164    * with a wrapped request.
    165    *
    166    * @return Returns true if this servlet triggered for the given request. Or false if
    167    *          guice-servlet should continue dispatching down the servlet pipeline.
    168    *
    169    * @throws IOException If thrown by underlying servlet
    170    * @throws ServletException If thrown by underlying servlet
    171    */
    172   public boolean service(ServletRequest servletRequest,
    173       ServletResponse servletResponse) throws IOException, ServletException {
    174 
    175     final HttpServletRequest request = (HttpServletRequest) servletRequest;
    176     final String path = ServletUtils.getContextRelativePath(request);
    177 
    178     final boolean serve = shouldServe(path);
    179 
    180     //invocations of the chain end at the first matched servlet
    181     if (serve) {
    182       doService(servletRequest, servletResponse);
    183     }
    184 
    185     //return false if no servlet matched (so we can proceed down to the web.xml servlets)
    186     return serve;
    187   }
    188 
    189   /**
    190    * Utility that delegates to the actual service method of the servlet wrapped with a contextual
    191    * request (i.e. with correctly computed path info).
    192    *
    193    * We need to suppress deprecation coz we use HttpServletRequestWrapper, which implements
    194    * deprecated API for backwards compatibility.
    195    */
    196   void doService(final ServletRequest servletRequest, ServletResponse servletResponse)
    197       throws ServletException, IOException {
    198 
    199     HttpServletRequest request = new HttpServletRequestWrapper(
    200         (HttpServletRequest) servletRequest) {
    201       private boolean pathComputed;
    202       private String path;
    203 
    204       private boolean pathInfoComputed;
    205       private String pathInfo;
    206 
    207       @Override
    208       public String getPathInfo() {
    209         if (!isPathInfoComputed()) {
    210           String servletPath = getServletPath();
    211           int servletPathLength = servletPath.length();
    212           String requestUri = getRequestURI();
    213           pathInfo = requestUri.substring(getContextPath().length()).replaceAll("[/]{2,}", "/");
    214           // See: https://github.com/google/guice/issues/372
    215           if (pathInfo.startsWith(servletPath)) {
    216             pathInfo = pathInfo.substring(servletPathLength);
    217             // Corner case: when servlet path & request path match exactly (without trailing '/'),
    218             // then pathinfo is null.
    219             if (pathInfo.isEmpty() && servletPathLength > 0) {
    220               pathInfo = null;
    221             } else {
    222               try {
    223                 pathInfo = new URI(pathInfo).getPath();
    224               } catch (URISyntaxException e) {
    225                 // ugh, just leave it alone then
    226               }
    227             }
    228           } else {
    229             pathInfo = null; // we know nothing additional about the URI.
    230           }
    231           pathInfoComputed = true;
    232         }
    233 
    234         return pathInfo;
    235       }
    236 
    237       // NOTE(dhanji): These two are a bit of a hack to help ensure that request dispatcher-sent
    238       // requests don't use the same path info that was memoized for the original request.
    239       // NOTE(iqshum): I don't think this is possible, since the dispatcher-sent request would
    240       // perform its own wrapping.
    241       private boolean isPathInfoComputed() {
    242         return pathInfoComputed && servletRequest.getAttribute(REQUEST_DISPATCHER_REQUEST) == null;
    243       }
    244 
    245       private boolean isPathComputed() {
    246         return pathComputed && servletRequest.getAttribute(REQUEST_DISPATCHER_REQUEST) == null;
    247       }
    248 
    249       @Override
    250       public String getServletPath() {
    251         return computePath();
    252       }
    253 
    254       @Override
    255       public String getPathTranslated() {
    256         final String info = getPathInfo();
    257 
    258         return (null == info) ? null : getRealPath(info);
    259       }
    260 
    261       // Memoizer pattern.
    262       private String computePath() {
    263         if (!isPathComputed()) {
    264           String servletPath = super.getServletPath();
    265           path = patternMatcher.extractPath(servletPath);
    266           pathComputed = true;
    267 
    268           if (null == path) {
    269             path = servletPath;
    270           }
    271         }
    272 
    273         return path;
    274       }
    275     };
    276 
    277     doServiceImpl(request, (HttpServletResponse) servletResponse);
    278   }
    279 
    280   private void doServiceImpl(HttpServletRequest request, HttpServletResponse response)
    281       throws ServletException, IOException {
    282     GuiceFilter.Context previous = GuiceFilter.localContext.get();
    283     HttpServletRequest originalRequest
    284         = (previous != null) ? previous.getOriginalRequest() : request;
    285     GuiceFilter.localContext.set(new GuiceFilter.Context(originalRequest, request, response));
    286     try {
    287       httpServlet.get().service(request, response);
    288     } finally {
    289       GuiceFilter.localContext.set(previous);
    290     }
    291   }
    292 
    293   String getKey() {
    294     return servletKey.toString();
    295   }
    296 
    297   String getPattern() {
    298     return pattern;
    299   }
    300 }
    301