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