First cut of Guice Servlet 2.0. Contains regex dispatching, improved binding DSL, and almost the full gamut of features short of per-servlet filtering.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@733 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/servlet/src/com/google/inject/servlet/DefaultFilterPipeline.java b/servlet/src/com/google/inject/servlet/DefaultFilterPipeline.java
new file mode 100755
index 0000000..d1c438b
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/DefaultFilterPipeline.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.Singleton;
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * This default pipeline simply dispatches to web.xml's servlet pipeline.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ * @see com.google.inject.servlet.ManagedFilterPipeline See Also ManagedFilterPipeline.
+ */
+@Singleton
+class DefaultFilterPipeline implements FilterPipeline {
+ public void initPipeline(ServletContext context) {
+ }
+
+ public void destroyPipeline() {
+ }
+
+ public void dispatch(ServletRequest request, ServletResponse response,
+ FilterChain proceedingFilterChain) throws IOException, ServletException {
+
+ proceedingFilterChain.doFilter(request, response);
+ }
+}
diff --git a/servlet/src/com/google/inject/servlet/FilterChainInvocation.java b/servlet/src/com/google/inject/servlet/FilterChainInvocation.java
new file mode 100755
index 0000000..7c47e77
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/FilterChainInvocation.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A Filter chain impl which basically passes itself to the "current" filter and iterates the chain
+ * on {@code doFilter()}. Modeled on something similar in Apache Tomcat.
+ *
+ * Following this, it attempts to dispatch to guice-servlet's registered servlets using the
+ * ManagedServletPipeline.
+ *
+ * And the end, it proceeds to the web.xml (default) servlet filter chain, if needed.
+ *
+ * @author Dhanji R. Prasanna
+ * @since 1.0
+ */
+class FilterChainInvocation implements FilterChain {
+ private final List<FilterDefinition> filterDefinitions;
+ private final FilterChain proceedingChain;
+ private final ManagedServletPipeline servletPipeline;
+
+ //state variable tracks current link in filterchain
+ private int index = -1;
+
+ public FilterChainInvocation(List<FilterDefinition> filterDefinitions,
+ ManagedServletPipeline servletPipeline, FilterChain proceedingChain) {
+
+ this.filterDefinitions = filterDefinitions;
+ this.servletPipeline = servletPipeline;
+ this.proceedingChain = proceedingChain;
+ }
+
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse)
+ throws IOException, ServletException {
+ index++;
+
+ //dispatch down the chain while there are more filters
+ if (index < filterDefinitions.size()) {
+ filterDefinitions.get(index).doFilter(servletRequest, servletResponse, this);
+ }
+ else {
+
+ //we've reached the end of the filterchain, let's try to dispatch to a servlet
+ final boolean serviced = servletPipeline.service(servletRequest, servletResponse);
+
+ //dispatch to the normal filter chain only if one of our servlets did not match
+ if (!serviced) {
+ proceedingChain.doFilter(servletRequest, servletResponse);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/servlet/src/com/google/inject/servlet/FilterDefinition.java b/servlet/src/com/google/inject/servlet/FilterDefinition.java
new file mode 100755
index 0000000..fcb7ebb
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/FilterDefinition.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * An internal representation of a filter definition against a particular URI pattern.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class FilterDefinition {
+ private final String pattern;
+ private final Key<? extends Filter> filterKey;
+ private final UriPatternMatcher patternMatcher;
+ private final Map<String, String> initParams;
+
+ private final AtomicReference<Filter> filter = new AtomicReference<Filter>();
+
+ public FilterDefinition(String pattern, Key<? extends Filter> filterKey,
+ UriPatternMatcher patternMatcher, Map<String, String> initParams) {
+ this.pattern = pattern;
+ this.filterKey = filterKey;
+ this.patternMatcher = patternMatcher;
+ this.initParams = Collections.unmodifiableMap(new HashMap<String, String>(initParams));
+ }
+
+ private boolean shouldFilter(String uri) {
+ return patternMatcher.matches(uri, pattern);
+ }
+
+ public void init(final ServletContext servletContext, Injector injector) throws ServletException {
+ //TODO validate that the filter is bound as a singleton, to match with servlet spec idiom
+ Filter filter = injector.getInstance(filterKey);
+ this.filter.set(filter);
+
+ //initialize our filter with the configured context params and servlet context
+ //noinspection OverlyComplexAnonymousInnerClass,AnonymousInnerClassWithTooManyMethods
+ filter.init(new FilterConfig() {
+ public String getFilterName() {
+ return filterKey.toString();
+ }
+
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ public String getInitParameter(String s) {
+ return initParams.get(s);
+ }
+
+ public Enumeration getInitParameterNames() {
+ //noinspection InnerClassTooDeeplyNested,AnonymousInnerClassWithTooManyMethods
+ return new Enumeration() {
+ private final Iterator<String> paramNames = initParams.keySet().iterator();
+
+ public boolean hasMoreElements() {
+ return paramNames.hasNext();
+ }
+
+ public Object nextElement() {
+ return paramNames.next();
+ }
+ };
+ }
+ });
+ }
+
+ public void destroy() {
+ //filters are always singletons
+ filter.get().destroy();
+ }
+
+ public void doFilter(ServletRequest servletRequest,
+ ServletResponse servletResponse, FilterChainInvocation filterChainInvocation)
+ throws IOException, ServletException {
+
+ final String path = ((HttpServletRequest) servletRequest).getServletPath();
+
+ if (shouldFilter(path)) {
+ filter.get()
+ .doFilter(servletRequest, servletResponse, filterChainInvocation);
+
+ } else {
+ //otherwise proceed down chain anyway
+ filterChainInvocation.doFilter(servletRequest, servletResponse);
+ }
+ }
+}
diff --git a/servlet/src/com/google/inject/servlet/FilterPipeline.java b/servlet/src/com/google/inject/servlet/FilterPipeline.java
new file mode 100755
index 0000000..c424b88
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/FilterPipeline.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.ImplementedBy;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import java.io.IOException;
+
+/**
+ * An internal dispatcher for guice-servlet registered servlets and filters.
+ * By default, we assume a Guice 1.0 style servlet module is in play. In other
+ * words, we dispatch directly to the web.xml pipeline after setting up scopes.
+ *
+ * <p>
+ * If on the other hand, {@link Servlets#configure} is used to register managed
+ * servlets and/or filters, then a different pipeline is bound instead. Which,
+ * after dispatching to Guice-injected filters and servlets continues to the web.xml
+ * pipeline (if necessary).
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ * @see com.google.inject.servlet.ManagedFilterPipeline Guice Servlet 2.0 Pipeline.
+ */
+@ImplementedBy(DefaultFilterPipeline.class)
+interface FilterPipeline {
+ void initPipeline(ServletContext context) throws ServletException;
+ void destroyPipeline();
+
+ void dispatch(ServletRequest request, ServletResponse response,
+ FilterChain proceedingFilterChain) throws IOException, ServletException;
+}
diff --git a/servlet/src/com/google/inject/servlet/FiltersModuleBuilder.java b/servlet/src/com/google/inject/servlet/FiltersModuleBuilder.java
new file mode 100755
index 0000000..e94cd51
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/FiltersModuleBuilder.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.common.collect.Lists;
+import com.google.inject.AbstractModule;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.servlet.Servlets.FilterBindingBuilder;
+import com.google.inject.servlet.Servlets.ServletBindingBuilder;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.Filter;
+
+/**
+ * Builds the guice module that binds configured filters, with their wrapper FilterDefinitions. Is
+ * part of the binding EDSL.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ * @see com.google.inject.servlet.Servlets#configure
+ */
+class FiltersModuleBuilder extends AbstractModule implements FilterBindingBuilder {
+ private List<FilterDefinition> filterDefinitions = Lists.newArrayList();
+
+ //invoked on injector config
+ @Override
+ protected void configure() {
+ //bind these filter definitions to a singleton dispatcher pipeline (overrides the
+ // DefaultFilterPipeline)
+ bind(FilterPipeline.class).toInstance(new ManagedFilterPipeline(filterDefinitions));
+ }
+
+ //the first level of the EDSL--
+ public FilterKeyBindingBuilder filter(String urlPattern) {
+ return new FilterKeyBindingBuilderImpl(urlPattern, UriPatternType.SERVLET);
+ }
+
+ public FilterKeyBindingBuilder filterRegex(String regex) {
+ return new FilterKeyBindingBuilderImpl(regex, UriPatternType.REGEX);
+ }
+
+ public ServletBindingBuilder servlets() {
+ return new ServletsModuleBuilder(this);
+ }
+
+ //shortcut method if there are no servlets to configure
+ public Module buildModule() {
+ return new ServletsModuleBuilder(this);
+ }
+
+ //non-static inner class so it can access state of enclosing module class
+ private class FilterKeyBindingBuilderImpl implements FilterKeyBindingBuilder {
+ private final String uriPattern;
+ private final UriPatternType uriPatternType;
+
+ private FilterKeyBindingBuilderImpl(String uriPattern, UriPatternType uriPatternType) {
+ this.uriPattern = uriPattern;
+ this.uriPatternType = uriPatternType;
+ }
+
+ public FilterBindingBuilder through(Class<? extends Filter> filterKey) {
+ return through(Key.get(filterKey));
+ }
+
+ public FilterBindingBuilder through(Key<? extends Filter> filterKey) {
+ return through(filterKey, new HashMap<String, String>());
+ }
+
+ public FilterBindingBuilder through(Class<? extends Filter> filterKey,
+ Map<String, String> contextParams) {
+ //careful you don't accidentally make this method recursive!! thank you IntelliJ IDEA!
+ return through(Key.get(filterKey), contextParams);
+ }
+
+ public FilterBindingBuilder through(Key<? extends Filter> filterKey,
+ Map<String, String> contextParams) {
+ filterDefinitions.add(
+ new FilterDefinition(uriPattern, filterKey, UriPatternType.get(uriPatternType),
+ contextParams));
+
+ return FiltersModuleBuilder.this;
+ }
+ }
+}
diff --git a/servlet/src/com/google/inject/servlet/GuiceFilter.java b/servlet/src/com/google/inject/servlet/GuiceFilter.java
index 462100a..1c22787 100644
--- a/servlet/src/com/google/inject/servlet/GuiceFilter.java
+++ b/servlet/src/com/google/inject/servlet/GuiceFilter.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2006 Google Inc.
+ * Copyright (C) 2006-2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,15 @@
package com.google.inject.servlet;
+import com.google.inject.Inject;
import com.google.inject.OutOfScopeException;
+
import java.io.IOException;
+import java.lang.ref.WeakReference;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -28,22 +32,88 @@
import javax.servlet.http.HttpServletResponse;
/**
- * Apply this filter to all requests where you plan to use servlet scopes.
+ * <p>
+ * Apply this filter in web.xml above all other filters (typically), to all requests where you plan
+ * to use servlet scopes. This is also needed in order to dispatch requests to injectable filters
+ * and servlets:
+ * <pre>
+ * <filter>
+ * <filter-name>guiceFilter</filter-name>
+ * <filter-class><b>com.google.inject.servlet.GuiceFilter</b></filter-class>
+ * </filter>
+ *
+ * <filter-mapping>
+ * <filter-name>guiceFilter</filter-name>
+ * <url-pattern>/*</url-pattern>
+ * </filter-mapping>
+ * </pre>
+ *
+ * This filter should appear above every filter that makes use of Guice injection or servlet
+ * scopes functionality. Ideally, you want to register ONLY this filter in web.xml and register
+ * any other filters using {@link Servlets#configure()}. But this is not strictly necessary.
+ *
+ * <p>
+ * You will generally want to place sitemesh and similar (purely decorative) filters above
+ * {@code GuiceFilter} in web.xml.
*
* @author crazybob@google.com (Bob Lee)
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
*/
public class GuiceFilter implements Filter {
-
static final ThreadLocal<Context> localContext = new ThreadLocal<Context>();
+ static volatile WeakReference<FilterPipeline> pipeline =
+ new WeakReference<FilterPipeline>(null);
+
+ static volatile WeakReference<ServletContext> servletContext =
+ new WeakReference<ServletContext>(null);
+
+ //VisibleForTesting
+ @Inject
+ static void setPipeline(FilterPipeline pipeline) {
+
+ //multiple injectors with ServletModules!
+ if (null != GuiceFilter.pipeline.get()) {
+ throw new RuntimeException(
+ "Multiple injectors detected. Please install only one"
+ + " ServletModule in your web application. While you may "
+ + "have more than one injector, you should only configure"
+ + " guice-servlet in one of them. (Hint: look for legacy "
+ + "ServetModules or multiple calls to Servlets.configure())."
+
+ );
+ }
+
+ //we obtain the pipeline using a special key, so we can identify if the
+ //servlet module was installed properly.
+ GuiceFilter.pipeline = new WeakReference<FilterPipeline>(pipeline);
+ }
+
+ //VisibleForTesting (only)
+ public static void clearPipeline() {
+ pipeline = new WeakReference<FilterPipeline>(null);
+ }
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
+
Context previous = localContext.get();
+ FilterPipeline filterPipeline = pipeline.get();
+
+ //not even a default pipeline was available--bad!
+ if (null == filterPipeline)
+ throw new ServletException("No Guice Injector was present. You should also "
+ + "setup the servlet module by using Servlets.configure()."
+ + " An injector must be present for GuiceFilter to work"
+ + " and for servlet support in your web application.");
+
try {
localContext.set(new Context((HttpServletRequest) servletRequest,
(HttpServletResponse) servletResponse));
- filterChain.doFilter(servletRequest, servletResponse);
+
+ //dispatch across the servlet pipeline, ensuring web.xml's filterchain is also honored
+ filterPipeline.dispatch(servletRequest, servletResponse, filterChain);
+
} finally {
localContext.set(previous);
}
@@ -57,6 +127,10 @@
return getContext().getResponse();
}
+ public static ServletContext getServletContext() {
+ return servletContext.get();
+ }
+
static Context getContext() {
Context context = localContext.get();
if (context == null) {
@@ -87,7 +161,29 @@
}
}
- public void init(FilterConfig filterConfig) throws ServletException {}
+ public void init(FilterConfig filterConfig) throws ServletException {
+ final ServletContext servletContext = filterConfig.getServletContext();
- public void destroy() {}
+ //store servlet context in a weakreference, for injection
+ GuiceFilter.servletContext = new WeakReference<ServletContext>(servletContext);
+
+ FilterPipeline filterPipeline = GuiceFilter.pipeline.get();
+
+ //we must allow for the possibility that the injector is created *after*
+ // GuiceFilter is initialized, to preserve backwards compatibility with Guice 1.0.
+ if (null != filterPipeline)
+ filterPipeline.initPipeline(servletContext);
+ }
+
+ public void destroy() {
+
+ try {
+ //destroy all registered filters & servlets in that order
+ pipeline.get().destroyPipeline();
+
+ } finally {
+ pipeline.clear();
+ servletContext.clear();
+ }
+ }
}
diff --git a/servlet/src/com/google/inject/servlet/GuiceServletContextListener.java b/servlet/src/com/google/inject/servlet/GuiceServletContextListener.java
index 370b71d..9cad2e3 100644
--- a/servlet/src/com/google/inject/servlet/GuiceServletContextListener.java
+++ b/servlet/src/com/google/inject/servlet/GuiceServletContextListener.java
@@ -24,6 +24,13 @@
/**
* Register your own subclass of this as a servlet context listener if you wish
* to have injectable servlets that extend {@link InjectedHttpServlet}.
+ *
+ * <p>
+ * As of Guice 2.0, {@code InjectedHttpServlet} is deprecated, however you can still
+ * use (your subclasses of) {@code GuiceServletContextListener} class as a logical
+ * place to create and configure your injector. Though it is not required that you
+ * do so, any more. Creating an injector anywhere, with {@link Servlets#configure}
+ * will work.
*
* @author Kevin Bourrillion (kevinb@google.com)
*/
diff --git a/servlet/src/com/google/inject/servlet/InjectedHttpServlet.java b/servlet/src/com/google/inject/servlet/InjectedHttpServlet.java
index b42d673..eb5f3e0 100644
--- a/servlet/src/com/google/inject/servlet/InjectedHttpServlet.java
+++ b/servlet/src/com/google/inject/servlet/InjectedHttpServlet.java
@@ -31,7 +31,10 @@
* servlet context listener.
*
* @author Kevin Bourrillion (kevinb@google.com)
+ * @deprecated Servlets can be injected like any other POJO by using the
+ * {@link Servlets#configure()} system instead.
*/
+@Deprecated
public abstract class InjectedHttpServlet extends HttpServlet {
@Override public void init(ServletConfig config) throws ServletException {
diff --git a/servlet/src/com/google/inject/servlet/ManagedFilterPipeline.java b/servlet/src/com/google/inject/servlet/ManagedFilterPipeline.java
new file mode 100755
index 0000000..cbaf021
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/ManagedFilterPipeline.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+/**
+ * Central routing/dispatch class handles lifecycle of managed filters, and delegates to the servlet
+ * pipeline.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+@Singleton
+class ManagedFilterPipeline implements FilterPipeline{
+ private final List<FilterDefinition> filterDefinitions;
+
+ //TODO make these setter for testing?
+ @Inject
+ private final ManagedServletPipeline servletPipeline = null;
+
+ @Inject
+ private final Provider<ServletContext> servletContext = null;
+
+ //Unfortunately, we need the injector itself in order to create filters + servlets
+ @Inject
+ private final Injector injector = null;
+
+ //Guards a DCL, so needs to be volatile
+ private volatile boolean initialized = false;
+
+ public ManagedFilterPipeline(List<FilterDefinition> filterDefinitions) {
+ this.filterDefinitions = Collections.unmodifiableList(filterDefinitions);
+ }
+
+ public synchronized void initPipeline(ServletContext servletContext)
+ throws ServletException {
+
+ //double-checked lock, prevents duplicate initialization
+ if (initialized)
+ return;
+
+ for (FilterDefinition filterDefinition : filterDefinitions) {
+ filterDefinition.init(servletContext, injector);
+ }
+
+ //next, initialize servlets...
+ servletPipeline.init(servletContext, injector);
+
+ //everything was ok...
+ initialized = true;
+ }
+
+ public void dispatch(ServletRequest request, ServletResponse response,
+ FilterChain proceedingFilterChain) throws IOException, ServletException {
+
+ //lazy init of filter pipeline (OK by the servlet specification). This is needed
+ //in order for us not to force users to create a GuiceServletContextListener subclass.
+ if (!initialized) {
+ initPipeline(servletContext.get());
+ }
+
+ //obtain the servlet pipeline to dispatch against (we use getInstance() to avoid holding refs)
+ new FilterChainInvocation(filterDefinitions, servletPipeline, proceedingFilterChain)
+ .doFilter(withDispatcher(request, servletPipeline), response);
+
+ }
+
+ /**
+ * Used to create an proxy that dispatches either to the guice-servlet pipeline or the regular
+ * pipeline based on uri-path match. This proxy also provides minimal forwarding support.
+ *
+ * We cannot forward from a web.xml Servlet/JSP to a guice-servlet (because the filter pipeline
+ * is not called again). However, we can wrap requests with our own dispatcher to forward the
+ * *other* way. web.xml Servlets/JSPs can forward to themselves as per normal.
+ *
+ * This is not a problem cuz we intend for people to migrate from web.xml to guice-servlet,
+ * incrementally, but not the other way around (which, we should actively discourage).
+ */
+ @SuppressWarnings({ "JavaDoc", "deprecation" })
+ private ServletRequest withDispatcher(ServletRequest servletRequest,
+ final ManagedServletPipeline servletPipeline) {
+
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+
+ //noinspection OverlyComplexAnonymousInnerClass
+ return new HttpServletRequestWrapper(request) {
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ final RequestDispatcher dispatcher = servletPipeline.getRequestDispatcher(path);
+
+ return (null != dispatcher) ? dispatcher : super.getRequestDispatcher(path);
+ }
+ };
+ }
+
+ public void destroyPipeline() {
+ //destroy servlets first
+ servletPipeline.destroy();
+
+ //go down chain and destroy all our filters
+ //TODO check servlet spec if we should continue destroying even if exceptions are thrown
+ for (FilterDefinition filterDefinition : filterDefinitions) {
+ filterDefinition.destroy();
+ }
+ }
+}
diff --git a/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
new file mode 100755
index 0000000..b03a390
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+
+import javax.servlet.*;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A wrapping dispatcher for servlets, in much the same way as {@link ManagedFilterPipeline} is for
+ * filters.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ * @see ManagedFilterPipeline
+ */
+@Singleton class ManagedServletPipeline {
+ private final List<ServletDefinition> servletDefinitions;
+
+ public ManagedServletPipeline(List<ServletDefinition> servletDefinitions) {
+ this.servletDefinitions = Collections.unmodifiableList(servletDefinitions);
+ }
+
+ public void init(ServletContext servletContext, Injector injector) throws ServletException {
+ for (ServletDefinition servletDefinition : servletDefinitions) {
+ servletDefinition.init(servletContext, injector);
+ }
+ }
+
+ public boolean service(ServletRequest request, ServletResponse response)
+ throws IOException, ServletException {
+
+ //stop at the first matching servlet and service
+ for (ServletDefinition servletDefinition : servletDefinitions) {
+ if (servletDefinition.service(request, response)) {
+ return true;
+ }
+ }
+
+ //there was no match...
+ return false;
+ }
+
+ public void destroy() {
+ for (ServletDefinition servletDefinition : servletDefinitions) {
+ servletDefinition.destroy();
+ }
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path) {
+ for (final ServletDefinition servletDefinition : servletDefinitions) {
+ if (servletDefinition.shouldServe(path)) {
+ return new RequestDispatcher() {
+
+ public void forward(ServletRequest servletRequest, ServletResponse servletResponse)
+ throws ServletException, IOException {
+
+ if (servletResponse.isCommitted()) {
+ throw new IllegalStateException("Response has been committed--you can "
+ + "only call forward before committing the response (hint: don't "
+ + "flush buffers)");
+ }
+
+ //clear buffer before forwarding
+ servletResponse.resetBuffer();
+
+ //now dispatch to the servlet
+ servletDefinition.doService(servletRequest, servletResponse);
+ }
+
+ public void include(ServletRequest servletRequest, ServletResponse servletResponse)
+ throws ServletException, IOException {
+
+ //route to the target servlet
+ servletDefinition.doService(servletRequest, servletResponse);
+ }
+ };
+ }
+ }
+
+ //otherwise, can't process
+ return null;
+ }
+}
diff --git a/servlet/src/com/google/inject/servlet/ServletDefinition.java b/servlet/src/com/google/inject/servlet/ServletDefinition.java
new file mode 100755
index 0000000..008d8aa
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/ServletDefinition.java
@@ -0,0 +1,198 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+/**
+ * An internal representation of a servlet definition mapped to a particular URI pattern. Also
+ * performs the request dispatch to that servlet. How nice and OO =)
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+class ServletDefinition {
+ private final String pattern;
+ private final Key<? extends HttpServlet> servletKey;
+ private final UriPatternMatcher patternMatcher;
+ private final Map<String, String> initParams;
+
+ //our servlet is always presumed to be a singleton
+ private final AtomicReference<HttpServlet> httpServlet = new AtomicReference<HttpServlet>();
+
+ public ServletDefinition(String pattern, Key<? extends HttpServlet> servletKey,
+ UriPatternMatcher patternMatcher, Map<String, String> initParams) {
+ this.pattern = pattern;
+ this.servletKey = servletKey;
+ this.patternMatcher = patternMatcher;
+ this.initParams = Collections.unmodifiableMap(new HashMap<String, String>(initParams));
+ }
+
+ boolean shouldServe(String uri) {
+ return patternMatcher.matches(uri, pattern);
+ }
+
+ public void init(final ServletContext servletContext, Injector injector) throws ServletException {
+ //TODO this absolutely must be a singleton, and so is only initialized once
+ HttpServlet httpServlet = injector.getInstance(servletKey);
+ this.httpServlet.set(httpServlet);
+
+ //initialize our servlet with the configured context params and servlet context
+ //noinspection OverlyComplexAnonymousInnerClass,AnonymousInnerClassWithTooManyMethods
+ httpServlet.init(new ServletConfig() {
+ public String getServletName() {
+ return servletKey.toString();
+ }
+
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ public String getInitParameter(String s) {
+ return initParams.get(s);
+ }
+
+ public Enumeration getInitParameterNames() {
+ //noinspection InnerClassTooDeeplyNested,AnonymousInnerClassWithTooManyMethods
+ return new Enumeration() {
+ private final Iterator<String> paramNames = initParams.keySet().iterator();
+
+ public boolean hasMoreElements() {
+ return paramNames.hasNext();
+ }
+
+ public Object nextElement() {
+ return paramNames.next();
+ }
+ };
+ }
+ });
+ }
+
+ public void destroy() {
+ httpServlet.get().destroy();
+ }
+
+ /**
+ * Wrapper around the service chain to ensure a servlet is servicing what it must and provides it
+ * with a wrapped request.
+ *
+ * @return Returns true if this servlet triggered for the given request. Or false if
+ * guice-servlet should continue dispatching down the servlet pipeline.
+ *
+ * @throws IOException If thrown by underlying servlet
+ * @throws ServletException If thrown by underlying servlet
+ */
+ public boolean service(ServletRequest servletRequest,
+ ServletResponse servletResponse) throws IOException, ServletException {
+
+ final boolean serve = shouldServe(((HttpServletRequest) servletRequest).getServletPath());
+
+ //invocations of the chain end at the first matched servlet
+ if (serve) {
+ doService(servletRequest, servletResponse);
+ }
+
+ //return false if no servlet matched (so we can proceed down to the web.xml servlets)
+ return serve;
+ }
+
+ /**
+ * Utility that delegates to the actual service method of the servlet wrapped with a contextual
+ * request (i.e. with correctly computed path info).
+ *
+ * We need to suppress deprecation coz we use HttpServletRequestWrapper, which implements
+ * deprecated API for backwards compatibility.
+ */
+ @SuppressWarnings({ "JavaDoc", "deprecation" }) void doService(
+ final ServletRequest servletRequest, ServletResponse servletResponse)
+ throws ServletException, IOException {
+
+ //noinspection OverlyComplexAnonymousInnerClass
+ HttpServletRequest request = new HttpServletRequestWrapper(
+ (HttpServletRequest) servletRequest) {
+ private String path;
+ private boolean pathComputed = false;
+ //must use a boolean on the memo field, because null is a legal value (TODO no, it's not)
+
+ private boolean pathInfoComputed = false;
+ private String pathInfo;
+
+ @Override
+ public String getPathInfo() {
+ if (!pathInfoComputed) {
+ final int servletPathLength = getServletPath().length();
+ pathInfo = getRequestURI().substring(getContextPath().length()).replaceAll("[/]{2,}", "/")
+ .substring(servletPathLength);
+
+ //corner case: when servlet path and request path match exactly (without trailing '/'),
+ //pathinfo is null
+ if ("".equals(pathInfo) && servletPathLength != 0) {
+ pathInfo = null;
+ }
+
+ pathInfoComputed = true;
+ }
+
+ return pathInfo;
+ }
+
+ @Override
+ public String getServletPath() {
+ return computePath();
+ }
+
+ @Override
+ public String getPathTranslated() {
+ final String info = getPathInfo();
+
+ return (null == info) ? null : getRealPath(info);
+ }
+
+ //memoizer pattern
+ private String computePath() {
+ if (!pathComputed) {
+ path = patternMatcher.extractPath(pattern);
+ pathComputed = true;
+
+ if (null == path) {
+ path = super.getServletPath();
+ }
+ }
+
+ return path;
+ }
+ };
+
+ httpServlet.get().service(request, servletResponse);
+ }
+
+}
\ No newline at end of file
diff --git a/servlet/src/com/google/inject/servlet/ServletModule.java b/servlet/src/com/google/inject/servlet/ServletModule.java
index 12aa025..304050f 100644
--- a/servlet/src/com/google/inject/servlet/ServletModule.java
+++ b/servlet/src/com/google/inject/servlet/ServletModule.java
@@ -22,6 +22,8 @@
import static com.google.inject.servlet.ServletScopes.REQUEST;
import static com.google.inject.servlet.ServletScopes.SESSION;
import java.util.Map;
+import java.util.logging.Logger;
+import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@@ -32,10 +34,37 @@
* Configures the servlet scopes and creates bindings for the servlet API
* objects so you can inject the request, response, session, etc.
*
+ * <p>
+ * <strong>
+ * As of Guice 2.0, this module is no longer used directly. Instead you should
+ * use the module obtained from the {@link Servlets#configure()} servlet binding
+ * API.</strong>
+ *
* @author crazybob@google.com (Bob Lee)
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ * @see Servlets#configure() Configuring Guice Servlet.
*/
public class ServletModule extends AbstractModule {
+ /**
+ * @deprecated Don't construct this module directly,
+ * use {@link Servlets#configure} instead.
+ */
+ @Deprecated
+ public ServletModule() {
+ //encourage people to switch to the new system
+ Logger.getLogger(ServletModule.class.getName())
+ .warning("Directly using ServletModule (in the Guice 1.0 style) is "
+ + "now deprecated. Prefer the use of Servlets.configure() "
+ + "instead. Your application will still work as normal, "
+ + "but you are encouraged to make the switch for additional"
+ + " functionality and flexibility.");
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ ServletModule(boolean locallyCreated) {}
+
+ @Override
protected void configure() {
// Scopes.
bindScope(RequestScoped.class, REQUEST);
@@ -80,6 +109,17 @@
}
});
+ // Bind servlet context.
+ bind(ServletContext.class).toProvider(new Provider<ServletContext>() {
+ public ServletContext get() {
+ return GuiceFilter.getServletContext();
+ }
+
+ public String toString() {
+ return "ServletContextProvider";
+ }
+ });
+
// Bind request parameters.
bind(new TypeLiteral<Map<String, String[]>>() {})
.annotatedWith(RequestParameters.class)
@@ -93,5 +133,9 @@
return "RequestParametersProvider";
}
});
+
+ //inject the pipeline into GuiceFilter so it can route requests correctly
+ //Unfortunate staticness... =(
+ requestStaticInjection(GuiceFilter.class);
}
}
diff --git a/servlet/src/com/google/inject/servlet/Servlets.java b/servlet/src/com/google/inject/servlet/Servlets.java
new file mode 100755
index 0000000..a307f7f
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/Servlets.java
@@ -0,0 +1,215 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.Key;
+import com.google.inject.Module;
+import java.util.Map;
+import javax.servlet.Filter;
+import javax.servlet.http.HttpServlet;
+
+/**
+ * <p> Use this utility class to configure your guice-servlet module. This is equivalent to
+ * installing the {@link ServletModule}. Do *not* install the servlet module AND this system.
+ * Always prefer this mechanism unless you are working with a legacy Guice 1.0 configuration.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ * @see com.google.inject.servlet.GuiceFilter Configuring the WebFilter
+ */
+public final class Servlets {
+ private Servlets() {
+ }
+
+ /**
+ * <h3>Servlet Mapping EDSL</h3>
+ *
+ * <p> Part of the EDSL builder language for configuring servlets
+ * and filters with guice-servlet. Think of this as an in-code replacement for web.xml.
+ * Filters and servlets are configured here using simple java method calls. Here is a typical
+ * example of registering a filter when creating your Guice injector:
+ *
+ * <pre>
+ * Guice.createInjector(..., Servlets.configure()
+ * .filters()
+ * .servlets()
+ * <b>.serve("*.html").with(MyServlet.class)</b>
+ *
+ * .buildModule();
+ * </pre>
+ *
+ * This registers a servlet (subclass of {@code HttpServlet}) called {@code MyServlet} to service
+ * any web pages ending in {@code .html}. You can also use a path-style syntax to register
+ * servlets:
+ *
+ * <pre>
+ * <b>.serve("/my/*").with(MyServlet.class)</b>
+ * </pre>
+ *
+ * You are free to register as many servlets and filters as you like this way:
+ *
+ * <pre>
+ * Guice.createInjector(..., Servlets.configure()
+ * .filters()
+ * .filter("/*").through(MyFilter.class)
+ * .filter("*.css").through(MyCssFilter.class)
+ * //etc..
+ *
+ * .servlets()
+ * .serve("*.html").with(MyServlet.class)
+ * .serve("/my/*").with(MyServlet.class)
+ * //etc..
+ *
+ * .buildModule();
+ * </pre>
+ *
+ * You can also map servlets (or filters) to URIs using regular expressions:
+ * <pre>
+ * .servlets()
+ * <b>.serveRegex("(.)*ajax(.)*").with(MyAjaxServlet.class)</b>
+ * </pre>
+ *
+ * This will map any URI containing the text "ajax" in it to {@code MyAjaxServlet}. Such as:
+ * <ul>
+ * <li>http://www.google.com/ajax.html</li>
+ * <li>http://www.google.com/content/ajax/index</li>
+ * <li>http://www.google.com/it/is_totally_ajaxian</li>
+ * </ul>
+ *
+ * </p>
+ *
+ * <h3>Initialization Parameters</h3>
+ * Servlets (and filters) allow you to pass in init params
+ * using the {@code <init-param>} tag in web.xml. You can similarly pass in parameters to
+ * Servlets and filters registered in Guice-servlet using a {@link java.util.Map} of parameter
+ * name/value pairs. For example, to initialize {@code MyServlet} with two parameters
+ * (name="Dhanji", site="google.com") you could write:
+ *
+ * <pre>
+ * Map<String, String> params = new HashMap<String, String>();
+ * params.put("name", "Dhanji");
+ * params.put("site", "google.com");
+ *
+ * ...
+ * .servlets()
+ * .serve("/*").with(MyServlet.class, <b>params</b>)
+ * </pre>
+ *
+ *
+ * <h3>Binding Keys</h3>
+ *
+ * <p> You can also bind keys rather than classes. This lets you hide
+ * implementations with package-local visbility and expose them using
+ * only a Guice module and an annotation:
+ *
+ * <pre>
+ *
+ * ...
+ * .filters()
+ * .filter("/*").through(<b>Key.get(Filter.class, Fave.class)</b>);
+ * </pre>
+ *
+ * Where {@code Filter.class} refers to the Servlet API interface and {@code Fave.class} is a
+ * custom binding annotation. Elsewhere (in one of your own modules) you can bind this
+ * filter's implementation:
+ *
+ * <pre>
+ * bind(Filter.class)<b>.annotatedWith(Fave.class)</b>.to(MyFilterImpl.class);
+ * </pre>
+ *
+ * See Guice documentation for more information on binding annotations.
+ *
+ * @return Returns the next step in the EDSL chain.
+ * @see com.google.inject.servlet.GuiceFilter Configuring the WebFilter
+ */
+ public static WebComponentBindingBuilder configure() {
+ return new WebComponentBindingBuilder() {
+ public FilterBindingBuilder filters() {
+ return new FiltersModuleBuilder();
+ }
+ };
+ }
+
+ public static interface WebComponentBindingBuilder {
+ FilterBindingBuilder filters();
+ }
+
+ public static interface FilterBindingBuilder {
+
+ /**
+ * @param urlPattern Any Servlet-style pattern. examples: /*, /html/*, *.html, etc.
+ * @see Servlets#configure() Configuring Guice Servlet.
+ */
+ FilterKeyBindingBuilder filter(String urlPattern);
+
+ /**
+ * @param regex Any Java-style regular expression.
+ * @see Servlets#configure() Configuring Guice Servlet.
+ */
+ FilterKeyBindingBuilder filterRegex(String regex);
+
+ /**
+ * @see Servlets#configure() Configuring Guice Servlet.
+ */
+ ServletBindingBuilder servlets();
+
+ /**
+ * @return Returns a fully prepared Guice module to be used in
+ * {@link com.google.inject.Guice#createInjector(com.google.inject.Module[])}.
+ * @see Servlets#configure() Configuring Guice Servlet.
+ */
+ Module buildModule();
+
+ public static interface FilterKeyBindingBuilder {
+ FilterBindingBuilder through(Class<? extends Filter> filterKey);
+ FilterBindingBuilder through(Key<? extends Filter> filterKey);
+ FilterBindingBuilder through(Class<? extends Filter> dummyFilterClass,
+ Map<String, String> contextParams);
+ FilterBindingBuilder through(Key<? extends Filter> dummyFilterClass,
+ Map<String, String> contextParams);
+ }
+ }
+
+ public static interface ServletBindingBuilder {
+ /**
+ * @param urlPattern A servlet-style URL pattern ({@code /*, *.html}, etc.)
+ * @see Servlets#configure() Configuring Guice Servlet.
+ */
+ ServletKeyBindingBuilder serve(String urlPattern);
+
+ /**
+ * @param regex A Java-style regular expression.
+ * @see Servlets#configure() Configuring Guice Servlet.
+ */
+ ServletKeyBindingBuilder serveRegex(String regex);
+
+
+ /**
+ * @return Returns configured Guice Module. Use install() or pass into createInjector().
+ * @see Servlets#configure() Configuring Guice Servlet.
+ */
+ Module buildModule();
+
+ public static interface ServletKeyBindingBuilder {
+ ServletBindingBuilder with(Class<? extends HttpServlet> servletKey);
+ ServletBindingBuilder with(Key<? extends HttpServlet> servletKey);
+ ServletBindingBuilder with(Class<? extends HttpServlet> servletKey,
+ Map<String, String> contextParams);
+ ServletBindingBuilder with(Key<? extends HttpServlet> servletKey,
+ Map<String, String> contextParams);
+ }
+ }
+}
diff --git a/servlet/src/com/google/inject/servlet/ServletsModuleBuilder.java b/servlet/src/com/google/inject/servlet/ServletsModuleBuilder.java
new file mode 100755
index 0000000..d60bb1c
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/ServletsModuleBuilder.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.common.collect.Lists;
+import com.google.inject.AbstractModule;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.servlet.Servlets.ServletBindingBuilder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServlet;
+
+/**
+ * Builds the guice module that binds configured servlets, with their wrapper ServletDefinitions. Is
+ * part of the binding EDSL. Very similar to {@link com.google.inject.servlet.FiltersModuleBuilder}.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail.com)
+ * @see com.google.inject.servlet.Servlets
+ */
+class ServletsModuleBuilder extends AbstractModule implements ServletBindingBuilder {
+ private List<ServletDefinition> servletDefinitions = Lists.newArrayList();
+ private final Module filtersModule;
+
+ public ServletsModuleBuilder(Module filtersModule) {
+ this.filtersModule = filtersModule;
+ }
+
+ //invoked on injector config
+ protected void configure() {
+ //install preceeding module(s)
+ install(new ServletModule(true)); //using the hidden local constructor
+ install(filtersModule);
+
+ //bind these servlet definitions to a singleton pipeline
+ bind(ManagedServletPipeline.class).toInstance(new ManagedServletPipeline(servletDefinitions));
+ }
+
+ //the first level of the EDSL--
+
+ public ServletKeyBindingBuilder serve(String urlPattern) {
+ return new ServletKeyBindingBuilderImpl(urlPattern, UriPatternType.SERVLET);
+ }
+
+ public ServletKeyBindingBuilder serveRegex(String regex) {
+ return new ServletKeyBindingBuilderImpl(regex, UriPatternType.REGEX);
+ }
+
+ public Module buildModule() {
+ return this;
+ }
+
+ //non-static inner class so it can access state of enclosing module class
+ private class ServletKeyBindingBuilderImpl implements ServletKeyBindingBuilder {
+ private final String uriPattern;
+ private final UriPatternType uriPatternType;
+
+ private ServletKeyBindingBuilderImpl(String uriPattern, UriPatternType uriPatternType) {
+ this.uriPattern = uriPattern;
+ this.uriPatternType = uriPatternType;
+ }
+
+ public ServletBindingBuilder with(Class<? extends HttpServlet> servletKey) {
+ return with(Key.get(servletKey));
+ }
+
+ public ServletBindingBuilder with(Key<? extends HttpServlet> servletKey) {
+ return with(servletKey, new HashMap<String, String>());
+ }
+
+ public ServletBindingBuilder with(Class<? extends HttpServlet> servletKey,
+ Map<String, String> contextParams) {
+ return with(Key.get(servletKey), contextParams);
+ }
+
+ public ServletBindingBuilder with(Key<? extends HttpServlet> servletKey,
+ Map<String, String> contextParams) {
+ servletDefinitions.add(
+ new ServletDefinition(uriPattern, servletKey, UriPatternType.get(uriPatternType),
+ contextParams));
+
+ return ServletsModuleBuilder.this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/servlet/src/com/google/inject/servlet/UriPatternMatcher.java b/servlet/src/com/google/inject/servlet/UriPatternMatcher.java
new file mode 100755
index 0000000..a86cafa
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/UriPatternMatcher.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.servlet;
+
+/**
+ * <p>
+ * A general interface for matching a URI against a URI pattern. Guice-servlet provides
+ * regex and servlet-style pattern matching out of the box.
+ * </p>
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ * @see com.google.inject.servlet.Servlets#configure() Binding Servlets.
+ */
+interface UriPatternMatcher {
+ /**
+ * @param uri A "contextual" (i.e. relative) Request URI, *not* a complete one.
+ * @param pattern A String containing some pattern that this service can match against.
+ * @return Returns true if the uri matches the pattern.
+ */
+ boolean matches(String uri, String pattern);
+
+ /**
+ * @param pattern A String containing some pattern that this service can match against.
+ * @return Returns a canonical servlet path from this pattern. For instance, if the
+ * pattern is {@code /home/*} then the path extracted will be {@code /home}. Each pattern
+ * matcher implementation must decide and publish what a canonical path represents.
+ *
+ * NOTE(dhanji) This method returns null for the regex pattern matcher.
+ *
+ */
+ String extractPath(String pattern);
+}
diff --git a/servlet/src/com/google/inject/servlet/UriPatternType.java b/servlet/src/com/google/inject/servlet/UriPatternType.java
new file mode 100755
index 0000000..5aa4b4d
--- /dev/null
+++ b/servlet/src/com/google/inject/servlet/UriPatternType.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+/**
+ * An enumeration of the available URI-pattern matching styles
+ */
+enum UriPatternType {
+ SERVLET, REGEX;
+
+ public static UriPatternMatcher get(UriPatternType type) {
+ switch (type) {
+ case SERVLET:
+ return new ServletStyleUriPatternMatcher();
+ case REGEX:
+ return new RegexUriPatternMatcher();
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Matchers URIs using the pattern grammar of the Servlet API and web.xml.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+ private static class ServletStyleUriPatternMatcher implements UriPatternMatcher {
+ public boolean matches(String uri, String pattern) {
+ if (null == uri) {
+ return false;
+ }
+
+ if (pattern.startsWith("*")) {
+ return uri.endsWith(pattern.substring(1));
+ }
+ else if (pattern.endsWith("*")) {
+ return uri.startsWith(pattern.substring(0, pattern.length() - 1));
+ }
+
+ //else treat as a literal
+ return pattern.equals(uri);
+ }
+
+ public String extractPath(String pattern) {
+ if (pattern.startsWith("*")) {
+ return null;
+ } else if (pattern.endsWith("*")) {
+ String extract = pattern.substring(0, pattern.length() - 1);
+
+ //trim the trailing '/'
+ if (extract.endsWith("/")) {
+ extract = extract.substring(0, extract.length() - 1);
+ }
+
+ return extract;
+ }
+
+ //else treat as literal
+ return pattern;
+ }
+ }
+
+ /**
+ * Matchers URIs using a regular expression.
+ * NOTE(dhanji) No path info is available when using regex mapping.
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+ private static class RegexUriPatternMatcher implements UriPatternMatcher {
+ public boolean matches(String uri, String pattern) {
+ return null != uri && uri.matches(pattern);
+ }
+
+ public String extractPath(String pattern) {
+ return null;
+ }
+ }
+}
diff --git a/servlet/src/com/google/inject/servlet/package-info.java b/servlet/src/com/google/inject/servlet/package-info.java
index f48ca9e..8f30760 100644
--- a/servlet/src/com/google/inject/servlet/package-info.java
+++ b/servlet/src/com/google/inject/servlet/package-info.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2006 Google Inc.
+ * Copyright (C) 2006-2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,7 @@
/**
* Servlet API scopes and bindings. Apply {@link
* com.google.inject.servlet.GuiceFilter} to any servlets which will use the
- * servlet scopes. Install {@link com.google.inject.servlet.ServletModule}
- * into your {@link com.google.inject.BinderImpl} to install everything
- * at once.
+ * servlet scopes. Install {@link com.google.inject.servlet.Servlets#configure()}
+ * into your {@link com.google.inject.Injector} to install everything at once.
*/
package com.google.inject.servlet;
\ No newline at end of file
diff --git a/servlet/test/com/google/inject/servlet/DummyFilterImpl.java b/servlet/test/com/google/inject/servlet/DummyFilterImpl.java
new file mode 100644
index 0000000..5977414
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/DummyFilterImpl.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.servlet;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Used in unit tests to verify the EDSL.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+public class DummyFilterImpl implements Filter {
+ int num;
+
+ public DummyFilterImpl() {
+ }
+
+ public DummyFilterImpl(int num) {
+ this.num = num;
+ }
+
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+ FilterChain filterChain) throws IOException, ServletException {
+ }
+
+ public void destroy() {
+ }
+}
diff --git a/servlet/test/com/google/inject/servlet/DummyServlet.java b/servlet/test/com/google/inject/servlet/DummyServlet.java
new file mode 100644
index 0000000..bfcfb94
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/DummyServlet.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import javax.servlet.http.HttpServlet;
+
+/**
+ * Used in unit tests to verify the EDSL.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+public class DummyServlet extends HttpServlet {
+
+}
diff --git a/servlet/test/com/google/inject/servlet/EdslTest.java b/servlet/test/com/google/inject/servlet/EdslTest.java
new file mode 100644
index 0000000..facc18b
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/EdslTest.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.inject.servlet;
+
+import com.google.inject.Guice;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Stage;
+import java.util.HashMap;
+import junit.framework.TestCase;
+
+/**
+ * Sanity checks the EDSL and resultant bound module(s).
+ *
+ * @author Dhanji R. Prasanna (dhanji gmail com)
+ */
+public class EdslTest extends TestCase {
+
+ public final void testConfigureServlets() {
+
+ //the various possible config calls--
+ Module webModule = Servlets.configure()
+ .filters()
+ .filter("/*").through(DummyFilterImpl.class)
+
+ .filter("*.html").through(DummyFilterImpl.class)
+
+ .filter("*.html").through(DummyFilterImpl.class, new HashMap<String, String>())
+
+ .filterRegex("/person/[0-9]*").through(DummyFilterImpl.class)
+
+ .filterRegex("/person/[0-9]*").through(Key.get(DummyFilterImpl.class))
+
+ .filterRegex("/person/[0-9]*").through(Key.get(DummyFilterImpl.class), new HashMap<String, String>())
+
+ .filter("/*").through(Key.get(DummyFilterImpl.class))
+
+ .servlets()
+
+ .serve("/*").with(DummyServlet.class)
+ .serve("/*").with(DummyServlet.class, new HashMap<String, String>())
+
+ .serve("*.html").with(Key.get(DummyServlet.class))
+ .serve("*.html").with(Key.get(DummyServlet.class), new HashMap<String, String>())
+
+ .serveRegex("/person/[0-9]*").with(DummyServlet.class)
+ .serveRegex("/person/[0-9]*").with(DummyServlet.class, new HashMap<String, String>())
+
+ .serveRegex("/person/[0-9]*").with(Key.get(DummyServlet.class))
+ .serveRegex("/person/[0-9]*").with(Key.get(DummyServlet.class), new HashMap<String, String>())
+
+ .buildModule();
+
+ //verify that it doesn't blow up!
+ Guice.createInjector(Stage.TOOL, webModule);
+ }
+}
diff --git a/servlet/test/com/google/inject/servlet/InjectedHttpServletTest.java b/servlet/test/com/google/inject/servlet/InjectedHttpServletTest.java
index 69152d6..3441356 100644
--- a/servlet/test/com/google/inject/servlet/InjectedHttpServletTest.java
+++ b/servlet/test/com/google/inject/servlet/InjectedHttpServletTest.java
@@ -18,6 +18,7 @@
import com.google.inject.Guice;
import com.google.inject.Inject;
+
import com.google.inject.Injector;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@@ -29,11 +30,15 @@
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
import junit.framework.TestCase;
+
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
+/**
+ * NOTE(dhanji): InjectedHttpServlet is deprecated
+ */
public class InjectedHttpServletTest extends TestCase {
private static class MyDependency {}
diff --git a/servlet/test/com/google/inject/servlet/ServletTest.java b/servlet/test/com/google/inject/servlet/ServletTest.java
index 5c5aaf0..984a1cf 100644
--- a/servlet/test/com/google/inject/servlet/ServletTest.java
+++ b/servlet/test/com/google/inject/servlet/ServletTest.java
@@ -16,28 +16,27 @@
package com.google.inject.servlet;
+
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
-import com.google.inject.Provider;
-import com.google.inject.name.Names;
+
+import junit.framework.TestCase;
+
import java.io.IOException;
-import java.util.Arrays;
-import java.util.Iterator;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
-import junit.framework.TestCase;
+
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
-import static org.easymock.EasyMock.isNull;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
@@ -46,125 +45,152 @@
*/
public class ServletTest extends TestCase {
- final Key<InSession> sessionKey = Key.get(InSession.class);
- final Key<InRequest> requestKey = Key.get(InRequest.class);
- final Key<String> nullSessionKey = Key.get(String.class, Names.named("null session"));
- final Key<String> nullRequestKey = Key.get(String.class, Names.named("null request"));
- final Injector injector = createInjector();
- final GuiceFilter filter = new GuiceFilter();
- final HttpServletRequest request = createMock(HttpServletRequest.class);
- final HttpSession session = createMock(HttpSession.class);
+ @Override
+ public void setUp() {
+ //we need to clear the reference to the pipeline every test =(
+ GuiceFilter.clearPipeline();
+ }
- FilterChain filterChain;
- boolean invoked;
+ public void testNewRequestObject()
+ throws CreationException, IOException, ServletException {
+ final Injector injector = createInjector();
- public void testNewRequestObject() throws Exception {
- expect(request.getAttribute(requestKey.toString())).andReturn(null);
- request.setAttribute(eq(requestKey.toString()), isA(InRequest.class));
+ GuiceFilter filter = new GuiceFilter();
- filterChain = new FilterChain() {
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) {
- invoked = true;
- assertSame(request, servletRequest);
+ final HttpServletRequest request = createMock(HttpServletRequest.class);
+
+ String name = Key.get(InRequest.class).toString();
+ expect(request.getAttribute(name)).andReturn(null);
+ request.setAttribute(eq(name), isA(InRequest.class));
+
+ final boolean[] invoked = new boolean[1];
+ FilterChain filterChain = new FilterChain() {
+ public void doFilter(ServletRequest servletRequest,
+ ServletResponse servletResponse) {
+ invoked[0] = true;
+// assertSame(request, servletRequest);
assertNotNull(injector.getInstance(InRequest.class));
}
};
- replayFilterAndVerify();
- }
-
- public void testNullRequestObject() throws Exception {
- expect(request.getAttribute(nullRequestKey.toString())).andReturn(null).times(2);
- request.setAttribute(eq(nullRequestKey.toString()), isNull());
-
- expect(request.getSession()).andReturn(session);
- expect(session.getAttribute(nullSessionKey.toString())).andReturn(null);
- session.setAttribute(eq(nullSessionKey.toString()), isNull());
-
- filterChain = new FilterChain() {
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) {
- invoked = true;
- assertSame(request, servletRequest);
- assertNull(injector.getInstance(nullRequestKey));
- assertNull(injector.getInstance(nullRequestKey));
- assertNull(injector.getInstance(nullSessionKey));
- assertNull(injector.getInstance(nullSessionKey));
- }
- };
-
- replayFilterAndVerify();
- }
-
- public void testExistingRequestObject() throws Exception {
- final InRequest inRequest = new InRequest();
- expect(request.getAttribute(requestKey.toString())).andReturn(inRequest).times(2);
-
- filterChain = new FilterChain() {
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) {
- invoked = true;
- assertSame(request, servletRequest);
- assertSame(inRequest, injector.getInstance(InRequest.class));
- assertSame(inRequest, injector.getInstance(InRequest.class));
- }
- };
-
- replayFilterAndVerify();
- }
-
- public void testNewSessionObject() throws Exception {
- expect(request.getSession()).andReturn(session);
- expect(session.getAttribute(sessionKey.toString())).andReturn(null);
- session.setAttribute(eq(sessionKey.toString()), isA(InSession.class));
-
- filterChain = new FilterChain() {
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) {
- invoked = true;
- assertSame(request, servletRequest);
- assertNotNull(injector.getInstance(InSession.class));
- }
- };
-
- replayFilterAndVerify();
- }
-
- public void testExistingSessionObject() throws Exception {
- final InSession inSession = new InSession();
- expect(request.getSession()).andReturn(session).times(2);
- expect(session.getAttribute(sessionKey.toString())).andReturn(inSession).times(2);
-
- filterChain = new FilterChain() {
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) {
- invoked = true;
- assertSame(request, servletRequest);
- assertSame(inSession, injector.getInstance(InSession.class));
- assertSame(inSession, injector.getInstance(InSession.class));
- }
- };
-
- replayFilterAndVerify();
- }
-
- private Injector createInjector() throws CreationException {
- return Guice.createInjector(new AbstractModule() {
- protected void configure() {
- install(new ServletModule());
- bind(InSession.class);
- bind(InRequest.class);
- bind(nullRequestKey).toProvider(new NullThenNonNullProvider()).in(RequestScoped.class);
- bind(nullSessionKey).toProvider(new NullThenNonNullProvider()).in(SessionScoped.class);
- }
- });
- }
-
- private void replayFilterAndVerify() throws IOException, ServletException {
replay(request);
- replay(session);
filter.doFilter(request, null, filterChain);
verify(request);
- verify(session);
- assertTrue(invoked);
+ assertTrue(invoked[0]);
+ }
+
+ public void testExistingRequestObject()
+ throws CreationException, IOException, ServletException {
+ final Injector injector = createInjector();
+
+ GuiceFilter filter = new GuiceFilter();
+
+ final HttpServletRequest request = createMock(HttpServletRequest.class);
+
+ final InRequest inRequest = new InRequest();
+ String name = Key.get(InRequest.class).toString();
+ expect(request.getAttribute(name)).andReturn(inRequest).times(2);
+
+ final boolean[] invoked = new boolean[1];
+ FilterChain filterChain = new FilterChain() {
+ public void doFilter(ServletRequest servletRequest,
+ ServletResponse servletResponse) {
+ invoked[0] = true;
+
+ assertSame(inRequest, injector.getInstance(InRequest.class));
+ assertSame(inRequest, injector.getInstance(InRequest.class));
+ }
+ };
+
+ replay(request);
+
+ filter.doFilter(request, null, filterChain);
+
+ verify(request);
+ assertTrue(invoked[0]);
+ }
+
+ public void testNewSessionObject()
+ throws CreationException, IOException, ServletException {
+ final Injector injector = createInjector();
+
+ GuiceFilter filter = new GuiceFilter();
+
+ final HttpServletRequest request = createMock(HttpServletRequest.class);
+ final HttpSession session = createMock(HttpSession.class);
+
+ String name = Key.get(InSession.class).toString();
+
+ expect(request.getSession()).andReturn(session);
+ expect(session.getAttribute(name)).andReturn(null);
+ session.setAttribute(eq(name), isA(InSession.class));
+
+ final boolean[] invoked = new boolean[1];
+ FilterChain filterChain = new FilterChain() {
+ public void doFilter(ServletRequest servletRequest,
+ ServletResponse servletResponse) {
+ invoked[0] = true;
+// assertSame(request, servletRequest);
+ assertNotNull(injector.getInstance(InSession.class));
+ }
+ };
+
+ replay(request, session);
+
+ filter.doFilter(request, null, filterChain);
+
+ verify(request, session);
+ assertTrue(invoked[0]);
+ }
+
+ public void testExistingSessionObject()
+ throws CreationException, IOException, ServletException {
+ final Injector injector = createInjector();
+
+ GuiceFilter filter = new GuiceFilter();
+
+ final HttpServletRequest request = createMock(HttpServletRequest.class);
+ final HttpSession session = createMock(HttpSession.class);
+
+ String name = Key.get(InSession.class).toString();
+
+ final InSession inSession = new InSession();
+ expect(request.getSession()).andReturn(session).times(2);
+ expect(session.getAttribute(name)).andReturn(inSession).times(2);
+
+ final boolean[] invoked = new boolean[1];
+ FilterChain filterChain = new FilterChain() {
+ public void doFilter(ServletRequest servletRequest,
+ ServletResponse servletResponse) {
+ invoked[0] = true;
+// assertSame(request, servletRequest);
+
+ assertSame(inSession, injector.getInstance(InSession.class));
+ assertSame(inSession, injector.getInstance(InSession.class));
+ }
+ };
+
+ replay(request, session);
+
+ filter.doFilter(request, null, filterChain);
+
+ verify(request, session);
+ assertTrue(invoked[0]);
+ }
+
+ private Injector createInjector() throws CreationException {
+
+ return Guice.createInjector(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ install(Servlets.configure().filters().buildModule());
+ bind(InSession.class);
+ bind(InRequest.class);
+ }
+ });
}
@SessionScoped
@@ -172,11 +198,4 @@
@RequestScoped
static class InRequest {}
-
- static class NullThenNonNullProvider implements Provider<String> {
- final Iterator<String> iterator = Arrays.asList(null, "A").iterator();
- public String get() {
- return iterator.next();
- }
- }
}