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