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 25 import java.io.IOException; 26 import java.lang.ref.WeakReference; 27 import java.util.concurrent.Callable; 28 import java.util.logging.Logger; 29 30 import javax.servlet.Filter; 31 import javax.servlet.FilterChain; 32 import javax.servlet.FilterConfig; 33 import javax.servlet.ServletContext; 34 import javax.servlet.ServletException; 35 import javax.servlet.ServletRequest; 36 import javax.servlet.ServletResponse; 37 import javax.servlet.http.HttpServletRequest; 38 import javax.servlet.http.HttpServletResponse; 39 40 /** 41 * <p> 42 * Apply this filter in web.xml above all other filters (typically), to all requests where you plan 43 * to use servlet scopes. This is also needed in order to dispatch requests to injectable filters 44 * and servlets: 45 * <pre> 46 * <filter> 47 * <filter-name>guiceFilter</filter-name> 48 * <filter-class><b>com.google.inject.servlet.GuiceFilter</b></filter-class> 49 * </filter> 50 * 51 * <filter-mapping> 52 * <filter-name>guiceFilter</filter-name> 53 * <url-pattern>/*</url-pattern> 54 * </filter-mapping> 55 * </pre> 56 * 57 * This filter must appear before every filter that makes use of Guice injection or servlet 58 * scopes functionality. Typically, you will only register this filter in web.xml and register 59 * any other filters (and servlets) using a {@link ServletModule}. 60 * 61 * @author crazybob@google.com (Bob Lee) 62 * @author dhanji@gmail.com (Dhanji R. Prasanna) 63 */ 64 public class GuiceFilter implements Filter { 65 static final ThreadLocal<Context> localContext = new ThreadLocal<Context>(); 66 static volatile FilterPipeline pipeline = new DefaultFilterPipeline(); 67 68 /** 69 * We allow both the static and dynamic versions of the pipeline to exist. 70 */ 71 private final FilterPipeline injectedPipeline; 72 73 /** Used to inject the servlets configured via {@link ServletModule} */ 74 static volatile WeakReference<ServletContext> servletContext = 75 new WeakReference<ServletContext>(null); 76 77 private static final String MULTIPLE_INJECTORS_WARNING = 78 "Multiple Servlet injectors detected. This is a warning " 79 + "indicating that you have more than one " 80 + GuiceFilter.class.getSimpleName() + " running " 81 + "in your web application. If this is deliberate, you may safely " 82 + "ignore this message. If this is NOT deliberate however, " 83 + "your application may not work as expected."; 84 85 private static final Logger LOGGER = Logger.getLogger(GuiceFilter.class.getName()); 86 GuiceFilter()87 public GuiceFilter() { 88 // Use the static FilterPipeline 89 this(null); 90 } 91 GuiceFilter(FilterPipeline filterPipeline)92 @Inject GuiceFilter(FilterPipeline filterPipeline) { 93 injectedPipeline = filterPipeline; 94 } 95 96 //VisibleForTesting 97 @Inject setPipeline(FilterPipeline pipeline)98 static void setPipeline(FilterPipeline pipeline) { 99 100 // This can happen if you create many injectors and they all have their own 101 // servlet module. This is legal, caveat a small warning. 102 if (GuiceFilter.pipeline instanceof ManagedFilterPipeline) { 103 LOGGER.warning(MULTIPLE_INJECTORS_WARNING); 104 } 105 106 // We overwrite the default pipeline 107 GuiceFilter.pipeline = pipeline; 108 } 109 110 //VisibleForTesting reset()111 static void reset() { 112 pipeline = new DefaultFilterPipeline(); 113 localContext.remove(); 114 } 115 doFilter( final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain)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 new Context(originalRequest, request, response).call(new Callable<Void>() { 131 @Override public Void call() throws Exception { 132 //dispatch across the servlet pipeline, ensuring web.xml's filterchain is honored 133 filterPipeline.dispatch(servletRequest, servletResponse, filterChain); 134 return null; 135 } 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 getOriginalRequest(Key<?> key)146 static HttpServletRequest getOriginalRequest(Key<?> key) { 147 return getContext(key).getOriginalRequest(); 148 } 149 getRequest(Key<?> key)150 static HttpServletRequest getRequest(Key<?> key) { 151 return getContext(key).getRequest(); 152 } 153 getResponse(Key<?> key)154 static HttpServletResponse getResponse(Key<?> key) { 155 return getContext(key).getResponse(); 156 } 157 getServletContext()158 static ServletContext getServletContext() { 159 return servletContext.get(); 160 } 161 getContext(Key<?> key)162 private static Context getContext(Key<?> key) { 163 Context context = localContext.get(); 164 if (context == null) { 165 throw new OutOfScopeException("Cannot access scoped [" + Errors.convert(key) 166 + "]. Either we are not currently inside an HTTP Servlet request, or you may" 167 + " have forgotten to apply " + GuiceFilter.class.getName() 168 + " as a servlet filter for this request."); 169 } 170 return context; 171 } 172 173 static class Context { 174 final HttpServletRequest originalRequest; 175 final HttpServletRequest request; 176 final HttpServletResponse response; 177 Context(HttpServletRequest originalRequest, HttpServletRequest request, HttpServletResponse response)178 Context(HttpServletRequest originalRequest, HttpServletRequest request, 179 HttpServletResponse response) { 180 this.originalRequest = originalRequest; 181 this.request = request; 182 this.response = response; 183 } 184 getOriginalRequest()185 HttpServletRequest getOriginalRequest() { 186 return originalRequest; 187 } 188 getRequest()189 HttpServletRequest getRequest() { 190 return request; 191 } 192 getResponse()193 HttpServletResponse getResponse() { 194 return response; 195 } 196 197 // Synchronized to prevent two threads from using the same request 198 // scope concurrently. call(Callable<T> callable)199 synchronized <T> T call(Callable<T> callable) throws Exception { 200 Context previous = localContext.get(); 201 localContext.set(this); 202 try { 203 return callable.call(); 204 } finally { 205 localContext.set(previous); 206 } 207 } 208 } 209 init(FilterConfig filterConfig)210 public void init(FilterConfig filterConfig) throws ServletException { 211 final ServletContext servletContext = filterConfig.getServletContext(); 212 213 // Store servlet context in a weakreference, for injection 214 GuiceFilter.servletContext = new WeakReference<ServletContext>(servletContext); 215 216 // In the default pipeline, this is a noop. However, if replaced 217 // by a managed pipeline, a lazy init will be triggered the first time 218 // dispatch occurs. 219 FilterPipeline filterPipeline = getFilterPipeline(); 220 filterPipeline.initPipeline(servletContext); 221 } 222 destroy()223 public void destroy() { 224 225 try { 226 // Destroy all registered filters & servlets in that order 227 FilterPipeline filterPipeline = getFilterPipeline(); 228 filterPipeline.destroyPipeline(); 229 230 } finally { 231 reset(); 232 servletContext.clear(); 233 } 234 } 235 getFilterPipeline()236 private FilterPipeline getFilterPipeline() { 237 // Prefer the injected pipeline, but fall back on the static one for web.xml users. 238 return (null != injectedPipeline) ? injectedPipeline : pipeline; 239 } 240 } 241