• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *  &lt;filter&gt;
46  *    &lt;filter-name&gt;guiceFilter&lt;/filter-name&gt;
47  *    &lt;filter-class&gt;<b>com.google.inject.servlet.GuiceFilter</b>&lt;/filter-class&gt;
48  *  &lt;/filter&gt;
49  *
50  *  &lt;filter-mapping&gt;
51  *    &lt;filter-name&gt;guiceFilter&lt;/filter-name&gt;
52  *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
53  *  &lt;/filter-mapping&gt;
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