| // 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 java.util.Objects.requireNonNull; | |
| import com.google.common.base.Throwables; | |
| import com.google.gerrit.entities.Project; | |
| import com.google.gerrit.server.RequestCleanup; | |
| import com.google.gerrit.server.git.ProjectRunnable; | |
| import com.google.inject.Key; | |
| import com.google.inject.Scope; | |
| import com.google.inject.servlet.ServletScopes; | |
| import java.util.concurrent.Callable; | |
| import java.util.concurrent.Executors; | |
| import java.util.concurrent.ScheduledThreadPoolExecutor; | |
| /** | |
| * 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; | |
| protected RequestScopePropagator(Scope scope, ThreadLocalRequestContext local) { | |
| this.scope = scope; | |
| this.local = local; | |
| } | |
| /** | |
| * Ensures that the current request state is available when the passed in Callable is invoked. | |
| * | |
| * <p>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 ScheduledThreadPoolExecutor} 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>State changes to the request scoped context after this method is called will not be seen | |
| * in the continued thread. | |
| * </ul> | |
| * | |
| * @param callable the Callable to wrap. | |
| * @return a new Callable which will execute in the current request scope. | |
| */ | |
| @SuppressWarnings("javadoc") // See GuiceRequestScopePropagator#wrapImpl | |
| public final <T> Callable<T> wrap(Callable<T> callable) { | |
| final RequestContext callerContext = requireNonNull(local.getContext()); | |
| final Callable<T> wrapped = wrapImpl(context(callerContext, cleanup(callable))); | |
| return new Callable<>() { | |
| @Override | |
| public T call() throws Exception { | |
| if (callerContext == local.getContext()) { | |
| return callable.call(); | |
| } | |
| 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. | |
| * | |
| * <p>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(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.throwIfUnchecked(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(); | |
| } | |
| }; | |
| } | |
| 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(); | |
| } | |
| }; | |
| } | |
| /** | |
| * Ensures that the current request state is available when the passed in Callable is invoked | |
| * | |
| * <p>See {@link #wrap(Callable)} | |
| */ | |
| protected abstract <T> Callable<T> wrapImpl(Callable<T> callable); | |
| protected <T> Callable<T> context(RequestContext context, Callable<T> callable) { | |
| return () -> { | |
| RequestContext old = local.setContext(context); | |
| try { | |
| return callable.call(); | |
| } finally { | |
| local.setContext(old); | |
| } | |
| }; | |
| } | |
| protected <T> Callable<T> cleanup(Callable<T> callable) { | |
| return () -> { | |
| RequestCleanup cleanup = | |
| scope.scope(Key.get(RequestCleanup.class), RequestCleanup::new).get(); | |
| try { | |
| return callable.call(); | |
| } finally { | |
| cleanup.run(); | |
| } | |
| }; | |
| } | |
| } |