// Copyright (C) 2012 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.server.util; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Throwables; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.RequestCleanup; import com.google.gerrit.server.config.RequestScopedReviewDbProvider; import com.google.gerrit.server.git.ProjectRunnable; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.Scope; import com.google.inject.servlet.ServletScopes; import java.util.concurrent.Callable; import java.util.concurrent.Executors; /** * Base class for propagating request-scoped data between threads. * <p> * Request scopes are typically linked to a {@link ThreadLocal}, which is only * available to the current thread. In order to allow background work involving * RequestScoped data, the ThreadLocal data must be copied from the request thread to * the new background thread. * <p> * Every type of RequestScope must provide an implementation of * RequestScopePropagator. See {@link #wrap(Callable)} for details on the * implementation, usage, and restrictions. * * @see ThreadLocalRequestScopePropagator */ public abstract class RequestScopePropagator { private final Scope scope; private final ThreadLocalRequestContext local; private final Provider<RequestScopedReviewDbProvider> dbProviderProvider; protected RequestScopePropagator(Scope scope, ThreadLocalRequestContext local, Provider<RequestScopedReviewDbProvider> dbProviderProvider) { this.scope = scope; this.local = local; this.dbProviderProvider = dbProviderProvider; } /** * Ensures that the current request state is available when the passed in * Callable is invoked. * * If needed wraps the passed in Callable in a new {@link Callable} that * propagates the current request state when the returned Callable is invoked. * The method must be called in a request scope and the returned Callable may * only be invoked in a thread that is not already in a request scope or is in * the same request scope. The returned Callable will inherit toString() from * the passed in Callable. A * {@link com.google.gerrit.server.git.WorkQueue.Executor} does not accept a * Callable, so there is no ProjectCallable implementation. Implementations of * this method must be consistent with Guice's * {@link ServletScopes#continueRequest(Callable, java.util.Map)}. * <p> * There are some limitations: * <ul> * <li>Derived objects (i.e. anything marked created in a request scope) will * not be transported.</li> * <li>State changes to the request scoped context after this method is called * will not be seen in the continued thread.</li> * </ul> * * @param callable the Callable to wrap. * @return a new Callable which will execute in the current request scope. */ public final <T> Callable<T> wrap(final Callable<T> callable) { final RequestContext callerContext = checkNotNull(local.getContext()); final Callable<T> wrapped = wrapImpl(context(callerContext, cleanup(callable))); return new Callable<T>() { @Override public T call() throws Exception { if (callerContext == local.getContext()) { return callable.call(); } else { return wrapped.call(); } } @Override public String toString() { return callable.toString(); } }; } /** * Wraps runnable in a new {@link Runnable} that propagates the current * request state when the runnable is invoked. The method must be called in a * request scope and the returned Runnable may only be invoked in a thread * that is not already in a request scope. The returned Runnable will inherit * toString() from the passed in Runnable. Furthermore, if the passed runnable * is of type {@link ProjectRunnable}, the returned runnable will be of the * same type with the methods delegated. * * See {@link #wrap(Callable)} for details on implementation and usage. * * @param runnable the Runnable to wrap. * @return a new Runnable which will execute in the current request scope. */ public final Runnable wrap(final Runnable runnable) { final Callable<Object> wrapped = wrap(Executors.callable(runnable)); if (runnable instanceof ProjectRunnable) { return new ProjectRunnable() { @Override public void run() { try { wrapped.call(); } catch (Exception e) { Throwables.propagateIfPossible(e); throw new RuntimeException(e); // Not possible. } } @Override public Project.NameKey getProjectNameKey() { return ((ProjectRunnable) runnable).getProjectNameKey(); } @Override public String getRemoteName() { return ((ProjectRunnable) runnable).getRemoteName(); } @Override public boolean hasCustomizedPrint() { return ((ProjectRunnable) runnable).hasCustomizedPrint(); } @Override public String toString() { return runnable.toString(); } }; } else { return new Runnable() { @Override public void run() { try { wrapped.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); // Not possible. } } @Override public String toString() { return runnable.toString(); } }; } } /** * @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(new RequestContext() { @Override public CurrentUser getCurrentUser() { return context.getCurrentUser(); } @Override public Provider<ReviewDb> getReviewDbProvider() { return dbProviderProvider.get(); } }); 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(); } } }; } }