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