GuiceFilter.java revision b7a02b02d81c830d148355c90bc309bcd66fb592
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.inject.Inject;
20import com.google.inject.OutOfScopeException;
21
22import java.io.IOException;
23import java.lang.ref.WeakReference;
24import java.util.logging.Logger;
25
26import javax.servlet.Filter;
27import javax.servlet.FilterChain;
28import javax.servlet.FilterConfig;
29import javax.servlet.ServletContext;
30import javax.servlet.ServletException;
31import javax.servlet.ServletRequest;
32import javax.servlet.ServletResponse;
33import javax.servlet.http.HttpServletRequest;
34import javax.servlet.http.HttpServletResponse;
35
36/**
37 * <p>
38 * Apply this filter in web.xml above all other filters (typically), to all requests where you plan
39 *  to use servlet scopes. This is also needed in order to dispatch requests to injectable filters
40 *  and servlets:
41 *  <pre>
42 *  &lt;filter&gt;
43 *    &lt;filter-name&gt;guiceFilter&lt;/filter-name&gt;
44 *    &lt;filter-class&gt;<b>com.google.inject.servlet.GuiceFilter</b>&lt;/filter-class&gt;
45 *  &lt;/filter&gt;
46 *
47 *  &lt;filter-mapping&gt;
48 *    &lt;filter-name&gt;guiceFilter&lt;/filter-name&gt;
49 *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
50 *  &lt;/filter-mapping&gt;
51 *  </pre>
52 *
53 * This filter must appear before every filter that makes use of Guice injection or servlet
54 * scopes functionality. Typically, you will only register this filter in web.xml and register
55 * any other filters (and servlets) using a {@link ServletModule}.
56 *
57 * @author crazybob@google.com (Bob Lee)
58 * @author dhanji@gmail.com (Dhanji R. Prasanna)
59 */
60public class GuiceFilter implements Filter {
61  static final ThreadLocal<Context> localContext = new ThreadLocal<Context>();
62  static volatile FilterPipeline pipeline = new DefaultFilterPipeline();
63
64  /**
65   * We allow both the static and dynamic versions of the pipeline to exist.
66   */
67  @Inject
68  private final FilterPipeline injectedPipeline = null;
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() + " running "
78      + "in your web application. If this is deliberate, you may safely "
79      + "ignore this message. If this is NOT deliberate however, "
80      + "your application may not work as expected.";
81
82  //VisibleForTesting
83  @Inject
84  static void setPipeline(FilterPipeline pipeline) {
85
86    // This can happen if you create many injectors and they all have their own
87    // servlet module. This is legal, caveat a small warning.
88    if (GuiceFilter.pipeline instanceof ManagedFilterPipeline) {
89        Logger.getLogger(GuiceFilter.class.getName()).warning(MULTIPLE_INJECTORS_WARNING);
90    }
91
92    // We overwrite the default pipeline
93    GuiceFilter.pipeline = pipeline;
94  }
95
96  //VisibleForTesting
97  static void reset() {
98    pipeline = new DefaultFilterPipeline();
99  }
100
101  public void doFilter(ServletRequest servletRequest,
102      ServletResponse servletResponse, FilterChain filterChain)
103      throws IOException, ServletException {
104
105    Context previous = localContext.get();
106
107    // Prefer the injected pipeline, but fall back on the static one for web.xml users.
108    FilterPipeline filterPipeline = null != injectedPipeline ? injectedPipeline : pipeline;
109
110    try {
111      localContext.set(new Context((HttpServletRequest) servletRequest,
112          (HttpServletResponse) servletResponse));
113
114      //dispatch across the servlet pipeline, ensuring web.xml's filterchain is honored
115      filterPipeline.dispatch(servletRequest, servletResponse, filterChain);
116
117    } finally {
118      localContext.set(previous);
119    }
120  }
121
122  static HttpServletRequest getRequest() {
123    return getContext().getRequest();
124  }
125
126  static HttpServletResponse getResponse() {
127    return getContext().getResponse();
128  }
129
130  static ServletContext getServletContext() {
131    return servletContext.get();
132  }
133
134  static Context getContext() {
135    Context context = localContext.get();
136    if (context == null) {
137      throw new OutOfScopeException("Cannot access scoped object. Either we"
138          + " are not currently inside an HTTP Servlet request, or you may"
139          + " have forgotten to apply " + GuiceFilter.class.getName()
140          + " as a servlet filter for this request.");
141    }
142    return context;
143  }
144
145  static class Context {
146
147    final HttpServletRequest request;
148    final HttpServletResponse response;
149
150    Context(HttpServletRequest request, HttpServletResponse response) {
151      this.request = request;
152      this.response = response;
153    }
154
155    HttpServletRequest getRequest() {
156      return request;
157    }
158
159    HttpServletResponse getResponse() {
160      return response;
161    }
162  }
163
164  public void init(FilterConfig filterConfig) throws ServletException {
165    final ServletContext servletContext = filterConfig.getServletContext();
166
167    // Store servlet context in a weakreference, for injection
168    GuiceFilter.servletContext = new WeakReference<ServletContext>(servletContext);
169
170    // In the default pipeline, this is a noop. However, if replaced
171    // by a managed pipeline, a lazy init will be triggered the first time
172    // dispatch occurs.
173    FilterPipeline filterPipeline = null != injectedPipeline ? injectedPipeline : pipeline;
174    filterPipeline.initPipeline(servletContext);
175  }
176
177  public void destroy() {
178
179    try {
180      // Destroy all registered filters & servlets in that order
181      FilterPipeline filterPipeline = null != injectedPipeline ? injectedPipeline : pipeline;
182      filterPipeline.destroyPipeline();
183
184    } finally {
185      reset();
186      servletContext.clear();
187    }
188  }
189}
190