blob: d1cf47c23a328af170c51e49868aecb88c1b602a [file] [log] [blame]
// 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 getUser() {
return context.getUser();
}
@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();
}
}
};
}
}