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