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