summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/acceptance/InProcessProtocol.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/acceptance/InProcessProtocol.java')
-rw-r--r--java/com/google/gerrit/acceptance/InProcessProtocol.java358
1 files changed, 358 insertions, 0 deletions
diff --git a/java/com/google/gerrit/acceptance/InProcessProtocol.java b/java/com/google/gerrit/acceptance/InProcessProtocol.java
new file mode 100644
index 0000000000..de8d10ccf3
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -0,0 +1,358 @@
+// Copyright (C) 2015 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.acceptance;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.InProcessProtocol.Context;
+import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.RemotePeer;
+import com.google.gerrit.server.RequestCleanup;
+import com.google.gerrit.server.config.GerritRequestModule;
+import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
+import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
+import com.google.gerrit.server.git.ReceivePackInitializer;
+import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.git.UploadPackInitializer;
+import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
+import com.google.gerrit.server.git.validators.UploadValidators;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.RequestScopePropagator;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.Provides;
+import com.google.inject.Scope;
+import com.google.inject.servlet.RequestScoped;
+import com.google.inject.util.Providers;
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PostReceiveHook;
+import org.eclipse.jgit.transport.PostReceiveHookChain;
+import org.eclipse.jgit.transport.PreUploadHook;
+import org.eclipse.jgit.transport.PreUploadHookChain;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.TestProtocol;
+import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+
+class InProcessProtocol extends TestProtocol<Context> {
+ static Module module() {
+ return new AbstractModule() {
+ @Override
+ public void configure() {
+ install(new GerritRequestModule());
+ bind(RequestScopePropagator.class).to(Propagator.class);
+ bindScope(RequestScoped.class, InProcessProtocol.REQUEST);
+ }
+
+ @Provides
+ @RemotePeer
+ SocketAddress getSocketAddress() {
+ // TODO(dborowitz): Could potentially fake this with thread ID or
+ // something.
+ throw new OutOfScopeException("No remote peer in acceptance tests");
+ }
+ };
+ }
+
+ private static final Scope REQUEST =
+ new Scope() {
+ @Override
+ public <T> Provider<T> scope(Key<T> key, Provider<T> creator) {
+ return new Provider<T>() {
+ @Override
+ public T get() {
+ Context ctx = current.get();
+ if (ctx == null) {
+ throw new OutOfScopeException("Not in TestProtocol scope");
+ }
+ return ctx.get(key, creator);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[%s]", creator, REQUEST);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "InProcessProtocol.REQUEST";
+ }
+ };
+
+ private static class Propagator extends ThreadLocalRequestScopePropagator<Context> {
+ @Inject
+ Propagator(
+ ThreadLocalRequestContext local,
+ Provider<RequestScopedReviewDbProvider> dbProviderProvider) {
+ super(REQUEST, current, local, dbProviderProvider);
+ }
+
+ @Override
+ protected Context continuingContext(Context ctx) {
+ return ctx.newContinuingContext();
+ }
+ }
+
+ private static final ThreadLocal<Context> current = new ThreadLocal<>();
+
+ // TODO(dborowitz): Merge this with AcceptanceTestRequestScope.
+ /**
+ * Multi-purpose session/context object.
+ *
+ * <p>Confusingly, Gerrit has two ideas of what a "context" object is: one for Guice {@link
+ * RequestScoped}, and one for its own simplified version of request scoping using {@link
+ * ThreadLocalRequestContext}. This class provides both, in essence just delegating the {@code
+ * ThreadLocalRequestContext} scoping to the Guice scoping mechanism.
+ *
+ * <p>It is also used as the session type for {@code UploadPackFactory} and {@code
+ * ReceivePackFactory}, since, after all, it encapsulates all the information about a single
+ * request.
+ */
+ static class Context implements RequestContext {
+ private static final Key<RequestScopedReviewDbProvider> DB_KEY =
+ Key.get(RequestScopedReviewDbProvider.class);
+ private static final Key<RequestCleanup> RC_KEY = Key.get(RequestCleanup.class);
+ private static final Key<CurrentUser> USER_KEY = Key.get(CurrentUser.class);
+
+ private final SchemaFactory<ReviewDb> schemaFactory;
+ private final IdentifiedUser.GenericFactory userFactory;
+ private final Account.Id accountId;
+ private final Project.NameKey project;
+ private final RequestCleanup cleanup;
+ private final Map<Key<?>, Object> map;
+
+ Context(
+ SchemaFactory<ReviewDb> schemaFactory,
+ IdentifiedUser.GenericFactory userFactory,
+ Account.Id accountId,
+ Project.NameKey project) {
+ this.schemaFactory = schemaFactory;
+ this.userFactory = userFactory;
+ this.accountId = accountId;
+ this.project = project;
+ map = new HashMap<>();
+ cleanup = new RequestCleanup();
+ map.put(DB_KEY, new RequestScopedReviewDbProvider(schemaFactory, Providers.of(cleanup)));
+ map.put(RC_KEY, cleanup);
+
+ IdentifiedUser user = userFactory.create(accountId);
+ user.setAccessPath(AccessPath.GIT);
+ map.put(USER_KEY, user);
+ }
+
+ private Context newContinuingContext() {
+ return new Context(schemaFactory, userFactory, accountId, project);
+ }
+
+ @Override
+ public CurrentUser getUser() {
+ return get(USER_KEY, null);
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return get(DB_KEY, null);
+ }
+
+ private 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;
+ }
+ }
+
+ private static class Upload implements UploadPackFactory<Context> {
+ private final TransferConfig transferConfig;
+ private final DynamicSet<UploadPackInitializer> uploadPackInitializers;
+ private final DynamicSet<PreUploadHook> preUploadHooks;
+ private final UploadValidators.Factory uploadValidatorsFactory;
+ private final ThreadLocalRequestContext threadContext;
+ private final ProjectCache projectCache;
+ private final PermissionBackend permissionBackend;
+
+ @Inject
+ Upload(
+ TransferConfig transferConfig,
+ DynamicSet<UploadPackInitializer> uploadPackInitializers,
+ DynamicSet<PreUploadHook> preUploadHooks,
+ UploadValidators.Factory uploadValidatorsFactory,
+ ThreadLocalRequestContext threadContext,
+ ProjectCache projectCache,
+ PermissionBackend permissionBackend) {
+ this.transferConfig = transferConfig;
+ this.uploadPackInitializers = uploadPackInitializers;
+ this.preUploadHooks = preUploadHooks;
+ this.uploadValidatorsFactory = uploadValidatorsFactory;
+ this.threadContext = threadContext;
+ this.projectCache = projectCache;
+ this.permissionBackend = permissionBackend;
+ }
+
+ @Override
+ public UploadPack create(Context req, Repository repo) throws ServiceNotAuthorizedException {
+ // Set the request context, but don't bother unsetting, since we don't
+ // have an easy way to run code when this instance is done being used.
+ // Each operation is run in its own thread, so we don't need to recover
+ // its original context anyway.
+ threadContext.setContext(req);
+ current.set(req);
+
+ PermissionBackend.ForProject perm = permissionBackend.currentUser().project(req.project);
+ try {
+ perm.check(ProjectPermission.RUN_UPLOAD_PACK);
+ } catch (AuthException e) {
+ throw new ServiceNotAuthorizedException();
+ } catch (PermissionBackendException e) {
+ throw new RuntimeException(e);
+ }
+
+ ProjectState projectState;
+ try {
+ projectState = projectCache.checkedGet(req.project);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ if (projectState == null) {
+ throw new RuntimeException("can't load project state for " + req.project.get());
+ }
+ UploadPack up = new UploadPack(repo);
+ up.setPackConfig(transferConfig.getPackConfig());
+ up.setTimeout(transferConfig.getTimeout());
+ up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
+ List<PreUploadHook> hooks = Lists.newArrayList(preUploadHooks);
+ hooks.add(uploadValidatorsFactory.create(projectState.getProject(), repo, "localhost-test"));
+ up.setPreUploadHook(PreUploadHookChain.newChain(hooks));
+ for (UploadPackInitializer initializer : uploadPackInitializers) {
+ initializer.init(req.project, up);
+ }
+ return up;
+ }
+ }
+
+ private static class Receive implements ReceivePackFactory<Context> {
+ private final Provider<CurrentUser> userProvider;
+ private final ProjectCache projectCache;
+ private final AsyncReceiveCommits.Factory factory;
+ private final TransferConfig config;
+ private final DynamicSet<ReceivePackInitializer> receivePackInitializers;
+ private final DynamicSet<PostReceiveHook> postReceiveHooks;
+ private final ThreadLocalRequestContext threadContext;
+ private final PermissionBackend permissionBackend;
+
+ @Inject
+ Receive(
+ Provider<CurrentUser> userProvider,
+ ProjectCache projectCache,
+ AsyncReceiveCommits.Factory factory,
+ TransferConfig config,
+ DynamicSet<ReceivePackInitializer> receivePackInitializers,
+ DynamicSet<PostReceiveHook> postReceiveHooks,
+ ThreadLocalRequestContext threadContext,
+ PermissionBackend permissionBackend) {
+ this.userProvider = userProvider;
+ this.projectCache = projectCache;
+ this.factory = factory;
+ this.config = config;
+ this.receivePackInitializers = receivePackInitializers;
+ this.postReceiveHooks = postReceiveHooks;
+ this.threadContext = threadContext;
+ this.permissionBackend = permissionBackend;
+ }
+
+ @Override
+ public ReceivePack create(Context req, Repository db) throws ServiceNotAuthorizedException {
+ // Set the request context, but don't bother unsetting, since we don't
+ // have an easy way to run code when this instance is done being used.
+ // Each operation is run in its own thread, so we don't need to recover
+ // its original context anyway.
+ threadContext.setContext(req);
+ current.set(req);
+ try {
+ permissionBackend
+ .currentUser()
+ .project(req.project)
+ .check(ProjectPermission.RUN_RECEIVE_PACK);
+ } catch (AuthException e) {
+ throw new ServiceNotAuthorizedException();
+ } catch (PermissionBackendException e) {
+ throw new RuntimeException(e);
+ }
+ try {
+ IdentifiedUser identifiedUser = userProvider.get().asIdentifiedUser();
+ ProjectState projectState = projectCache.checkedGet(req.project);
+ if (projectState == null) {
+ throw new RuntimeException(String.format("project %s not found", req.project));
+ }
+
+ AsyncReceiveCommits arc = factory.create(projectState, identifiedUser, db, null);
+ if (arc.canUpload() != Capable.OK) {
+ throw new ServiceNotAuthorizedException();
+ }
+
+ ReceivePack rp = arc.getReceivePack();
+ rp.setRefLogIdent(identifiedUser.newRefLogIdent());
+ rp.setTimeout(config.getTimeout());
+ rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
+
+ for (ReceivePackInitializer initializer : receivePackInitializers) {
+ initializer.init(projectState.getNameKey(), rp);
+ }
+
+ rp.setPostReceiveHook(PostReceiveHookChain.newChain(Lists.newArrayList(postReceiveHooks)));
+ return rp;
+ } catch (IOException | PermissionBackendException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Inject
+ InProcessProtocol(Upload uploadPackFactory, Receive receivePackFactory) {
+ super(uploadPackFactory, receivePackFactory);
+ }
+}