Added varargs mapping for filters and servlets. And added a bunch of tests that verify the dispatch pipeline, servlet spec compliance, and so forth.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@738 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/servlet/src/com/google/inject/servlet/FiltersModuleBuilder.java b/servlet/src/com/google/inject/servlet/FiltersModuleBuilder.java
index 2ae75e7..66ca704 100755
--- a/servlet/src/com/google/inject/servlet/FiltersModuleBuilder.java
+++ b/servlet/src/com/google/inject/servlet/FiltersModuleBuilder.java
@@ -41,21 +41,21 @@
     bind(FilterPipeline.class).toInstance(new ManagedFilterPipeline(filterDefinitions));
   }
 
-  public ServletModule.FilterKeyBindingBuilder filter(String urlPattern) {
-    return new FilterKeyBindingBuilderImpl(urlPattern, UriPatternType.SERVLET);
+  public ServletModule.FilterKeyBindingBuilder filter(List<String> patterns) {
+    return new FilterKeyBindingBuilderImpl(patterns, UriPatternType.SERVLET);
   }
 
-  public ServletModule.FilterKeyBindingBuilder filterRegex(String regex) {
-    return new FilterKeyBindingBuilderImpl(regex, UriPatternType.REGEX);
+  public ServletModule.FilterKeyBindingBuilder filterRegex(List<String> regexes) {
+    return new FilterKeyBindingBuilderImpl(regexes, UriPatternType.REGEX);
   }
 
   //non-static inner class so it can access state of enclosing module class
   class FilterKeyBindingBuilderImpl implements ServletModule.FilterKeyBindingBuilder {
-    private final String uriPattern;
+    private final List<String> uriPatterns;
     private final UriPatternType uriPatternType;
 
-    private FilterKeyBindingBuilderImpl(String uriPattern, UriPatternType uriPatternType) {
-      this.uriPattern = uriPattern;
+    private FilterKeyBindingBuilderImpl(List<String> uriPatterns, UriPatternType uriPatternType) {
+      this.uriPatterns = uriPatterns;
       this.uriPatternType = uriPatternType;
     }
 
@@ -76,9 +76,11 @@
     public void through(Key<? extends Filter> filterKey,
         Map<String, String> contextParams) {
 
-      filterDefinitions.add(
-          new FilterDefinition(uriPattern, filterKey, UriPatternType.get(uriPatternType),
-              contextParams));
+      for (String pattern : uriPatterns) {
+        filterDefinitions.add(
+            new FilterDefinition(pattern, filterKey, UriPatternType.get(uriPatternType),
+                contextParams));
+      }
     }
   }
 }
diff --git a/servlet/src/com/google/inject/servlet/GuiceFilter.java b/servlet/src/com/google/inject/servlet/GuiceFilter.java
index 52b07fc..0973fc1 100644
--- a/servlet/src/com/google/inject/servlet/GuiceFilter.java
+++ b/servlet/src/com/google/inject/servlet/GuiceFilter.java
@@ -56,8 +56,7 @@
  */
 public class GuiceFilter implements Filter {
   static final ThreadLocal<Context> localContext = new ThreadLocal<Context>();
-  static volatile WeakReference<FilterPipeline> pipeline =
-      new WeakReference<FilterPipeline>(new DefaultFilterPipeline());
+  static volatile FilterPipeline pipeline = new DefaultFilterPipeline();
 
   /** Used to inject the servlets configured via {@link ServletModule} */
   static volatile WeakReference<ServletContext> servletContext =
@@ -68,7 +67,7 @@
   static void setPipeline(FilterPipeline pipeline) {
 
     // Multiple injectors with ServletModules?
-    if (GuiceFilter.pipeline.get() instanceof ManagedFilterPipeline) {
+    if (GuiceFilter.pipeline instanceof ManagedFilterPipeline) {
       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"
@@ -77,12 +76,12 @@
     }
 
     // We will only overwrite the default pipeline
-    GuiceFilter.pipeline = new WeakReference<FilterPipeline>(pipeline);
+    GuiceFilter.pipeline = pipeline;
   }
 
   //VisibleForTesting
   static void clearPipeline() {
-    pipeline = new WeakReference<FilterPipeline>(null);
+    pipeline = null;
   }
 
   public void doFilter(ServletRequest servletRequest,
@@ -90,7 +89,7 @@
       throws IOException, ServletException {
 
     Context previous = localContext.get();
-    FilterPipeline filterPipeline = pipeline.get();
+    FilterPipeline filterPipeline = pipeline;
 
     try {
       localContext.set(new Context((HttpServletRequest) servletRequest,
@@ -155,17 +154,17 @@
     // In the default pipeline, this is a noop. However, if replaced
     // by a managed pipeline, a lazy init will be triggered the first time
     // dispatch occurs.
-    GuiceFilter.pipeline.get().initPipeline(servletContext);
+    GuiceFilter.pipeline.initPipeline(servletContext);
   }
 
   public void destroy() {
 
     try {
       // Destroy all registered filters & servlets in that order
-      pipeline.get().destroyPipeline();
+      pipeline.destroyPipeline();
 
     } finally {
-      pipeline.clear();
+      clearPipeline();
       servletContext.clear();
     }
   }
diff --git a/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
index b03a390..a354a3c 100755
--- a/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
+++ b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
@@ -77,17 +77,17 @@
                   + "flush buffers)");
             }
 
-            //clear buffer before forwarding
+            // clear buffer before forwarding
             servletResponse.resetBuffer();
 
-            //now dispatch to the servlet
+            // now dispatch to the servlet
             servletDefinition.doService(servletRequest, servletResponse);
           }
 
           public void include(ServletRequest servletRequest, ServletResponse servletResponse)
               throws ServletException, IOException {
 
-            //route to the target servlet
+            // route to the target servlet
             servletDefinition.doService(servletRequest, servletResponse);
           }
         };
diff --git a/servlet/src/com/google/inject/servlet/ServletDefinition.java b/servlet/src/com/google/inject/servlet/ServletDefinition.java
index 008d8aa..43b0df3 100755
--- a/servlet/src/com/google/inject/servlet/ServletDefinition.java
+++ b/servlet/src/com/google/inject/servlet/ServletDefinition.java
@@ -153,8 +153,8 @@
           pathInfo = getRequestURI().substring(getContextPath().length()).replaceAll("[/]{2,}", "/")
               .substring(servletPathLength);
 
-          //corner case: when servlet path and request path match exactly (without trailing '/'),
-          //pathinfo is null
+          // Corner case: when servlet path and request path match exactly (without trailing '/'),
+          // then pathinfo is null
           if ("".equals(pathInfo) && servletPathLength != 0) {
             pathInfo = null;
           }
@@ -177,7 +177,7 @@
         return (null == info) ? null : getRealPath(info);
       }
 
-      //memoizer pattern
+      // Memoizer pattern.
       private String computePath() {
         if (!pathComputed) {
           path = patternMatcher.extractPath(pattern);
diff --git a/servlet/src/com/google/inject/servlet/ServletModule.java b/servlet/src/com/google/inject/servlet/ServletModule.java
index c0d45b5..b62e694 100644
--- a/servlet/src/com/google/inject/servlet/ServletModule.java
+++ b/servlet/src/com/google/inject/servlet/ServletModule.java
@@ -16,14 +16,13 @@
 
 package com.google.inject.servlet;
 
-import static com.google.inject.servlet.ServletScopes.REQUEST;
-import static com.google.inject.servlet.ServletScopes.SESSION;
-
+import com.google.common.collect.Lists;
 import com.google.inject.AbstractModule;
 import com.google.inject.Key;
 import com.google.inject.Provider;
 import com.google.inject.TypeLiteral;
-
+import static com.google.inject.servlet.ServletScopes.REQUEST;
+import static com.google.inject.servlet.ServletScopes.SESSION;
 import java.util.Map;
 import javax.servlet.Filter;
 import javax.servlet.ServletContext;
@@ -154,7 +153,13 @@
    *       <b>serve("/my/*").with(MyServlet.class)</b>
    * </pre>
    *
-   * You are free to register as many servlets and filters as you like this way:
+   * Every servlet is required to be a singleton and will implicitly be bound as one if it isn't
+   * already. Mapping a servlet that is bound under any other scope is an error.
+   *
+   * <p>
+   * <h4>Dispatch Order</h4>
+   * You are free to register as many servlets and filters as you like this way. They will
+   * be compared and dispatched in the order in which the filter methods are called:
    *
    * <pre>
    *
@@ -162,17 +167,41 @@
    *
    *     {@literal @}Override
    *     protected void configureServlets() {
-   *       filter("/*").through(MyFilter.class)
-   *       filter("*.css").through(MyCssFilter.class)
+   *       filter("/*").through(MyFilter.class);
+   *       filter("*.css").through(MyCssFilter.class);
    *       // etc..
    *
-   *       serve("*.html").with(MyServlet.class)
-   *       serve("/my/*").with(MyServlet.class)
+   *       serve("*.html").with(MyServlet.class);
+   *       serve("/my/*").with(MyServlet.class);
    *       // etc..
    *      }
    *    }
    * </pre>
+   * This will traverse down the list of rules in lexical order. For example, a url
+   *  "{@code /my/file.js}" (after it runs through the matching filters) will first
+   *  be compared against the servlet mapping:
+   * 
+   * <pre>
+   *       serve("*.html").with(MyServlet.class);
+   * </pre>
+   * And failing that, it will descend to the next servlet mapping:
    *
+   * <pre>
+   *       serve("/my/*").with(MyServlet.class);
+   * </pre>
+   *
+   * Since this rule matches, Guice Servlet will dispatch to {@code MyServlet}. These
+   * two mapping rules can also be written in more compact form using varargs syntax:
+   *
+   * <pre>
+   *       serve(<b>"*.html", "/my/*"</b>).with(MyServlet.class);
+   * </pre>
+   * 
+   * This way you can map several URI patterns to the same servlet. A similar syntax is
+   * also available for filter mappings.
+   *
+   * <p>
+   * <h4>Regular Expressions</h4>
    * You can also map servlets (or filters) to URIs using regular expressions:
    * <pre>
    *    <b>serveRegex("(.)*ajax(.)*").with(MyAjaxServlet.class)</b>
@@ -203,15 +232,14 @@
    *      serve("/*").with(MyServlet.class, <b>params</b>)
    * </pre>
    *
-   *
+   * <p>
    * <h3>Binding Keys</h3>
    *
-   * <p> You can also bind keys rather than classes. This lets you hide
+   * 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>
-   *
    *  ...
    *      filter("/*").through(<b>Key.get(Filter.class, Fave.class)</b>);
    * </pre>
@@ -224,7 +252,7 @@
    *   bind(Filter.class)<b>.annotatedWith(Fave.class)</b>.to(MyFilterImpl.class);
    * </pre>
    *
-   * See Guice documentation for more information on binding annotations.
+   * See {@link com.google.inject.Binder} for more information on binding syntax.
    */
   protected void configureServlets() {
   }
@@ -236,29 +264,29 @@
   /**
    * @param urlPattern Any Servlet-style pattern. examples: /*, /html/*, *.html, etc.
    */
-  protected final FilterKeyBindingBuilder filter(String urlPattern) {
-    return filtersModuleBuilder.filter(urlPattern);
+  protected final FilterKeyBindingBuilder filter(String urlPattern, String... morePatterns) {
+    return filtersModuleBuilder.filter(Lists.asList(urlPattern, morePatterns));
   }
 
   /**
    * @param regex Any Java-style regular expression.
    */
-  protected final FilterKeyBindingBuilder filterRegex(String regex) {
-    return filtersModuleBuilder.filterRegex(regex);
+  protected final FilterKeyBindingBuilder filterRegex(String regex, String... regexes) {
+    return filtersModuleBuilder.filterRegex(Lists.asList(regex, regexes));
   }
 
   /**
    * @param urlPattern Any Servlet-style pattern. examples: /*, /html/*, *.html, etc.
    */
-  protected final ServletKeyBindingBuilder serve(String urlPattern) {
-    return servletsModuleBuilder.serve(urlPattern);
+  protected final ServletKeyBindingBuilder serve(String urlPattern, String... morePatterns) {
+    return servletsModuleBuilder.serve(Lists.asList(urlPattern, morePatterns));
   }
 
   /**
    * @param regex Any Java-style regular expression.
    */
-  protected final ServletKeyBindingBuilder serveRegex(String regex) {
-    return servletsModuleBuilder.serveRegex(regex);
+  protected final ServletKeyBindingBuilder serveRegex(String regex, String... regexes) {
+    return servletsModuleBuilder.serveRegex(Lists.asList(regex, regexes));
   }
 
   public static interface FilterKeyBindingBuilder {
diff --git a/servlet/src/com/google/inject/servlet/ServletsModuleBuilder.java b/servlet/src/com/google/inject/servlet/ServletsModuleBuilder.java
index bb79139..6f22d3f 100755
--- a/servlet/src/com/google/inject/servlet/ServletsModuleBuilder.java
+++ b/servlet/src/com/google/inject/servlet/ServletsModuleBuilder.java
@@ -41,21 +41,21 @@
   }
 
   //the first level of the EDSL--
-  public ServletModule.ServletKeyBindingBuilder serve(String urlPattern) {
-    return new ServletKeyBindingBuilderImpl(urlPattern, UriPatternType.SERVLET);
+  public ServletModule.ServletKeyBindingBuilder serve(List<String> urlPatterns) {
+    return new ServletKeyBindingBuilderImpl(urlPatterns, UriPatternType.SERVLET);
   }
 
-  public ServletModule.ServletKeyBindingBuilder serveRegex(String regex) {
-    return new ServletKeyBindingBuilderImpl(regex, UriPatternType.REGEX);
+  public ServletModule.ServletKeyBindingBuilder serveRegex(List<String> regexes) {
+    return new ServletKeyBindingBuilderImpl(regexes, UriPatternType.REGEX);
   }
 
   //non-static inner class so it can access state of enclosing module class
   class ServletKeyBindingBuilderImpl implements ServletModule.ServletKeyBindingBuilder {
-    private final String uriPattern;
+    private final List<String> uriPatterns;
     private final UriPatternType uriPatternType;
 
-    private ServletKeyBindingBuilderImpl(String uriPattern, UriPatternType uriPatternType) {
-      this.uriPattern = uriPattern;
+    private ServletKeyBindingBuilderImpl(List<String> uriPatterns, UriPatternType uriPatternType) {
+      this.uriPatterns = uriPatterns;
       this.uriPatternType = uriPatternType;
     }
 
@@ -74,9 +74,12 @@
 
     public void with(Key<? extends HttpServlet> servletKey,
         Map<String, String> contextParams) {
-      servletDefinitions.add(
-          new ServletDefinition(uriPattern, servletKey, UriPatternType.get(uriPatternType),
-              contextParams));
+
+      for (String pattern : uriPatterns) {
+        servletDefinitions.add(
+            new ServletDefinition(pattern, servletKey, UriPatternType.get(uriPatternType),
+                contextParams));
+      }
     }
   }
 }
\ No newline at end of file
diff --git a/servlet/test/com/google/inject/servlet/AllTests.java b/servlet/test/com/google/inject/servlet/AllTests.java
index fe2475c..d868bf1 100644
--- a/servlet/test/com/google/inject/servlet/AllTests.java
+++ b/servlet/test/com/google/inject/servlet/AllTests.java
@@ -27,12 +27,22 @@
   public static Test suite() {
     TestSuite suite = new TestSuite();
 
-    // Servlet tests.
+    // Filter tests.
     suite.addTestSuite(EdslTest.class);
     suite.addTestSuite(FilterDefinitionTest.class);
     suite.addTestSuite(FilterDispatchIntegrationTest.class);
     suite.addTestSuite(FilterPipelineTest.class);
+
+    // Servlet + integration tests.
     suite.addTestSuite(ServletTest.class);
+    suite.addTestSuite(ServletDefinitionTest.class);
+    suite.addTestSuite(ServletDefinitionPathsTest.class);
+    suite.addTestSuite(ServletPipelineRequestDispatcherTest.class);
+    suite.addTestSuite(ServletDispatchIntegrationTest.class);
+
+    // Varargs URL mapping tests.
+    suite.addTestSuite(VarargsFilterDispatchIntegrationTest.class);
+    suite.addTestSuite(VarargsServletDispatchIntegrationTest.class);
 
     return suite;
   }
diff --git a/servlet/test/com/google/inject/servlet/ServletDefinitionPathsTest.java b/servlet/test/com/google/inject/servlet/ServletDefinitionPathsTest.java
new file mode 100644
index 0000000..a8e3a36
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/ServletDefinitionPathsTest.java
@@ -0,0 +1,260 @@
+/**
+ * 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 static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Ensures servlet spec compliance for CGI-style variables and general
+ *  path/pattern matching.
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+public class ServletDefinitionPathsTest extends TestCase {
+
+  // Data-driven test.
+  public final void testServletPathMatching() throws IOException, ServletException {
+    servletPath("/index.html", "*.html", "/index.html");
+    servletPath("/somewhere/index.html", "*.html", "/somewhere/index.html");
+    servletPath("/somewhere/index.html", "/*", "");
+    servletPath("/index.html", "/*", "");
+    servletPath("/", "/*", "");
+    servletPath("//", "/*", "");
+    servletPath("/////", "/*", "");
+    servletPath("", "/*", "");
+    servletPath("/thing/index.html", "/thing/*", "/thing");
+    servletPath("/thing/wing/index.html", "/thing/*", "/thing");
+  }
+
+  private void servletPath(final String requestPath, String mapping,
+      final String expectedServletPath) throws IOException, ServletException {
+
+    Injector injector = createMock(Injector.class);
+    HttpServletRequest request = createMock(HttpServletRequest.class);
+    HttpServletResponse response = createMock(HttpServletResponse.class);
+
+    final boolean[] run = new boolean[1];
+
+    //get an instance of this servlet
+    expect(injector.getInstance(Key.get(HttpServlet.class)))
+        .andReturn(new HttpServlet() {
+
+          @Override
+          protected void service(HttpServletRequest servletRequest,
+              HttpServletResponse httpServletResponse) throws ServletException, IOException {
+
+            final String path = servletRequest.getServletPath();
+            assertEquals(String.format("expected [%s] but was [%s]", expectedServletPath, path),
+                expectedServletPath, path);
+            run[0] = true;
+          }
+        });
+
+    expect(request.getServletPath())
+        .andReturn(requestPath);
+
+    replay(injector, request);
+
+    ServletDefinition servletDefinition = new ServletDefinition(mapping, Key.get(HttpServlet.class),
+        UriPatternType.get(UriPatternType.SERVLET), new HashMap<String, String>());
+
+    servletDefinition.init(null, injector);
+    servletDefinition.doService(request, response);
+
+    assertTrue("Servlet did not run!", run[0]);
+
+  }
+
+  // Data-driven test.
+  public final void testPathInfoWithServletStyleMatching() throws IOException, ServletException {
+    pathInfoWithServletStyleMatching("/path/index.html", "/path", "/*", "/index.html", "");
+    pathInfoWithServletStyleMatching("/path//hulaboo///index.html", "/path", "/*",
+        "/hulaboo/index.html", "");
+    pathInfoWithServletStyleMatching("/path/", "/path", "/*", "/", "");
+    pathInfoWithServletStyleMatching("/path////////", "/path", "/*", "/", "");
+
+    // a servlet mapping of /thing/*
+    pathInfoWithServletStyleMatching("/path/thing////////", "/path", "/thing/*", "/", "/thing");
+    pathInfoWithServletStyleMatching("/path/thing/stuff", "/path", "/thing/*", "/stuff", "/thing");
+    pathInfoWithServletStyleMatching("/path/thing/stuff.html", "/path", "/thing/*", "/stuff.html",
+        "/thing");
+    pathInfoWithServletStyleMatching("/path/thing", "/path", "/thing/*", null, "/thing");
+
+    // *.xx style mapping
+    pathInfoWithServletStyleMatching("/path/thing.thing", "/path", "*.thing", null, "/thing.thing");
+    pathInfoWithServletStyleMatching("/path///h.thing", "/path", "*.thing", null, "/h.thing");
+    pathInfoWithServletStyleMatching("/path///...//h.thing", "/path", "*.thing", null,
+        "/.../h.thing");
+    pathInfoWithServletStyleMatching("/path/my/h.thing", "/path", "*.thing", null, "/my/h.thing");
+
+  }
+
+  private void pathInfoWithServletStyleMatching(final String requestUri, final String contextPath,
+      String mapping, final String expectedPathInfo, final String servletPath)
+      throws IOException, ServletException {
+
+    Injector injector = createMock(Injector.class);
+    HttpServletRequest request = createMock(HttpServletRequest.class);
+    HttpServletResponse response = createMock(HttpServletResponse.class);
+
+    final boolean[] run = new boolean[1];
+
+    //get an instance of this servlet
+    expect(injector.getInstance(Key.get(HttpServlet.class)))
+        .andReturn(new HttpServlet() {
+
+          @Override
+          protected void service(HttpServletRequest servletRequest,
+              HttpServletResponse httpServletResponse) throws ServletException, IOException {
+
+            final String path = servletRequest.getPathInfo();
+
+            if (null == expectedPathInfo) {
+              assertNull(String.format("expected [%s] but was [%s]", expectedPathInfo, path),
+                  path);
+            }
+            else {
+              assertEquals(String.format("expected [%s] but was [%s]", expectedPathInfo, path),
+                  expectedPathInfo, path);
+            }
+
+            //assert memoizer
+            //noinspection StringEquality
+            assertSame("memo field did not work", path, servletRequest.getPathInfo());
+
+            run[0] = true;
+          }
+        });
+
+    expect(request.getRequestURI())
+        .andReturn(requestUri);
+
+    expect(request.getServletPath())
+        .andReturn(servletPath)
+        .anyTimes();
+
+    expect(request.getContextPath())
+        .andReturn(contextPath);
+
+    replay(injector, request);
+
+    ServletDefinition servletDefinition = new ServletDefinition(mapping, Key.get(HttpServlet.class),
+        UriPatternType.get(UriPatternType.SERVLET), new HashMap<String, String>());
+
+    servletDefinition.init(null, injector);
+    servletDefinition.doService(request, response);
+
+    assertTrue("Servlet did not run!", run[0]);
+
+  }
+
+  // Data-driven test.
+  public final void testPathInfoWithRegexMatching() throws IOException, ServletException {
+    // first a mapping of /*
+    pathInfoWithRegexMatching("/path/index.html", "/path", "/(.)*", "/index.html", "");
+    pathInfoWithRegexMatching("/path//hulaboo///index.html", "/path", "/(.)*",
+        "/hulaboo/index.html", "");
+    pathInfoWithRegexMatching("/path/", "/path", "/(.)*", "/", "");
+    pathInfoWithRegexMatching("/path////////", "/path", "/(.)*", "/", "");
+
+    // a servlet mapping of /thing/*
+    pathInfoWithRegexMatching("/path/thing////////", "/path", "/thing/(.)*", "/", "/thing");
+    pathInfoWithRegexMatching("/path/thing/stuff", "/path", "/thing/(.)*", "/stuff", "/thing");
+    pathInfoWithRegexMatching("/path/thing/stuff.html", "/path", "/thing/(.)*", "/stuff.html",
+        "/thing");
+    pathInfoWithRegexMatching("/path/thing", "/path", "/thing/(.)*", null, "/thing");
+
+    // *.xx style mapping
+    pathInfoWithRegexMatching("/path/thing.thing", "/path", "(.)*\\.thing", null, "/thing.thing");
+    pathInfoWithRegexMatching("/path///h.thing", "/path", "(.)*\\.thing", null, "/h.thing");
+    pathInfoWithRegexMatching("/path///...//h.thing", "/path", "(.)*\\.thing", null,
+        "/.../h.thing");
+    pathInfoWithRegexMatching("/path/my/h.thing", "/path", "(.)*\\.thing", null, "/my/h.thing");
+  }
+
+  public final void pathInfoWithRegexMatching(final String requestUri, final String contextPath,
+      String mapping, final String expectedPathInfo, final String servletPath)
+      throws IOException, ServletException {
+
+    Injector injector = createMock(Injector.class);
+    HttpServletRequest request = createMock(HttpServletRequest.class);
+    HttpServletResponse response = createMock(HttpServletResponse.class);
+
+    final boolean[] run = new boolean[1];
+
+    //get an instance of this servlet
+    expect(injector.getInstance(Key.get(HttpServlet.class)))
+        .andReturn(new HttpServlet() {
+
+          @Override
+          protected void service(HttpServletRequest servletRequest,
+              HttpServletResponse httpServletResponse) throws ServletException, IOException {
+
+            final String path = servletRequest.getPathInfo();
+
+            if (null == expectedPathInfo) {
+              assertNull(String.format("expected [%s] but was [%s]", expectedPathInfo, path),
+                  path);
+            }
+            else {
+              assertEquals(String.format("expected [%s] but was [%s]", expectedPathInfo, path),
+                  expectedPathInfo, path);
+            }
+
+            //assert memoizer
+            //noinspection StringEquality
+            assertSame("memo field did not work", path, servletRequest.getPathInfo());
+
+            run[0] = true;
+          }
+        });
+
+    expect(request.getRequestURI())
+        .andReturn(requestUri);
+
+    expect(request.getServletPath())
+        .andReturn(servletPath)
+        .anyTimes();
+
+    expect(request.getContextPath())
+        .andReturn(contextPath);
+
+    replay(injector, request);
+
+    ServletDefinition servletDefinition = new ServletDefinition(mapping, Key.get(HttpServlet.class),
+        UriPatternType.get(UriPatternType.REGEX), new HashMap<String, String>());
+
+    servletDefinition.init(null, injector);
+    servletDefinition.doService(request, response);
+
+    assertTrue("Servlet did not run!", run[0]);
+  }
+}
diff --git a/servlet/test/com/google/inject/servlet/ServletDefinitionTest.java b/servlet/test/com/google/inject/servlet/ServletDefinitionTest.java
new file mode 100644
index 0000000..8197914
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/ServletDefinitionTest.java
@@ -0,0 +1,90 @@
+/**
+ * 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 static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import junit.framework.TestCase;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+/**
+ * Basic unit test for lifecycle of a ServletDefinition (wrapper).
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+public class ServletDefinitionTest extends TestCase {
+
+  public final void testServletInitAndConfig() throws ServletException {
+    Injector injector = createMock(Injector.class);
+
+    final HttpServlet mockServlet = new HttpServlet() {
+    };
+    expect(injector.getInstance(Key.get(HttpServlet.class)))
+        .andReturn(mockServlet)
+        .anyTimes();
+
+    replay(injector);
+
+    //some init params
+    //noinspection SSBasedInspection
+    final Map<String, String> initParams = new HashMap<String, String>() {
+      {
+        put("ahsd", "asdas24dok");
+        put("ahssd", "asdasd124ok");
+        put("ahfsasd", "asda124sdok");
+        put("ahsasgd", "a124sdasdok");
+        put("ahsd124124", "as124124124dasdok");
+      }
+    };
+
+    final ServletDefinition servletDefinition = new ServletDefinition("/*",
+        Key.get(HttpServlet.class), UriPatternType.get(UriPatternType.SERVLET), initParams);
+
+    ServletContext servletContext = createMock(ServletContext.class);
+    final String contextName = "thing__!@@44__SRV" + getClass();
+    expect(servletContext.getServletContextName())
+        .andReturn(contextName);
+
+    replay(servletContext);
+
+    servletDefinition.init(servletContext, injector);
+
+    assertNotNull(mockServlet.getServletContext());
+    assertEquals(contextName, mockServlet.getServletContext().getServletContextName());
+    assertEquals(Key.get(HttpServlet.class).toString(), mockServlet.getServletName());
+
+    final ServletConfig servletConfig = mockServlet.getServletConfig();
+    final Enumeration names = servletConfig.getInitParameterNames();
+    while (names.hasMoreElements()) {
+      String name = (String) names.nextElement();
+
+      assertTrue(initParams.containsKey(name));
+      assertEquals(initParams.get(name), servletConfig.getInitParameter(name));
+    }
+  }
+}
diff --git a/servlet/test/com/google/inject/servlet/ServletDispatchIntegrationTest.java b/servlet/test/com/google/inject/servlet/ServletDispatchIntegrationTest.java
new file mode 100644
index 0000000..1264623
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/ServletDispatchIntegrationTest.java
@@ -0,0 +1,191 @@
+/**
+ * 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.Injector;
+import com.google.inject.Key;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+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;
+
+/**
+ * Tests the FilterPipeline that dispatches to guice-managed servlets,
+ * is a full integration test, with a real injector.
+ *
+ * @author Dhanji R. Prasanna (dhanji gmail com)
+ */
+public class ServletDispatchIntegrationTest extends TestCase {
+  private static int inits, services, destroys, doFilters;
+
+  @Override
+  public void setUp() {
+    inits = 0;
+    services = 0;
+    destroys = 0;
+    doFilters = 0;
+
+    GuiceFilter.clearPipeline();
+  }
+
+  public final void testDispatchRequestToManagedPipelineServlets()
+      throws ServletException, IOException {
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        serve("/*").with(TestServlet.class);
+
+        // These servets should never fire... (ordering test)
+        serve("*.html").with(NeverServlet.class);
+        serve("/*").with(Key.get(NeverServlet.class));
+        serve("/index/*").with(Key.get(NeverServlet.class));
+        serve("*.jsp").with(Key.get(NeverServlet.class));
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+        .andReturn("/index.html")
+        .times(1);
+
+    //dispatch request
+    replay(requestMock);
+
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assertTrue("lifecycle states did not fire correct number of times-- inits: " + inits + "; dos: "
+            + services + "; destroys: " + destroys,
+        inits == 5 && services == 1 && destroys == 5);
+  }
+
+  public final void testDispatchRequestToManagedPipelineWithFilter()
+      throws ServletException, IOException {
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        filter("/*").through(TestFilter.class);
+
+        serve("/*").with(TestServlet.class);
+
+        // These servets should never fire...
+        serve("*.html").with(NeverServlet.class);
+        serve("/*").with(Key.get(NeverServlet.class));
+        serve("/index/*").with(Key.get(NeverServlet.class));
+        serve("*.jsp").with(Key.get(NeverServlet.class));
+
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+        .andReturn("/index.html")
+        .times(2);
+
+    //dispatch request
+    replay(requestMock);
+
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assertTrue("lifecycle states did not fire correct number of times-- inits: " + inits + "; dos: "
+            + services + "; destroys: " + destroys,
+        inits == 6 && services == 1 && destroys == 6 && doFilters == 1);
+  }
+
+  @Singleton
+  public static class TestServlet extends HttpServlet {
+    public void init(ServletConfig filterConfig) throws ServletException {
+      inits++;
+    }
+
+    public void service(ServletRequest servletRequest, ServletResponse servletResponse)
+        throws IOException, ServletException {
+      services++;
+    }
+
+    public void destroy() {
+      destroys++;
+    }
+  }
+
+  @Singleton
+  public static class NeverServlet extends HttpServlet {
+    public void init(ServletConfig filterConfig) throws ServletException {
+      inits++;
+    }
+
+    public void service(ServletRequest servletRequest, ServletResponse servletResponse)
+        throws IOException, ServletException {
+      assertTrue("NeverServlet was fired, when it should not have been.", false);
+    }
+
+    public void destroy() {
+      destroys++;
+    }
+  }
+
+  @Singleton
+  public static class TestFilter implements Filter {
+    public void init(FilterConfig filterConfig) throws ServletException {
+      inits++;
+    }
+
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+        FilterChain filterChain) throws IOException, ServletException {
+      doFilters++;
+      filterChain.doFilter(servletRequest, servletResponse);
+    }
+
+    public void destroy() {
+      destroys++;
+    }
+  }
+}
\ No newline at end of file
diff --git a/servlet/test/com/google/inject/servlet/ServletPipelineRequestDispatcherTest.java b/servlet/test/com/google/inject/servlet/ServletPipelineRequestDispatcherTest.java
new file mode 100644
index 0000000..b0ded86
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/ServletPipelineRequestDispatcherTest.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.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.UUID;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import junit.framework.TestCase;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * Tests forwarding and inclusion (RequestDispatcher actions from the
+ * servlet spec).
+ *
+ * @author Dhanji R. Prasanna (dhanji@gmail com)
+ */
+public class ServletPipelineRequestDispatcherTest extends TestCase {
+  private static final Key<HttpServlet> HTTP_SERLVET_KEY = Key.get(HttpServlet.class);
+  private static final String A_KEY = "thinglyDEgintly" + new Date() + UUID.randomUUID();
+  private static final String A_VALUE = ServletPipelineRequestDispatcherTest.class.toString()
+      + new Date() + UUID.randomUUID();
+
+  public final void testIncludeManagedServlet() throws IOException, ServletException {
+    final ServletDefinition servletDefinition = new ServletDefinition("blah.html",
+        Key.get(HttpServlet.class), UriPatternType.get(UriPatternType.SERVLET),
+        new HashMap<String, String>());
+
+    final Injector injector = createMock(Injector.class);
+    final HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+
+    expect(mockRequest.getAttribute(A_KEY))
+        .andReturn(A_VALUE);
+
+    final boolean[] run = new boolean[1];
+    final HttpServlet mockServlet = new HttpServlet() {
+      protected void service(HttpServletRequest request, HttpServletResponse httpServletResponse)
+          throws ServletException, IOException {
+        run[0] = true;
+
+        final Object o = request.getAttribute(A_KEY);
+        assertEquals("Wrong attrib returned - " + o, A_VALUE, o);
+      }
+    };
+
+    expect(injector.getInstance(HTTP_SERLVET_KEY))
+        .andReturn(mockServlet);
+
+    replay(injector, mockRequest);
+
+    // Have to init the Servlet before we can dispatch to it.
+    servletDefinition.init(null, injector);
+
+    final RequestDispatcher dispatcher = new ManagedServletPipeline(
+        Arrays.asList(servletDefinition))
+        .getRequestDispatcher("blah.html");
+
+    assertNotNull(dispatcher);
+    dispatcher.include(mockRequest, createMock(HttpServletResponse.class));
+
+    assertTrue("Include did not dispatch to our servlet!", run[0]);
+
+    verify(injector, mockRequest);
+  }
+
+  public final void testForwardToManagedServlet() throws IOException, ServletException {
+    final ServletDefinition servletDefinition = new ServletDefinition("blah.html",
+        Key.get(HttpServlet.class), UriPatternType.get(UriPatternType.SERVLET),
+        new HashMap<String, String>());
+
+    final Injector injector = createMock(Injector.class);
+    final HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+    final HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+
+    expect(mockRequest.getAttribute(A_KEY))
+        .andReturn(A_VALUE);
+
+    expect(mockResponse.isCommitted())
+        .andReturn(false);
+
+    mockResponse.resetBuffer();
+    expectLastCall().once();
+
+    final boolean[] run = new boolean[1];
+    final HttpServlet mockServlet = new HttpServlet() {
+      protected void service(HttpServletRequest request, HttpServletResponse httpServletResponse)
+          throws ServletException, IOException {
+        run[0] = true;
+
+        final Object o = request.getAttribute(A_KEY);
+        assertEquals("Wrong attrib returned - " + o, A_VALUE, o);
+      }
+    };
+
+    expect(injector.getInstance(HTTP_SERLVET_KEY))
+        .andReturn(mockServlet);
+
+    replay(injector, mockRequest, mockResponse);
+
+    // Have to init the Servlet before we can dispatch to it.
+    servletDefinition.init(null, injector);
+
+    final RequestDispatcher dispatcher = new ManagedServletPipeline(
+        Arrays.asList(servletDefinition))
+        .getRequestDispatcher("blah.html");
+
+    assertNotNull(dispatcher);
+    dispatcher.forward(mockRequest, mockResponse);
+
+    assertTrue("Include did not dispatch to our servlet!", run[0]);
+
+    verify(injector, mockRequest, mockResponse);
+  }
+
+  public final void testForwardToManagedServletFailureOnCommittedBuffer()
+      throws IOException, ServletException {
+    IllegalStateException expected = null;
+    try {
+      forwardToManagedServletFailureOnCommittedBuffer();
+    }
+    catch (IllegalStateException ise) {
+      expected = ise;
+    } finally {
+      assertNotNull("Expected IllegalStateException was not thrown", expected);
+    }
+  }
+
+  public final void forwardToManagedServletFailureOnCommittedBuffer()
+      throws IOException, ServletException {
+    final ServletDefinition servletDefinition = new ServletDefinition("blah.html",
+        Key.get(HttpServlet.class), UriPatternType.get(UriPatternType.SERVLET),
+        new HashMap<String, String>());
+
+    final Injector injector = createMock(Injector.class);
+    final HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+    final HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+
+    expect(mockResponse.isCommitted())
+        .andReturn(true);
+
+    final HttpServlet mockServlet = new HttpServlet() {
+      protected void service(HttpServletRequest request, HttpServletResponse httpServletResponse)
+          throws ServletException, IOException {
+
+        final Object o = request.getAttribute(A_KEY);
+        assertEquals("Wrong attrib returned - " + o, A_VALUE, o);
+      }
+    };
+
+    expect(injector.getInstance(Key.get(HttpServlet.class)))
+        .andReturn(mockServlet);
+
+    replay(injector, mockRequest, mockResponse);
+
+    // Have to init the Servlet before we can dispatch to it.
+    servletDefinition.init(null, injector);
+
+    final RequestDispatcher dispatcher = new ManagedServletPipeline(
+        Arrays.asList(servletDefinition))
+        .getRequestDispatcher("blah.html");
+
+    assertNotNull(dispatcher);
+
+    try {
+      dispatcher.forward(mockRequest, mockResponse);
+    }
+    finally {
+      verify(injector, mockRequest, mockResponse);
+    }
+
+  }
+}
diff --git a/servlet/test/com/google/inject/servlet/VarargsFilterDispatchIntegrationTest.java b/servlet/test/com/google/inject/servlet/VarargsFilterDispatchIntegrationTest.java
new file mode 100644
index 0000000..e4ec35c
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/VarargsFilterDispatchIntegrationTest.java
@@ -0,0 +1,168 @@
+package com.google.inject.servlet;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Singleton;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+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;
+import javax.servlet.http.HttpServletRequest;
+import junit.framework.TestCase;
+
+/**
+ *
+ * This tests that filter stage of the pipeline dispatches
+ * correctly to guice-managed filters.
+ *
+ * WARNING(dhanji): Non-parallelizable test =(
+ *
+ * @author dhanji@gmail.com (Dhanji R. Prasanna)
+ */
+public class VarargsFilterDispatchIntegrationTest extends TestCase {
+    private static int inits, doFilters, destroys;
+
+  @Override
+  public final void setUp() {
+    inits = 0;
+    doFilters = 0;
+    destroys = 0;
+
+    GuiceFilter.clearPipeline();
+  }
+
+
+  public final void testDispatchRequestToManagedPipeline() throws ServletException, IOException {
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        // This is actually a double match for "/*"
+        filter("/*", "*.html", "/*").through(Key.get(TestFilter.class));
+
+        // These filters should never fire
+        filter("/index/*").through(Key.get(TestFilter.class));
+        filter("*.jsp").through(Key.get(TestFilter.class));
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+            .andReturn("/index.html")
+            .anyTimes();
+
+    //dispatch request
+    replay(requestMock);
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assert inits == 5 && doFilters == 3 && destroys == 5 : "lifecycle states did not"
+          + " fire correct number of times-- inits: " + inits + "; dos: " + doFilters
+          + "; destroys: " + destroys;
+  }
+
+  public final void testDispatchThatNoFiltersFire() throws ServletException, IOException {
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        filter("/public/*", "*.html", "*.xml").through(Key.get(TestFilter.class));
+
+        // These filters should never fire
+        filter("/index/*").through(Key.get(TestFilter.class));
+        filter("*.jsp").through(Key.get(TestFilter.class));
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+            .andReturn("/index.xhtml")
+            .anyTimes();
+
+    //dispatch request
+    replay(requestMock);
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assert inits == 5 && doFilters == 0 && destroys == 5 : "lifecycle states did not "
+          + "fire correct number of times-- inits: " + inits + "; dos: " + doFilters
+          + "; destroys: " + destroys;
+  }
+
+  public final void testDispatchFilterPipelineWithRegexMatching() throws ServletException,
+      IOException {
+
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        filterRegex("/[A-Za-z]*", "/index").through(TestFilter.class);
+
+        //these filters should never fire
+        filterRegex("\\w").through(Key.get(TestFilter.class));
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+            .andReturn("/index")
+            .anyTimes();
+
+    //dispatch request
+    replay(requestMock);
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assert inits == 3 && doFilters == 2 && destroys == 3 : "lifecycle states did not fire "
+        + "correct number of times-- inits: " + inits + "; dos: " + doFilters
+        + "; destroys: " + destroys;
+  }
+
+  @Singleton
+  public static class TestFilter implements Filter {
+    public void init(FilterConfig filterConfig) throws ServletException {
+      inits++;
+    }
+
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+        FilterChain filterChain) throws IOException, ServletException {
+      doFilters++;
+      filterChain.doFilter(servletRequest, servletResponse);
+    }
+
+    public void destroy() {
+      destroys++;
+    }
+  }
+}
\ No newline at end of file
diff --git a/servlet/test/com/google/inject/servlet/VarargsServletDispatchIntegrationTest.java b/servlet/test/com/google/inject/servlet/VarargsServletDispatchIntegrationTest.java
new file mode 100644
index 0000000..28d89d1
--- /dev/null
+++ b/servlet/test/com/google/inject/servlet/VarargsServletDispatchIntegrationTest.java
@@ -0,0 +1,221 @@
+/**
+ * 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.Injector;
+import com.google.inject.Key;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+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;
+
+/**
+ * Tests the FilterPipeline that dispatches to guice-managed servlets,
+ * is a full integration test, with a real injector.
+ *
+ * @author Dhanji R. Prasanna (dhanji gmail com)
+ */
+public class VarargsServletDispatchIntegrationTest extends TestCase {
+  private static int inits, services, destroys, doFilters;
+
+  @Override
+  public void setUp() {
+    inits = 0;
+    services = 0;
+    destroys = 0;
+    doFilters = 0;
+
+    GuiceFilter.clearPipeline();
+  }
+
+  public final void testDispatchRequestToManagedPipelineServlets()
+      throws ServletException, IOException {
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        serve("/*", "/index.html").with(TestServlet.class);
+
+        // These servets should never fire... (ordering test)
+        serve("*.html", "/*", "/index/*", "*.jsp").with(Key.get(NeverServlet.class));
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+        .andReturn("/index.html")
+        .times(1);
+
+    //dispatch request
+    replay(requestMock);
+
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assertTrue("lifecycle states did not fire correct number of times-- inits: " + inits + "; dos: "
+            + services + "; destroys: " + destroys,
+        inits == 6 && services == 1 && destroys == 6);
+  }
+
+  public final void testVarargsSkipDispatchRequestToManagedPipelineServlets()
+      throws ServletException, IOException {
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        serve("/notindex", "/&*", "/index.html").with(TestServlet.class);
+
+        // These servets should never fire... (ordering test)
+        serve("*.html", "/*", "/index/*", "*.jsp").with(Key.get(NeverServlet.class));
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+        .andReturn("/index.html")
+        .times(3);
+
+    //dispatch request
+    replay(requestMock);
+
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assertTrue("lifecycle states did not fire correct number of times-- inits: " + inits + "; dos: "
+            + services + "; destroys: " + destroys,
+        inits == 7 && services == 1 && destroys == 7);
+  }
+
+  public final void testDispatchRequestToManagedPipelineWithFilter()
+      throws ServletException, IOException {
+    final Injector injector = Guice.createInjector(new ServletModule() {
+
+      @Override
+      protected void configureServlets() {
+        filter("/*").through(TestFilter.class);
+
+        serve("/*").with(TestServlet.class);
+
+        // These servets should never fire...
+        serve("*.html", "/*", "/index/*", "*.jsp").with(Key.get(NeverServlet.class));
+
+      }
+    });
+
+    final FilterPipeline pipeline = injector.getInstance(FilterPipeline.class);
+
+    pipeline.initPipeline(null);
+
+    //create ourselves a mock request with test URI
+    HttpServletRequest requestMock = createMock(HttpServletRequest.class);
+
+    expect(requestMock.getServletPath())
+        .andReturn("/index.html")
+        .times(2);
+
+    //dispatch request
+    replay(requestMock);
+
+    pipeline.dispatch(requestMock, null, createMock(FilterChain.class));
+
+    pipeline.destroyPipeline();
+
+    verify(requestMock);
+
+    assertTrue("lifecycle states did not fire correct number of times-- inits: " + inits + "; dos: "
+            + services + "; destroys: " + destroys,
+        inits == 6 && services == 1 && destroys == 6 && doFilters == 1);
+  }
+
+  @Singleton
+  public static class TestServlet extends HttpServlet {
+    public void init(ServletConfig filterConfig) throws ServletException {
+      inits++;
+    }
+
+    public void service(ServletRequest servletRequest, ServletResponse servletResponse)
+        throws IOException, ServletException {
+      services++;
+    }
+
+    public void destroy() {
+      destroys++;
+    }
+  }
+
+  @Singleton
+  public static class NeverServlet extends HttpServlet {
+    public void init(ServletConfig filterConfig) throws ServletException {
+      inits++;
+    }
+
+    public void service(ServletRequest servletRequest, ServletResponse servletResponse)
+        throws IOException, ServletException {
+      assertTrue("NeverServlet was fired, when it should not have been.", false);
+    }
+
+    public void destroy() {
+      destroys++;
+    }
+  }
+
+  @Singleton
+  public static class TestFilter implements Filter {
+    public void init(FilterConfig filterConfig) throws ServletException {
+      inits++;
+    }
+
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+        FilterChain filterChain) throws IOException, ServletException {
+      doFilters++;
+      filterChain.doFilter(servletRequest, servletResponse);
+    }
+
+    public void destroy() {
+      destroys++;
+    }
+  }
+}
\ No newline at end of file