All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.util.RequestScopePropagator Maven / Gradle / Ivy

// 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;
import java.util.concurrent.ScheduledThreadPoolExecutor;

/**
 * Base class for propagating request-scoped data between threads.
 *
 * 

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. * *

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 dbProviderProvider; protected RequestScopePropagator( Scope scope, ThreadLocalRequestContext local, Provider 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 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)}. * *

There are some limitations: * *

    *
  • Derived objects (i.e. anything marked created in a request scope) will not be * transported. *
  • State changes to the request scoped context after this method is called will not be seen * in the continued thread. *
* * @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 Callable wrap(Callable callable) { final RequestContext callerContext = checkNotNull(local.getContext()); final Callable 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. * *

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 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(); } }; } /** @see #wrap(Callable) */ protected abstract Callable wrapImpl(Callable callable); protected Callable context(RequestContext context, Callable callable) { return () -> { RequestContext old = local.setContext( new RequestContext() { @Override public CurrentUser getUser() { return context.getUser(); } @Override public Provider getReviewDbProvider() { return dbProviderProvider.get(); } }); try { return callable.call(); } finally { local.setContext(old); } }; } protected Callable cleanup(Callable callable) { return () -> { RequestCleanup cleanup = scope .scope( Key.get(RequestCleanup.class), new Provider() { @Override public RequestCleanup get() { return new RequestCleanup(); } }) .get(); try { return callable.call(); } finally { cleanup.run(); } }; } }