Added servlet scopes.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@104 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/build.properties b/build.properties
index 989e7b6..ea4aa7d 100644
--- a/build.properties
+++ b/build.properties
@@ -3,4 +3,4 @@
 src.dir=src
 test.dir=test
 build.dir=build
-javadoc.packagenames=com.google.inject,com.google.inject.spi,com.google.inject.intercept
+javadoc.packagenames=com.google.inject,com.google.inject.spi,com.google.inject.intercept,com.google.inject.servlet
diff --git a/build.xml b/build.xml
index 345b44c..301f1e1 100644
--- a/build.xml
+++ b/build.xml
@@ -98,6 +98,8 @@
         <pathelement location="${build.dir}/dist/guice-${version}.jar"/>
         <pathelement location="lib/aopalliance.jar"/>
         <pathelement location="lib/build/junit.jar"/>
+        <pathelement location="lib/build/servlet-api-2.5.jar"/>
+        <pathelement location="lib/build/easymock.jar"/>
       </classpath>
       <arg value="com.google.inject.AllTests"/>    
     </java>
diff --git a/guice.iml b/guice.iml
index 2881ec3..a86f9df 100644
--- a/guice.iml
+++ b/guice.iml
@@ -68,6 +68,24 @@
         </SOURCES>
       </library>
     </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/build/servlet-api-2.5.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/build/easymock.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
     <orderEntryProperties />
   </component>
 </module>
diff --git a/guice.ipr b/guice.ipr
index 0bed8d0..b7a3645 100644
--- a/guice.ipr
+++ b/guice.ipr
@@ -6,11 +6,11 @@
   <component name="Build editor project-level loader">
     <settings>
       <class-settings class="com.google.devtools.intellig.configcheck.ProjectPathChecker" />
+      <class-settings class="com.google.devtools.intellig.configcheck.PythonSdkChecker" />
       <class-settings class="com.google.devtools.intellig.configcheck.ProjectJdkChecker">
         <setting name="getProjectJdk" value="/usr/local/buildtools/java/jdk1.5.0_06" />
         <setting name="getModuleJdks" value="rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAABA/QAAAAAAAAHg=" />
       </class-settings>
-      <class-settings class="com.google.devtools.intellig.configcheck.PythonSdkChecker" />
       <class-settings class="com.google.devtools.intellig.configcheck.ClearOutputChecker" />
     </settings>
   </component>
diff --git a/lib/build/easymock.jar b/lib/build/easymock.jar
new file mode 100644
index 0000000..c4159f5
--- /dev/null
+++ b/lib/build/easymock.jar
Binary files differ
diff --git a/lib/build/servlet-api-2.5.jar b/lib/build/servlet-api-2.5.jar
new file mode 100644
index 0000000..fb52493
--- /dev/null
+++ b/lib/build/servlet-api-2.5.jar
Binary files differ
diff --git a/src/com/google/inject/ContainerScoped.java b/src/com/google/inject/ContainerScoped.java
index 6a8e338..7d1408e 100644
--- a/src/com/google/inject/ContainerScoped.java
+++ b/src/com/google/inject/ContainerScoped.java
@@ -22,8 +22,8 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 /**
- * Apply this to annotation classes when you want one instance per container,
- * as opposed to one instance per injection.
+ * Apply this to implementation classes when you want one instance per
+ * container, as opposed to one instance per injection.
  *
  * @author crazybob@google.com (Bob Lee)
  */
diff --git a/src/com/google/inject/servlet/GuiceFilter.java b/src/com/google/inject/servlet/GuiceFilter.java
new file mode 100644
index 0000000..6d880b5
--- /dev/null
+++ b/src/com/google/inject/servlet/GuiceFilter.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (C) 2006 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.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Apply this filter to all requests where you plan to use servlet scopes.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class GuiceFilter implements Filter {
+
+  static ThreadLocal<HttpServletRequest> localRequest =
+      new ThreadLocal<HttpServletRequest>();
+
+  public void doFilter(ServletRequest servletRequest,
+      ServletResponse servletResponse, FilterChain filterChain)
+      throws IOException, ServletException {
+    HttpServletRequest previous = localRequest.get();
+    try {
+      localRequest.set((HttpServletRequest) servletRequest);
+      filterChain.doFilter(servletRequest, servletResponse);
+    } finally {
+      localRequest.set(previous);
+    }
+  }
+
+  static HttpServletRequest getRequest() {
+    HttpServletRequest request = localRequest.get();
+    if (request == null) {
+      throw new RuntimeException("Please apply " + GuiceFilter.class.getName()
+        + " to any request which uses servlet scopes.");
+    }
+    return request;
+  }
+
+  public void init(FilterConfig filterConfig) throws ServletException {}
+  public void destroy() {}
+}
diff --git a/src/com/google/inject/servlet/RequestScoped.java b/src/com/google/inject/servlet/RequestScoped.java
new file mode 100644
index 0000000..c863fe5
--- /dev/null
+++ b/src/com/google/inject/servlet/RequestScoped.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2006 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.Scoped;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Apply this to implementation classes when you want one instance per request.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Scoped(ServletScopes.REQUEST_NAME)
+public @interface RequestScoped {}
diff --git a/src/com/google/inject/servlet/ServletModule.java b/src/com/google/inject/servlet/ServletModule.java
new file mode 100644
index 0000000..c47bc92
--- /dev/null
+++ b/src/com/google/inject/servlet/ServletModule.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2006 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.AbstractModule;
+import static com.google.inject.servlet.ServletScopes.*;
+
+/**
+ * Configures the servlet scopes.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class ServletModule extends AbstractModule {
+
+  protected void configure() {
+    scope(REQUEST_NAME, REQUEST);
+    scope(SESSION_NAME, SESSION);
+  }
+}
diff --git a/src/com/google/inject/servlet/ServletScopes.java b/src/com/google/inject/servlet/ServletScopes.java
new file mode 100644
index 0000000..d8ece57
--- /dev/null
+++ b/src/com/google/inject/servlet/ServletScopes.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (C) 2006 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.Scope;
+import com.google.inject.Factory;
+import com.google.inject.Key;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * Servlet scopes.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class ServletScopes {
+
+  private ServletScopes() {}
+
+  /**
+   * Name of HTTP servlet request scope.
+   */
+  public static final String REQUEST_NAME = "REQUEST";
+
+  /**
+   * HTTP servlet request scope.
+   */
+  public static final Scope REQUEST = new Scope() {
+    public <T> Factory<T> scope(Key<T> key, final Factory<T> creator) {
+      final String name = key.toString();
+      return new Factory<T>() {
+        public T get() {
+          HttpServletRequest request = GuiceFilter.getRequest();
+          synchronized (request) {
+            @SuppressWarnings("unchecked")
+            T t = (T) request.getAttribute(name);
+            if (t == null) {
+              t = creator.get();
+              request.setAttribute(name, t);
+            }
+            return t;
+          }
+        }
+      };
+    }
+  };
+
+  /**
+   * Name of HTTP session scope.
+   */
+  public static final String SESSION_NAME = "SESSION";
+
+  /**
+   * HTTP session scope.
+   */
+  public static final Scope SESSION = new Scope() {
+    public <T> Factory<T> scope(Key<T> key, final Factory<T> creator) {
+      final String name = key.toString();
+      return new Factory<T>() {
+        public T get() {
+          HttpSession session = GuiceFilter.getRequest().getSession();
+          synchronized (session) {
+            @SuppressWarnings("unchecked")
+            T t = (T) session.getAttribute(name);
+            if (t == null) {
+              t = creator.get();
+              session.setAttribute(name, t);
+            }
+            return t;
+          }
+        }
+      };
+    }
+  };
+}
diff --git a/src/com/google/inject/servlet/SessionScoped.java b/src/com/google/inject/servlet/SessionScoped.java
new file mode 100644
index 0000000..2c931e6
--- /dev/null
+++ b/src/com/google/inject/servlet/SessionScoped.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2006 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.Scoped;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Apply this to implementation classes when you want one instance per session.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Scoped(ServletScopes.SESSION_NAME)
+public @interface SessionScoped {}
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index ec1cea9..1071145 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -23,6 +23,7 @@
 import com.google.inject.util.ReferenceCacheTest;
 import com.google.inject.util.ReferenceMapTest;
 import com.google.inject.util.ReferenceMapTestSuite;
+import com.google.inject.servlet.ServletTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -52,6 +53,8 @@
     suite.addTestSuite(ProxyFactoryTest.class);
     suite.addTestSuite(IntegrationTest.class);
 
+    suite.addTestSuite(ServletTest.class);
+
     suite.addTestSuite(FinalizableReferenceQueueTest.class);
     suite.addTestSuite(ReferenceMapTest.class);
     suite.addTest(ReferenceMapTestSuite.suite());
diff --git a/test/com/google/inject/servlet/ServletTest.java b/test/com/google/inject/servlet/ServletTest.java
new file mode 100644
index 0000000..a36a2d2
--- /dev/null
+++ b/test/com/google/inject/servlet/ServletTest.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (C) 2006 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.Container;
+import com.google.inject.ContainerBuilder;
+import com.google.inject.ContainerCreationException;
+import com.google.inject.Key;
+
+import junit.framework.TestCase;
+
+import static org.easymock.EasyMock.*;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class ServletTest extends TestCase {
+
+  public void testNewRequestObject()
+      throws ContainerCreationException, IOException, ServletException {
+    ContainerBuilder builder = new ContainerBuilder();
+    builder.install(new ServletModule());
+    builder.bind(InSession.class);
+    builder.bind(InRequest.class);
+    final Container container = builder.create(false);
+
+    GuiceFilter filter = new GuiceFilter();
+
+    final HttpServletRequest request = createMock(HttpServletRequest.class);
+
+    String requestName = Key.get(InRequest.class).toString();
+    expect(request.getAttribute(requestName)).andReturn(null);
+    request.setAttribute(eq(requestName), 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);
+        assertTrue(container.getInstance(InRequest.class) instanceof InRequest);
+      }
+    };
+
+    replay(request);
+
+    filter.doFilter(request, null, filterChain);
+
+    verify(request);
+    assertTrue(invoked[0]);
+  }
+
+  @SessionScoped
+  static class InSession {}
+
+  @RequestScoped
+  static class InRequest {}
+}