Update the ThreadLocal based scopes to use RequestContext.

Bound the ThreadLocalRequestContext module, which makes the CurrentUser
available in the Global module. Removed any binding in the scopes that
provided the CurrentUser. Updated all of the scopes to propagate the
RequestContext. The PerThreadRequestScope.Propagator allows scoping
callables to enter a request context.

Change-Id: Idf682ed1d7485cf8c9cdd22cd89bfe1ad5296880
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpIdentifiedUserProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpIdentifiedUserProvider.java
deleted file mode 100644
index 6c420a5..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpIdentifiedUserProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// 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.gerrit.httpd;
-
-import com.google.gerrit.common.errors.NotSignedInException;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-
-class HttpIdentifiedUserProvider implements Provider<IdentifiedUser> {
-  private final Provider<CurrentUser> currUserProvider;
-
-  @Inject
-  HttpIdentifiedUserProvider(Provider<CurrentUser> currUserProvider) {
-    this.currUserProvider = currUserProvider;
-  }
-
-  @Override
-  public IdentifiedUser get() {
-    CurrentUser user = currUserProvider.get();
-    if (user instanceof IdentifiedUser) {
-      return (IdentifiedUser) user;
-    }
-    throw new ProvisionException(NotSignedInException.MESSAGE,
-        new NotSignedInException());
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpCurrentUserProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
similarity index 81%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpCurrentUserProvider.java
rename to gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
index c87a143..4c88240 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpCurrentUserProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpRequestContext.java
@@ -15,19 +15,20 @@
 package com.google.gerrit.httpd;
 
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.util.RequestContext;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-class HttpCurrentUserProvider implements Provider<CurrentUser> {
+class HttpRequestContext implements RequestContext {
   private final WebSession session;
 
   @Inject
-  HttpCurrentUserProvider(final WebSession session) {
+  HttpRequestContext(final WebSession session) {
     this.session = session;
   }
 
   @Override
-  public CurrentUser get() {
+  public CurrentUser getCurrentUser() {
     return session.getCurrentUser();
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestCleanupFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestContextFilter.java
similarity index 61%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestCleanupFilter.java
rename to gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestContextFilter.java
index 0e6a567..b46505f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestCleanupFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RequestContextFilter.java
@@ -15,9 +15,13 @@
 package com.google.gerrit.httpd;
 
 import com.google.gerrit.server.RequestCleanup;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.inject.Inject;
+import com.google.inject.Module;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import com.google.inject.servlet.ServletModule;
 
 import java.io.IOException;
 
@@ -30,12 +34,27 @@
 
 /** Executes any pending {@link RequestCleanup} at the end of a request. */
 @Singleton
-class RequestCleanupFilter implements Filter {
+public class RequestContextFilter implements Filter {
+  public static Module module() {
+    return new ServletModule() {
+      @Override
+      protected void configureServlets() {
+        filter("/*").through(RequestContextFilter.class);
+      }
+    };
+  }
+
   private final Provider<RequestCleanup> cleanup;
+  private final Provider<HttpRequestContext> requestContext;
+  private final ThreadLocalRequestContext local;
 
   @Inject
-  RequestCleanupFilter(final Provider<RequestCleanup> r) {
+  RequestContextFilter(final Provider<RequestCleanup> r,
+      final Provider<HttpRequestContext> c,
+      final ThreadLocalRequestContext l) {
     cleanup = r;
+    requestContext = c;
+    local = l;
   }
 
   @Override
@@ -50,10 +69,15 @@
   public void doFilter(final ServletRequest request,
       final ServletResponse response, final FilterChain chain)
       throws IOException, ServletException {
+    RequestContext old = local.setContext(requestContext.get());
     try {
-      chain.doFilter(request, response);
+      try {
+        chain.doFilter(request, response);
+      } finally {
+        cleanup.get().run();
+      }
     } finally {
-      cleanup.get().run();
+      local.setContext(old);
     }
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index d198b93..852caae 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -25,8 +25,6 @@
 import com.google.gerrit.httpd.gitweb.GitWebModule;
 import com.google.gerrit.httpd.rpc.UiRpcModule;
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.ChangeUserName;
@@ -78,13 +76,8 @@
 
   @Override
   protected void configure() {
-    install(new ServletModule() {
-      @Override
-      protected void configureServlets() {
-        filter("/*").through(RequestCleanupFilter.class);
-      }
-    });
     bind(RequestScopePropagator.class).to(GuiceRequestScopePropagator.class);
+    bind(HttpRequestContext.class);
 
     if (wantSSL) {
       install(new RequireSslFilter.Module());
@@ -147,9 +140,6 @@
     bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
         HttpRemotePeerProvider.class).in(RequestScoped.class);
 
-    bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class);
-    bind(IdentifiedUser.class).toProvider(HttpIdentifiedUserProvider.class);
-
     install(new LifecycleModule() {
       @Override
       protected void configure() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 202d55b..c05948d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.httpd.CacheBasedWebSession;
 import com.google.gerrit.httpd.GitOverHttpModule;
 import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
+import com.google.gerrit.httpd.RequestContextFilter;
 import com.google.gerrit.httpd.WebModule;
 import com.google.gerrit.httpd.WebSshGlueModule;
 import com.google.gerrit.httpd.auth.openid.OpenIdModule;
@@ -271,6 +272,7 @@
 
   private Injector createWebInjector() {
     final List<Module> modules = new ArrayList<Module>();
+    modules.add(RequestContextFilter.module());
     modules.add(CacheBasedWebSession.module());
     modules.add(HttpContactStoreConnection.module());
     modules.add(sysInjector.getInstance(GitOverHttpModule.class));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index b46a819..2875920 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.rules.PrologModule;
 import com.google.gerrit.rules.RulesCache;
+import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.FileTypeRegistry;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.InternalUser;
@@ -64,8 +65,10 @@
 import com.google.gerrit.server.project.SectionSortCache;
 import com.google.gerrit.server.tools.ToolsCatalog;
 import com.google.gerrit.server.util.IdGenerator;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.workflow.FunctionState;
 import com.google.inject.Inject;
+import com.google.inject.servlet.RequestScoped;
 
 import org.apache.velocity.runtime.RuntimeInstance;
 import org.eclipse.jgit.lib.Config;
@@ -117,6 +120,7 @@
     install(new AccessControlModule());
     install(new GitModule());
     install(new PrologModule());
+    install(ThreadLocalRequestContext.module());
 
     factory(AccountInfoCacheFactory.Factory.class);
     factory(CapabilityControl.Factory.class);
@@ -154,5 +158,7 @@
     bind(GitReferenceUpdated.class);
     DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
     DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
+
+    bind(AnonymousUser.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index f581adc..5d6ecc3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -77,7 +77,6 @@
     bind(ListProjects.class);
     bind(ApprovalsUtil.class);
 
-    bind(AnonymousUser.class).in(RequestScoped.class);
     bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
     bind(ChangeControl.Factory.class).in(SINGLETON);
     bind(GroupControl.Factory.class).in(SINGLETON);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
index 86e0740..6d1b155 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
@@ -20,16 +20,17 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.config.GerritRequestModule;
 import com.google.gerrit.server.ssh.SshInfo;
+import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.RequestScopePropagator;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
+import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import com.google.inject.servlet.RequestScoped;
 
@@ -43,6 +44,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 
 @Singleton
@@ -57,6 +59,7 @@
 
   private final WorkQueue workQueue;
   private final Provider<MergeOp.Factory> bgFactory;
+  private final PerThreadRequestScope.Scoper threadScoper;
 
   @Inject
   ChangeMergeQueue(final WorkQueue wq, Injector parent) {
@@ -68,15 +71,9 @@
         bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
         bind(RequestScopePropagator.class)
             .to(PerThreadRequestScope.Propagator.class);
+        bind(PerThreadRequestScope.Propagator.class);
         install(new GerritRequestModule());
 
-        bind(CurrentUser.class).to(IdentifiedUser.class);
-        bind(IdentifiedUser.class).toProvider(new Provider<IdentifiedUser>() {
-          @Override
-          public IdentifiedUser get() {
-            throw new OutOfScopeException("No user on merge thread");
-          }
-        });
         bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
             new Provider<SocketAddress>() {
               @Override
@@ -91,8 +88,26 @@
           }
         });
       }
+
+      @Provides
+      public PerThreadRequestScope.Scoper provideScoper(
+          final PerThreadRequestScope.Propagator propagator) {
+        final RequestContext requestContext = new RequestContext() {
+          @Override
+          public CurrentUser getCurrentUser() {
+            throw new OutOfScopeException("No user on merge thread");
+          }
+        };
+        return new PerThreadRequestScope.Scoper() {
+          @Override
+          public <T> Callable<T> scope(Callable<T> callable) {
+            return propagator.scope(requestContext, callable);
+          }
+        };
+      }
     });
     bgFactory = child.getProvider(MergeOp.Factory.class);
+    threadScoper = child.getInstance(PerThreadRequestScope.Scoper.class);
   }
 
   @Override
@@ -186,19 +201,15 @@
     }
   }
 
-  private void mergeImpl(Branch.NameKey branch) {
+  private void mergeImpl(final Branch.NameKey branch) {
     try {
-      PerThreadRequestScope ctx = new PerThreadRequestScope();
-      PerThreadRequestScope old = PerThreadRequestScope.set(ctx);
-      try {
-        try {
+      threadScoper.scope(new Callable<Void>(){
+        @Override
+        public Void call() throws Exception {
           bgFactory.get().create(branch).merge();
-        } finally {
-          ctx.cleanup.run();
+          return null;
         }
-      } finally {
-        PerThreadRequestScope.set(old);
-      }
+      }).call();
     } catch (Throwable e) {
       log.error("Merge attempt for " + branch + " failed", e);
     } finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java
index 6b92ff0..8aea73a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java
@@ -14,50 +14,86 @@
 
 package com.google.gerrit.server.git;
 
-import com.google.gerrit.server.RequestCleanup;
+import com.google.common.collect.Maps;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
+import com.google.inject.Inject;
 import com.google.inject.Key;
 import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
 import com.google.inject.Scope;
 
-import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 public class PerThreadRequestScope {
-  static class Propagator
-      extends ThreadLocalRequestScopePropagator<PerThreadRequestScope> {
-    Propagator() {
-      super(REQUEST, current);
+  public interface Scoper {
+    <T> Callable<T> scope(Callable<T> callable);
+  }
+
+  private static class Context {
+    private final Map<Key<?>, Object> map;
+
+    private Context() {
+      map = Maps.newHashMap();
     }
 
-    @Override
-    protected PerThreadRequestScope continuingContext(
-        PerThreadRequestScope ctx) {
-      return new PerThreadRequestScope();
+    private <T> T get(Key<T> key, Provider<T> creator) {
+      @SuppressWarnings("unchecked")
+      T t = (T) map.get(key);
+      if (t == null) {
+        t = creator.get();
+        map.put(key, t);
+      }
+      return t;
     }
   }
 
-  private static final ThreadLocal<PerThreadRequestScope> current =
-      new ThreadLocal<PerThreadRequestScope>();
+  public static class Propagator extends ThreadLocalRequestScopePropagator<Context> {
+    @Inject
+    Propagator(ThreadLocalRequestContext local) {
+      super(REQUEST, current, local);
+    }
 
-  private static PerThreadRequestScope requireContext() {
-    final PerThreadRequestScope ctx = current.get();
+    @Override
+    protected Context continuingContext(Context ctx) {
+      return new Context();
+    }
+
+    public <T> Callable<T> scope(RequestContext requestContext, Callable<T> callable) {
+      final Context ctx = new Context();
+      final Callable<T> wrapped = context(requestContext, cleanup(callable));
+      return new Callable<T>() {
+        @Override
+        public T call() throws Exception {
+          Context old = current.get();
+          current.set(ctx);
+          try {
+            return wrapped.call();
+          } finally {
+            current.set(old);
+          }
+        }
+      };
+    }
+  }
+
+  private static final ThreadLocal<Context> current = new ThreadLocal<Context>();
+
+  private static Context requireContext() {
+    final Context ctx = current.get();
     if (ctx == null) {
       throw new OutOfScopeException("Not in command/request");
     }
     return ctx;
   }
 
-  public static PerThreadRequestScope set(PerThreadRequestScope ctx) {
-    PerThreadRequestScope old = current.get();
-    current.set(ctx);
-    return old;
-  }
-
   public static final Scope REQUEST = new Scope() {
+    @Override
     public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
       return new Provider<T>() {
+        @Override
         public T get() {
           return requireContext().get(key, creator);
         }
@@ -74,26 +110,4 @@
       return "PerThreadRequestScope.REQUEST";
     }
   };
-
-  private static final Key<RequestCleanup> RC_KEY =
-      Key.get(RequestCleanup.class);
-
-  final RequestCleanup cleanup;
-  private final Map<Key<?>, Object> map;
-
-  public PerThreadRequestScope() {
-    cleanup = new RequestCleanup();
-    map = new HashMap<Key<?>, Object>();
-    map.put(RC_KEY, cleanup);
-  }
-
-  synchronized <T> T get(Key<T> key, Provider<T> creator) {
-    @SuppressWarnings("unchecked")
-    T t = (T) map.get(key);
-    if (t == null) {
-      t = creator.get();
-      map.put(key, t);
-    }
-    return t;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
index 9befc7d..cc3c2f7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
@@ -38,17 +38,15 @@
 
   private final String url;
   private final SocketAddress peer;
-  private final CurrentUser user;
 
   @Inject
   GuiceRequestScopePropagator(
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       @RemotePeer Provider<SocketAddress> remotePeerProvider,
-      Provider<CurrentUser> currentUserProvider) {
-    super(ServletScopes.REQUEST);
+      ThreadLocalRequestContext local) {
+    super(ServletScopes.REQUEST, local);
     this.url = urlProvider != null ? urlProvider.get() : null;
     this.peer = remotePeerProvider.get();
-    this.user = currentUserProvider.get();
   }
 
   /**
@@ -69,9 +67,6 @@
         Providers.of(peer));
     seedMap.put(Key.get(SocketAddress.class, RemotePeer.class), peer);
 
-    seedMap.put(Key.get(typeOfProvider(CurrentUser.class)), Providers.of(user));
-    seedMap.put(Key.get(CurrentUser.class), user);
-
     return ServletScopes.continueRequest(callable, seedMap);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java
index 3661aa2..84c61e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RequestScopePropagator.java
@@ -42,9 +42,12 @@
 public abstract class RequestScopePropagator {
 
   private final Scope scope;
+  private final ThreadLocalRequestContext local;
 
-  protected RequestScopePropagator(Scope scope) {
+  protected RequestScopePropagator(Scope scope,
+      ThreadLocalRequestContext local) {
     this.scope = scope;
+    this.local = local;
   }
 
   /**
@@ -70,26 +73,8 @@
    * @return a new Callable which will execute in the current request scope.
    */
   public final <T> Callable<T> wrap(final Callable<T> callable) {
-    final Callable<T> wrapped = wrapImpl(new Callable<T>() {
-      @Override
-      public T call() throws Exception {
-        RequestCleanup cleanup = scope.scope(
-            Key.get(RequestCleanup.class),
-            new Provider<RequestCleanup>() {
-              @Override
-              public RequestCleanup get() {
-                return new RequestCleanup();
-              }
-            }).get();
-
-        try {
-          return callable.call();
-        } finally {
-          cleanup.run();
-        }
-      }
-    });
-
+    final Callable<T> wrapped =
+        wrapImpl(context(local.getContext(), cleanup(callable)));
     return new Callable<T>() {
       @Override
       public T call() throws Exception {
@@ -178,4 +163,41 @@
    * @see #wrap(Callable)
    */
   protected abstract <T> Callable<T> wrapImpl(final Callable<T> callable);
+
+  protected <T> Callable<T> context(final RequestContext context,
+      final Callable<T> callable) {
+    return new Callable<T>() {
+      @Override
+      public T call() throws Exception {
+        RequestContext old = local.setContext(context);
+        try {
+          return callable.call();
+        } finally {
+          local.setContext(old);
+        }
+      }
+    };
+  }
+
+  protected <T> Callable<T> cleanup(final Callable<T> callable) {
+    return new Callable<T>() {
+      @Override
+      public T call() throws Exception {
+        RequestCleanup cleanup = scope.scope(
+            Key.get(RequestCleanup.class),
+            new Provider<RequestCleanup>() {
+              @Override
+              public RequestCleanup get() {
+                return new RequestCleanup();
+              }
+            }).get();
+
+        try {
+          return callable.call();
+        } finally {
+          cleanup.run();
+        }
+      }
+    };
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
index 581ccc1..e465247 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
@@ -31,8 +31,8 @@
   private final ThreadLocal<C> threadLocal;
 
   protected ThreadLocalRequestScopePropagator(Scope scope,
-      ThreadLocal<C> threadLocal) {
-    super(scope);
+      ThreadLocal<C> threadLocal, ThreadLocalRequestContext local) {
+    super(scope, local);
     this.threadLocal = threadLocal;
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index 8eaa829..be6659d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -122,7 +122,7 @@
 
     public void setSession(final ServerSession session) {
       final SshSession s = session.getAttribute(SshSession.KEY);
-      this.ctx = new Context(s, commandLine);
+      this.ctx = sshScope.newContext(s, commandLine);
     }
 
     public void start(final Environment env) throws IOException {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 0b31a65..a69a2f1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -173,7 +173,7 @@
       // session, record a login event in the log and add
       // a close listener to record a logout event.
       //
-      Context ctx = new Context(sd, null);
+      Context ctx = sshScope.newContext(sd, null);
       Context old = sshScope.set(ctx);
       try {
         sshLog.onLogin();
@@ -185,7 +185,7 @@
           new IoFutureListener<IoFuture>() {
             @Override
             public void operationComplete(IoFuture future) {
-              final Context ctx = new Context(sd, null);
+              final Context ctx = sshScope.newContext(sd, null);
               final Context old = sshScope.set(ctx);
               try {
                 sshLog.onLogout();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
index 5ea2f6b..d2fdf78 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
@@ -89,7 +89,7 @@
     }
 
     public void setSession(final ServerSession session) {
-      this.context = new Context(session.getAttribute(SshSession.KEY), "");
+      this.context = sshScope.newContext(session.getAttribute(SshSession.KEY), "");
     }
 
     public void start(final Environment env) throws IOException {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
deleted file mode 100644
index 5839cf1..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshCurrentUserProvider.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// 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.gerrit.sshd;
-
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-
-@Singleton
-class SshCurrentUserProvider implements Provider<CurrentUser> {
-  private final Provider<SshSession> session;
-  private final Provider<IdentifiedUser> identifiedProvider;
-
-  @Inject
-  SshCurrentUserProvider(Provider<SshSession> s, Provider<IdentifiedUser> p) {
-    session = s;
-    identifiedProvider = p;
-  }
-
-  @Override
-  public CurrentUser get() {
-    final CurrentUser user = session.get().getCurrentUser();
-    if (user instanceof IdentifiedUser) {
-      return identifiedProvider.get();
-    }
-    return session.get().getCurrentUser();
-  }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java
deleted file mode 100644
index 516b59c..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshIdentifiedUserProvider.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// 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.gerrit.sshd;
-
-import com.google.gerrit.common.errors.NotSignedInException;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
-
-@Singleton
-class SshIdentifiedUserProvider implements Provider<IdentifiedUser> {
-  private final Provider<SshSession> session;
-  private final IdentifiedUser.RequestFactory factory;
-
-  @Inject
-  SshIdentifiedUserProvider(Provider<SshSession> s,
-      IdentifiedUser.RequestFactory f) {
-    session = s;
-    factory = f;
-  }
-
-  @Override
-  public IdentifiedUser get() {
-    final CurrentUser user = session.get().getCurrentUser();
-    if (user instanceof IdentifiedUser) {
-      return factory.create(user.getAccessPath(), //
-          ((IdentifiedUser) user).getAccountId());
-    }
-    throw new ProvisionException(NotSignedInException.MESSAGE,
-        new NotSignedInException());
-  }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 72fe6b0..bcb1d9f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -152,11 +152,6 @@
     bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
         SshRemotePeerProvider.class).in(SshScope.REQUEST);
 
-    bind(CurrentUser.class).toProvider(SshCurrentUserProvider.class).in(
-        SshScope.REQUEST);
-    bind(IdentifiedUser.class).toProvider(SshIdentifiedUserProvider.class).in(
-        SshScope.REQUEST);
-
     bind(WorkQueue.Executor.class).annotatedWith(CommandExecutor.class)
         .toProvider(CommandExecutorProvider.class).in(SshScope.REQUEST);
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
index 8cd501e..d6f66ca 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
@@ -14,8 +14,13 @@
 
 package com.google.gerrit.sshd;
 
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.RequestCleanup;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
+import com.google.inject.Inject;
 import com.google.inject.Key;
 import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
@@ -26,10 +31,10 @@
 
 /** Guice scopes for state during an SSH connection. */
 class SshScope {
-  static class Context {
-    private static final Key<RequestCleanup> RC_KEY =
-        Key.get(RequestCleanup.class);
+  private static final Key<RequestCleanup> RC_KEY =
+      Key.get(RequestCleanup.class);
 
+  class Context implements RequestContext {
     private final RequestCleanup cleanup;
     private final SshSession session;
     private final String commandLine;
@@ -56,10 +61,6 @@
       finished = p.finished;
     }
 
-    Context(final SshSession s, final String c) {
-      this(s, c, System.currentTimeMillis());
-    }
-
     String getCommandLine() {
       return commandLine;
     }
@@ -68,6 +69,16 @@
       return session;
     }
 
+    @Override
+    public CurrentUser getCurrentUser() {
+      final CurrentUser user = session.getCurrentUser();
+      if (user instanceof IdentifiedUser) {
+        return userFactory.create(user.getAccessPath(), //
+            ((IdentifiedUser) user).getAccountId());
+      }
+      return user;
+    }
+
     synchronized <T> T get(Key<T> key, Provider<T> creator) {
       @SuppressWarnings("unchecked")
       T t = (T) map.get(key);
@@ -100,15 +111,19 @@
   }
 
   static class Propagator extends ThreadLocalRequestScopePropagator<Context> {
-    Propagator() {
-      super(REQUEST, current);
+    private final SshScope sshScope;
+
+    @Inject
+    Propagator(SshScope sshScope, ThreadLocalRequestContext local) {
+      super(REQUEST, current, local);
+      this.sshScope = sshScope;
     }
 
     @Override
     protected Context continuingContext(Context ctx) {
       // The cleanup is not chained, since the RequestScopePropagator executors
       // the Context's cleanup when finished executing.
-      return new Context(ctx, ctx.getSession(), ctx.getCommandLine());
+      return sshScope.newContinuingContext(ctx);
     }
   }
 
@@ -123,9 +138,28 @@
     return ctx;
   }
 
+  private final ThreadLocalRequestContext local;
+  private final IdentifiedUser.RequestFactory userFactory;
+
+  @Inject
+  SshScope(ThreadLocalRequestContext local,
+      IdentifiedUser.RequestFactory userFactory) {
+    this.local = local;
+    this.userFactory = userFactory;
+  }
+
+  Context newContext(SshSession session, String commandLine) {
+    return new Context(session, commandLine, System.currentTimeMillis());
+  }
+
+  private Context newContinuingContext(Context ctx) {
+    return new Context(ctx, ctx.getSession(), ctx.getCommandLine());
+  }
+
   Context set(Context ctx) {
     Context old = current.get();
     current.set(ctx);
+    local.setContext(ctx);
     return old;
   }
 
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 0bb24cc..58d6116 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -223,6 +223,7 @@
 
   private Injector createWebInjector() {
     final List<Module> modules = new ArrayList<Module>();
+    modules.add(RequestContextFilter.module());
     modules.add(sysInjector.getInstance(GitOverHttpModule.class));
     modules.add(sshInjector.getInstance(WebModule.class));
     modules.add(sshInjector.getInstance(WebSshGlueModule.class));