RestApiModule: Support binding a RestView for resource creation

Instead of handling resource creation by implementing AcceptsCreate in
the RestCollection, add a new RestCreateView that can be bound via
RestApiModule.

This improves code readability since by reading the Module class we can
now directly see which REST collections support resource creation. In
addition we no longer need factories to create the REST view that
creates the resource.

This allows us at Google to bind a different REST view for the resource
creation internally while we still use the upstream REST collection for
parsing and listing. Without this change we would need to subclass the
upstream RestCollection and override the create method which is
error-prone.

This change adds REST tests for creating a resource on a root collection
(com.google.gerrit.acceptance.rest.account.CreateAccountIT#createAccountRestApi)
and for creating a resource on a child collection
(com.google.gerrit.acceptance.rest.account.CreateBranchIT#createBranchRestApi),
however it doesn't test resource creation via REST for all possible
resource types.

There are more things that we likely also want to replace by bindings
(AcceptsPost, AcceptsDelete, listing members). This should be done by
follow-up changes.

Change-Id: I5cd61f77aad2a59a02333b5f68b86bda6c353879
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2018-06-18 13:23:33 +02:00
parent 7a2b89569e
commit 6b6afe2681
44 changed files with 455 additions and 404 deletions

View File

@@ -1,34 +0,0 @@
// 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.extensions.restapi;
/**
* Optional interface for {@link RestCollection}.
*
* <p>Collections that implement this interface can accept a {@code PUT} or {@code POST} when the
* parse method throws {@link ResourceNotFoundException}.
*/
public interface AcceptsCreate<P extends RestResource> {
/**
* Handle creation of a child resource.
*
* @param parent parent collection handle.
* @param id id of the resource being created.
* @return a view to perform the creation. The create method must embed the id into the newly
* returned view object, as it will not be passed.
* @throws RestApiException the view cannot be constructed.
*/
RestModifyView<P, ?> create(P parent, IdString id) throws RestApiException;
}

View File

@@ -28,6 +28,7 @@ public abstract class RestApiModule extends FactoryModule {
protected static final String PUT = "PUT";
protected static final String DELETE = "DELETE";
protected static final String POST = "POST";
protected static final String CREATE = "CREATE";
protected <R extends RestResource> ReadViewBinder<R> get(TypeLiteral<RestView<R>> viewType) {
return new ReadViewBinder<>(view(viewType, GET, "/"));
@@ -45,6 +46,11 @@ public abstract class RestApiModule extends FactoryModule {
return new ModifyViewBinder<>(view(viewType, DELETE, "/"));
}
protected <P extends RestResource, R extends RestResource> CreateViewBinder<R> create(
TypeLiteral<RestView<R>> viewType) {
return new CreateViewBinder<>(createView(viewType, CREATE, "/"));
}
protected <R extends RestResource> ReadViewBinder<R> get(
TypeLiteral<RestView<R>> viewType, String name) {
return new ReadViewBinder<>(view(viewType, GET, name));
@@ -75,6 +81,12 @@ public abstract class RestApiModule extends FactoryModule {
return bind(viewType).annotatedWith(export(method, name));
}
protected <P extends RestResource, R extends RestResource>
LinkedBindingBuilder<RestView<R>> createView(
TypeLiteral<RestView<R>> viewType, String method, String name) {
return bind(viewType).annotatedWith(export(method, name));
}
private static Export export(String method, String name) {
if (name.length() > 1 && name.startsWith("/")) {
// Views may be bound as "/" to mean the resource itself, or
@@ -137,6 +149,33 @@ public abstract class RestApiModule extends FactoryModule {
}
}
public static class CreateViewBinder<C extends RestResource> {
private final LinkedBindingBuilder<RestView<C>> binder;
private CreateViewBinder(LinkedBindingBuilder<RestView<C>> binder) {
this.binder = binder;
}
public <P extends RestResource, T extends RestCreateView<P, C, ?>> ScopedBindingBuilder to(
Class<T> impl) {
return binder.to(impl);
}
public <P extends RestResource, T extends RestCreateView<P, C, ?>> void toInstance(T impl) {
binder.toInstance(impl);
}
public <P extends RestResource, T extends RestCreateView<P, C, ?>>
ScopedBindingBuilder toProvider(Class<? extends Provider<? extends T>> providerType) {
return binder.toProvider(providerType);
}
public <P extends RestResource, T extends RestCreateView<P, C, ?>>
ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
return binder.toProvider(provider);
}
}
public static class ChildCollectionBinder<P extends RestResource> {
private final LinkedBindingBuilder<RestView<P>> binder;

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2018 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.extensions.restapi;
/**
* RestView that supports accepting input and creating a resource.
*
* <p>The input must be supplied as JSON as the body of the HTTP request. Create views can be
* invoked by the HTTP methods {@code PUT} and {@code POST}.
*
* <p>The RestCreateView is only invoked when the parse method of the {@code RestCollection} throws
* {@link ResourceNotFoundException}, and hence the resource doesn't exist yet.
*
* @param <P> type of the parent resource
* @param <C> type of the child resource that is created
* @param <I> type of input the JSON parser will parse the input into.
*/
public interface RestCreateView<P extends RestResource, C extends RestResource, I>
extends RestView<C> {
/**
* Process the view operation by creating the resource.
*
* @param parentResource parent resource of the resource that should be created
* @param input input after parsing from request.
* @return result to return to the client. Use {@link BinaryResult} to avoid automatic conversion
* to JSON.
* @throws RestApiException if the resource creation is rejected
* @throws Exception the implementation of the view failed. The exception will be logged and HTTP
* 500 Internal Server Error will be returned to the client.
*/
Object apply(P parentResource, IdString id, I input) throws Exception;
}

View File

@@ -68,7 +68,6 @@ import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AcceptsDelete;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -88,6 +87,7 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestResource;
@@ -323,11 +323,15 @@ public class RestApiServlet extends HttpServlet {
checkPreconditions(req);
}
} catch (ResourceNotFoundException e) {
if (rc instanceof AcceptsCreate && path.isEmpty() && (isPost(req) || isPut(req))) {
@SuppressWarnings("unchecked")
AcceptsCreate<RestResource> ac = (AcceptsCreate<RestResource>) rc;
viewData = new ViewData(null, ac.create(rsrc, id));
if (!path.isEmpty() || (!isPost(req) && !isPut(req))) {
throw e;
}
RestView<RestResource> createView = rc.views().get("gerrit", "CREATE./");
if (createView != null) {
viewData = new ViewData(null, createView);
status = SC_CREATED;
path.add(id);
} else {
throw e;
}
@@ -365,12 +369,20 @@ public class RestApiServlet extends HttpServlet {
checkPreconditions(req);
viewData = new ViewData(null, null);
} catch (ResourceNotFoundException e) {
if (c instanceof AcceptsCreate && path.isEmpty() && (isPost(req) || isPut(req))) {
@SuppressWarnings("unchecked")
AcceptsCreate<RestResource> ac = (AcceptsCreate<RestResource>) c;
viewData = new ViewData(viewData.pluginName, ac.create(rsrc, id));
status = SC_CREATED;
} else if (c instanceof AcceptsDelete && path.isEmpty() && isDelete(req)) {
if (!path.isEmpty()) {
throw e;
}
if (isPost(req) || isPut(req)) {
RestView<RestResource> createView = c.views().get("gerrit", "CREATE./");
if (createView != null) {
viewData = new ViewData(null, createView);
status = SC_CREATED;
path.add(id);
} else {
throw e;
}
} else if (c instanceof AcceptsDelete && isDelete(req)) {
@SuppressWarnings("unchecked")
AcceptsDelete<RestResource> ac = (AcceptsDelete<RestResource>) c;
viewData = new ViewData(viewData.pluginName, ac.delete(rsrc, id));
@@ -409,6 +421,19 @@ public class RestApiServlet extends HttpServlet {
ServletUtils.consumeRequestBody(is);
}
}
} else if (viewData.view instanceof RestCreateView<?, ?, ?>) {
@SuppressWarnings("unchecked")
RestCreateView<RestResource, RestResource, Object> m =
(RestCreateView<RestResource, RestResource, Object>) viewData.view;
Type type = inputType(m);
inputRequestBody = parseRequest(req, type);
result = m.apply(rsrc, path.get(0), inputRequestBody);
if (inputRequestBody instanceof RawInput) {
try (InputStream is = req.getInputStream()) {
ServletUtils.consumeRequestBody(is);
}
}
} else {
throw new ResourceNotFoundException();
}
@@ -735,6 +760,24 @@ public class RestApiServlet extends HttpServlet {
return ((ParameterizedType) supertype).getActualTypeArguments()[1];
}
private static Type inputType(RestCreateView<RestResource, RestResource, Object> m) {
// MyCreateView implements RestCreateView<SomeResource, SomeResource, MyInput>
TypeLiteral<?> typeLiteral = TypeLiteral.get(m.getClass());
// RestCreateView<SomeResource, SomeResource, MyInput>
// This is smart enough to resolve even when there are intervening subclasses, even if they have
// reordered type arguments.
TypeLiteral<?> supertypeLiteral = typeLiteral.getSupertype(RestCreateView.class);
Type supertype = supertypeLiteral.getType();
checkState(
supertype instanceof ParameterizedType,
"supertype of %s is not parameterized: %s",
typeLiteral,
supertypeLiteral);
return ((ParameterizedType) supertype).getActualTypeArguments()[2];
}
private Object parseRequest(HttpServletRequest req, Type type)
throws IOException, BadRequestException, SecurityException, IllegalArgumentException,
NoSuchMethodException, IllegalAccessException, InstantiationException,

View File

@@ -111,7 +111,7 @@ public class AccountApiImpl implements AccountApi {
private final Stars.Get starsGet;
private final Stars.Post starsPost;
private final GetEmails getEmails;
private final CreateEmail.Factory createEmailFactory;
private final CreateEmail createEmail;
private final DeleteEmail deleteEmail;
private final GpgApiAdapter gpgApiAdapter;
private final GetSshKeys getSshKeys;
@@ -151,7 +151,7 @@ public class AccountApiImpl implements AccountApi {
Stars.Get starsGet,
Stars.Post starsPost,
GetEmails getEmails,
CreateEmail.Factory createEmailFactory,
CreateEmail createEmail,
DeleteEmail deleteEmail,
GpgApiAdapter gpgApiAdapter,
GetSshKeys getSshKeys,
@@ -190,7 +190,7 @@ public class AccountApiImpl implements AccountApi {
this.starsGet = starsGet;
this.starsPost = starsPost;
this.getEmails = getEmails;
this.createEmailFactory = createEmailFactory;
this.createEmail = createEmail;
this.deleteEmail = deleteEmail;
this.getSshKeys = getSshKeys;
this.addSshKey = addSshKey;
@@ -341,9 +341,8 @@ public class AccountApiImpl implements AccountApi {
@Override
public void starChange(String changeId) throws RestApiException {
try {
ChangeResource rsrc = changes.parse(TopLevelResource.INSTANCE, IdString.fromUrl(changeId));
starredChangesCreate.setChange(rsrc);
starredChangesCreate.apply(account, new StarredChanges.EmptyInput());
starredChangesCreate.apply(
account, IdString.fromUrl(changeId), new StarredChanges.EmptyInput());
} catch (Exception e) {
throw asRestApiException("Cannot star change", e);
}
@@ -412,7 +411,7 @@ public class AccountApiImpl implements AccountApi {
public void addEmail(EmailInput input) throws RestApiException {
AccountResource.Email rsrc = new AccountResource.Email(account.getUser(), input.email);
try {
createEmailFactory.create(input.email).apply(rsrc, input);
createEmail.apply(rsrc, IdString.fromDecoded(input.email), input);
} catch (Exception e) {
throw asRestApiException("Cannot add email", e);
}
@@ -432,7 +431,7 @@ public class AccountApiImpl implements AccountApi {
public EmailApi createEmail(EmailInput input) throws RestApiException {
AccountResource.Email rsrc = new AccountResource.Email(account.getUser(), input.email);
try {
createEmailFactory.create(input.email).apply(rsrc, input);
createEmail.apply(rsrc, IdString.fromDecoded(input.email), input);
return email(rsrc.getEmail());
} catch (Exception e) {
throw asRestApiException("Cannot create email", e);

View File

@@ -45,7 +45,7 @@ public class AccountsImpl implements Accounts {
private final AccountApiImpl.Factory api;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> self;
private final CreateAccount.Factory createAccount;
private final CreateAccount createAccount;
private final Provider<QueryAccounts> queryAccountsProvider;
@Inject
@@ -54,7 +54,7 @@ public class AccountsImpl implements Accounts {
AccountApiImpl.Factory api,
PermissionBackend permissionBackend,
Provider<CurrentUser> self,
CreateAccount.Factory createAccount,
CreateAccount createAccount,
Provider<QueryAccounts> queryAccountsProvider) {
this.accounts = accounts;
this.api = api;
@@ -99,9 +99,13 @@ public class AccountsImpl implements Accounts {
throw new BadRequestException("AccountInput must specify username");
}
try {
CreateAccount impl = createAccount.create(in.username);
permissionBackend.currentUser().checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
AccountInfo info = impl.apply(TopLevelResource.INSTANCE, in).value();
permissionBackend
.currentUser()
.checkAny(GlobalPermission.fromAnnotation(createAccount.getClass()));
AccountInfo info =
createAccount
.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(in.username), in)
.value();
return id(info._accountId);
} catch (Exception e) {
throw asRestApiException("Cannot create account " + in.username, e);

View File

@@ -49,7 +49,7 @@ class GroupsImpl implements Groups {
private final Provider<ListGroups> listGroups;
private final Provider<QueryGroups> queryGroups;
private final PermissionBackend permissionBackend;
private final CreateGroup.Factory createGroup;
private final CreateGroup createGroup;
private final GroupApiImpl.Factory api;
@Inject
@@ -60,7 +60,7 @@ class GroupsImpl implements Groups {
Provider<ListGroups> listGroups,
Provider<QueryGroups> queryGroups,
PermissionBackend permissionBackend,
CreateGroup.Factory createGroup,
CreateGroup createGroup,
GroupApiImpl.Factory api) {
this.accounts = accounts;
this.groups = groups;
@@ -90,9 +90,11 @@ class GroupsImpl implements Groups {
throw new BadRequestException("GroupInput must specify name");
}
try {
CreateGroup impl = createGroup.create(in.name);
permissionBackend.currentUser().checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
GroupInfo info = impl.apply(TopLevelResource.INSTANCE, in);
permissionBackend
.currentUser()
.checkAny(GlobalPermission.fromAnnotation(createGroup.getClass()));
GroupInfo info =
createGroup.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(in.name), in);
return id(info.id);
} catch (Exception e) {
throw asRestApiException("Cannot create group " + in.name, e);

View File

@@ -46,7 +46,7 @@ public class BranchApiImpl implements BranchApi {
}
private final BranchesCollection branches;
private final CreateBranch.Factory createBranchFactory;
private final CreateBranch createBranch;
private final DeleteBranch deleteBranch;
private final FilesCollection filesCollection;
private final GetBranch getBranch;
@@ -58,7 +58,7 @@ public class BranchApiImpl implements BranchApi {
@Inject
BranchApiImpl(
BranchesCollection branches,
CreateBranch.Factory createBranchFactory,
CreateBranch createBranch,
DeleteBranch deleteBranch,
FilesCollection filesCollection,
GetBranch getBranch,
@@ -67,7 +67,7 @@ public class BranchApiImpl implements BranchApi {
@Assisted ProjectResource project,
@Assisted String ref) {
this.branches = branches;
this.createBranchFactory = createBranchFactory;
this.createBranch = createBranch;
this.deleteBranch = deleteBranch;
this.filesCollection = filesCollection;
this.getBranch = getBranch;
@@ -80,7 +80,7 @@ public class BranchApiImpl implements BranchApi {
@Override
public BranchApi create(BranchInput input) throws RestApiException {
try {
createBranchFactory.create(ref).apply(project, input);
createBranch.apply(project, IdString.fromDecoded(ref), input);
return this;
} catch (Exception e) {
throw asRestApiException("Cannot create branch", e);

View File

@@ -88,7 +88,7 @@ public class ProjectApiImpl implements ProjectApi {
}
private final PermissionBackend permissionBackend;
private final CreateProject.Factory createProjectFactory;
private final CreateProject createProject;
private final ProjectApiImpl.Factory projectApi;
private final ProjectsCollection projects;
private final GetDescription getDescription;
@@ -122,7 +122,7 @@ public class ProjectApiImpl implements ProjectApi {
@AssistedInject
ProjectApiImpl(
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
CreateProject createProject,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
GetDescription getDescription,
@@ -153,7 +153,7 @@ public class ProjectApiImpl implements ProjectApi {
@Assisted ProjectResource project) {
this(
permissionBackend,
createProjectFactory,
createProject,
projectApi,
projects,
getDescription,
@@ -188,7 +188,7 @@ public class ProjectApiImpl implements ProjectApi {
@AssistedInject
ProjectApiImpl(
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
CreateProject createProject,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
GetDescription getDescription,
@@ -219,7 +219,7 @@ public class ProjectApiImpl implements ProjectApi {
@Assisted String name) {
this(
permissionBackend,
createProjectFactory,
createProject,
projectApi,
projects,
getDescription,
@@ -253,7 +253,7 @@ public class ProjectApiImpl implements ProjectApi {
private ProjectApiImpl(
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
CreateProject createProject,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
GetDescription getDescription,
@@ -284,7 +284,7 @@ public class ProjectApiImpl implements ProjectApi {
SetParent setParent,
String name) {
this.permissionBackend = permissionBackend;
this.createProjectFactory = createProjectFactory;
this.createProject = createProject;
this.projectApi = projectApi;
this.projects = projects;
this.getDescription = getDescription;
@@ -330,9 +330,10 @@ public class ProjectApiImpl implements ProjectApi {
if (in.name != null && !name.equals(in.name)) {
throw new BadRequestException("name must match input.name");
}
CreateProject impl = createProjectFactory.create(name);
permissionBackend.currentUser().checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
impl.apply(TopLevelResource.INSTANCE, in);
permissionBackend
.currentUser()
.checkAny(GlobalPermission.fromAnnotation(createProject.getClass()));
createProject.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(name), in);
return projectApi.create(projects.parse(name));
} catch (Exception e) {
throw asRestApiException("Cannot create project: " + e.getMessage(), e);

View File

@@ -39,7 +39,7 @@ public class TagApiImpl implements TagApi {
}
private final ListTags listTags;
private final CreateTag.Factory createTagFactory;
private final CreateTag createTag;
private final DeleteTag deleteTag;
private final TagsCollection tags;
private final String ref;
@@ -48,13 +48,13 @@ public class TagApiImpl implements TagApi {
@Inject
TagApiImpl(
ListTags listTags,
CreateTag.Factory createTagFactory,
CreateTag createTag,
DeleteTag deleteTag,
TagsCollection tags,
@Assisted ProjectResource project,
@Assisted String ref) {
this.listTags = listTags;
this.createTagFactory = createTagFactory;
this.createTag = createTag;
this.deleteTag = deleteTag;
this.tags = tags;
this.project = project;
@@ -64,7 +64,7 @@ public class TagApiImpl implements TagApi {
@Override
public TagApi create(TagInput input) throws RestApiException {
try {
createTagFactory.create(ref).apply(project, input);
createTag.apply(project, IdString.fromDecoded(ref), input);
return this;
} catch (Exception e) {
throw asRestApiException("Cannot create tag", e);

View File

@@ -19,8 +19,10 @@ import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.InstallPluginInput;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.inject.Inject;
@@ -91,6 +93,26 @@ public class InstallPlugin implements RestModifyView<TopLevelResource, InstallPl
}
}
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
static class Create
implements RestCreateView<TopLevelResource, PluginResource, InstallPluginInput> {
private final PluginLoader loader;
private final Provider<InstallPlugin> install;
@Inject
Create(PluginLoader loader, Provider<InstallPlugin> install) {
this.loader = loader;
this.install = install;
}
@Override
public Response<PluginInfo> apply(
TopLevelResource parentResource, IdString id, InstallPluginInput input) throws Exception {
loader.checkRemoteAdminEnabled();
return install.get().setName(id.get()).setCreated(true).apply(parentResource, input);
}
}
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
static class Overwrite implements RestModifyView<PluginResource, InstallPluginInput> {
private final Provider<InstallPlugin> install;

View File

@@ -27,6 +27,7 @@ public class PluginRestApiModule extends RestApiModule {
requireBinding(Key.get(PluginUser.Factory.class));
bind(PluginsCollection.class);
DynamicMap.mapOf(binder(), PLUGIN_KIND);
create(PLUGIN_KIND).to(InstallPlugin.Create.class);
put(PLUGIN_KIND).to(InstallPlugin.Overwrite.class);
delete(PLUGIN_KIND).to(DisablePlugin.class);
get(PLUGIN_KIND, "status").to(GetStatus.class);

View File

@@ -15,10 +15,8 @@
package com.google.gerrit.server.plugins;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -27,24 +25,18 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
public class PluginsCollection
implements RestCollection<TopLevelResource, PluginResource>, AcceptsCreate<TopLevelResource> {
public class PluginsCollection implements RestCollection<TopLevelResource, PluginResource> {
private final DynamicMap<RestView<PluginResource>> views;
private final PluginLoader loader;
private final Provider<ListPlugins> list;
private final Provider<InstallPlugin> install;
@Inject
public PluginsCollection(
DynamicMap<RestView<PluginResource>> views,
PluginLoader loader,
Provider<ListPlugins> list,
Provider<InstallPlugin> install) {
DynamicMap<RestView<PluginResource>> views, PluginLoader loader, Provider<ListPlugins> list) {
this.views = views;
this.loader = loader;
this.list = list;
this.install = install;
}
@Override
@@ -66,12 +58,6 @@ public class PluginsCollection
return new PluginResource(p);
}
@Override
public InstallPlugin create(TopLevelResource parent, IdString id) throws RestApiException {
loader.checkRemoteAdminEnabled();
return install.get().setName(id.get()).setCreated(true);
}
@Override
public DynamicMap<RestView<PluginResource>> views() {
return views;

View File

@@ -16,11 +16,9 @@ package com.google.gerrit.server.restapi.account;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -40,15 +38,13 @@ import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
public class AccountsCollection
implements RestCollection<TopLevelResource, AccountResource>, AcceptsCreate<TopLevelResource> {
public class AccountsCollection implements RestCollection<TopLevelResource, AccountResource> {
private final Provider<CurrentUser> self;
private final AccountResolver resolver;
private final AccountControl.Factory accountControlFactory;
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<QueryAccounts> list;
private final DynamicMap<RestView<AccountResource>> views;
private final CreateAccount.Factory createAccountFactory;
@Inject
public AccountsCollection(
@@ -57,15 +53,13 @@ public class AccountsCollection
AccountControl.Factory accountControlFactory,
IdentifiedUser.GenericFactory userFactory,
Provider<QueryAccounts> list,
DynamicMap<RestView<AccountResource>> views,
CreateAccount.Factory createAccountFactory) {
DynamicMap<RestView<AccountResource>> views) {
this.self = self;
this.resolver = resolver;
this.accountControlFactory = accountControlFactory;
this.userFactory = userFactory;
this.list = list;
this.views = views;
this.createAccountFactory = createAccountFactory;
}
@Override
@@ -159,9 +153,4 @@ public class AccountsCollection
public DynamicMap<RestView<AccountResource>> views() {
return views;
}
@Override
public CreateAccount create(TopLevelResource parent, IdString username) throws RestApiException {
return createAccountFactory.create(username.get());
}
}

View File

@@ -29,9 +29,10 @@ import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
@@ -40,6 +41,7 @@ import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountExternalIdCreator;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
@@ -52,7 +54,6 @@ import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
@@ -61,11 +62,8 @@ import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
@RequiresCapability(GlobalCapability.CREATE_ACCOUNT)
public class CreateAccount implements RestModifyView<TopLevelResource, AccountInput> {
public interface Factory {
CreateAccount create(String username);
}
public class CreateAccount
implements RestCreateView<TopLevelResource, AccountResource, AccountInput> {
private final Sequences seq;
private final GroupsCollection groupsCollection;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
@@ -75,7 +73,6 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
private final DynamicSet<AccountExternalIdCreator> externalIdCreators;
private final Provider<GroupsUpdate> groupsUpdate;
private final OutgoingEmailValidator validator;
private final String username;
@Inject
CreateAccount(
@@ -87,8 +84,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
AccountLoader.Factory infoLoader,
DynamicSet<AccountExternalIdCreator> externalIdCreators,
@UserInitiated Provider<GroupsUpdate> groupsUpdate,
OutgoingEmailValidator validator,
@Assisted String username) {
OutgoingEmailValidator validator) {
this.seq = seq;
this.groupsCollection = groupsCollection;
this.authorizedKeys = authorizedKeys;
@@ -98,23 +94,23 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
this.externalIdCreators = externalIdCreators;
this.groupsUpdate = groupsUpdate;
this.validator = validator;
this.username = username;
}
@Override
public Response<AccountInfo> apply(TopLevelResource rsrc, @Nullable AccountInput input)
public Response<AccountInfo> apply(
TopLevelResource rsrc, IdString id, @Nullable AccountInput input)
throws BadRequestException, ResourceConflictException, UnprocessableEntityException,
OrmException, IOException, ConfigInvalidException {
return apply(input != null ? input : new AccountInput());
return apply(id, input != null ? input : new AccountInput());
}
public Response<AccountInfo> apply(AccountInput input)
public Response<AccountInfo> apply(IdString id, AccountInput input)
throws BadRequestException, ResourceConflictException, UnprocessableEntityException,
OrmException, IOException, ConfigInvalidException {
String username = id.get();
if (input.username != null && !username.equals(input.username)) {
throw new BadRequestException("username must match URL");
}
if (!ExternalId.isValidUsername(username)) {
throw new BadRequestException(
"Username '" + username + "' must contain only letters, numbers, _, - or .");
@@ -122,19 +118,19 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
Set<AccountGroup.UUID> groups = parseGroups(input.groups);
Account.Id id = new Account.Id(seq.nextAccountId());
Account.Id accountId = new Account.Id(seq.nextAccountId());
List<ExternalId> extIds = new ArrayList<>();
if (input.email != null) {
if (!validator.isValid(input.email)) {
throw new BadRequestException("invalid email address");
}
extIds.add(ExternalId.createEmail(id, input.email));
extIds.add(ExternalId.createEmail(accountId, input.email));
}
extIds.add(ExternalId.createUsername(username, id, input.httpPassword));
extIds.add(ExternalId.createUsername(username, accountId, input.httpPassword));
for (AccountExternalIdCreator c : externalIdCreators) {
extIds.addAll(c.create(id, username, input.email));
extIds.addAll(c.create(accountId, username, input.email));
}
try {
@@ -142,7 +138,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
.get()
.insert(
"Create Account via API",
id,
accountId,
u -> u.setFullName(input.name).setPreferredEmail(input.email).addExternalIds(extIds));
} catch (DuplicateExternalIdKeyException e) {
if (e.getDuplicateKey().isScheme(SCHEME_USERNAME)) {
@@ -159,7 +155,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
for (AccountGroup.UUID groupUuid : groups) {
try {
addGroupMember(groupUuid, id);
addGroupMember(groupUuid, accountId);
} catch (NoSuchGroupException e) {
throw new UnprocessableEntityException(String.format("Group %s not found", groupUuid));
}
@@ -167,7 +163,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
if (input.sshKey != null) {
try {
authorizedKeys.addKey(id, input.sshKey);
authorizedKeys.addKey(accountId, input.sshKey);
sshKeyCache.evict(username);
} catch (InvalidSshKeyException e) {
throw new BadRequestException(e.getMessage());
@@ -175,7 +171,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
}
AccountLoader loader = infoLoader.create(true);
AccountInfo info = loader.get(id);
AccountInfo info = loader.get(accountId);
loader.fill();
return Response.created(info);
}

View File

@@ -22,11 +22,12 @@ import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.common.EmailInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountException;
@@ -43,17 +44,13 @@ import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
public class CreateEmail implements RestModifyView<AccountResource, EmailInput> {
public class CreateEmail
implements RestCreateView<AccountResource, AccountResource.Email, EmailInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
CreateEmail create(String email);
}
private final Provider<CurrentUser> self;
private final Realm realm;
private final PermissionBackend permissionBackend;
@@ -61,7 +58,6 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
private final RegisterNewEmailSender.Factory registerNewEmailFactory;
private final PutPreferred putPreferred;
private final OutgoingEmailValidator validator;
private final String email;
private final boolean isDevMode;
@Inject
@@ -73,8 +69,7 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
AccountManager accountManager,
RegisterNewEmailSender.Factory registerNewEmailFactory,
PutPreferred putPreferred,
OutgoingEmailValidator validator,
@Assisted String email) {
OutgoingEmailValidator validator) {
this.self = self;
this.realm = realm;
this.permissionBackend = permissionBackend;
@@ -82,12 +77,11 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
this.registerNewEmailFactory = registerNewEmailFactory;
this.putPreferred = putPreferred;
this.validator = validator;
this.email = email != null ? email.trim() : null;
this.isDevMode = authConfig.getAuthType() == DEVELOPMENT_BECOME_ANY_ACCOUNT;
}
@Override
public Response<EmailInfo> apply(AccountResource rsrc, EmailInput input)
public Response<EmailInfo> apply(AccountResource rsrc, IdString id, EmailInput input)
throws RestApiException, OrmException, EmailException, MethodNotAllowedException, IOException,
ConfigInvalidException, PermissionBackendException {
if (input == null) {
@@ -102,13 +96,15 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
throw new MethodNotAllowedException("realm does not allow adding emails");
}
return apply(rsrc.getUser(), input);
return apply(rsrc.getUser(), id, input);
}
/** To be used from plugins that want to create emails without permission checks. */
public Response<EmailInfo> apply(IdentifiedUser user, EmailInput input)
public Response<EmailInfo> apply(IdentifiedUser user, IdString id, EmailInput input)
throws RestApiException, OrmException, EmailException, MethodNotAllowedException, IOException,
ConfigInvalidException, PermissionBackendException {
String email = id.get().trim();
if (input == null) {
input = new EmailInput();
}

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
@@ -33,27 +32,22 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
@Singleton
public class EmailsCollection
implements ChildCollection<AccountResource, AccountResource.Email>,
AcceptsCreate<AccountResource> {
public class EmailsCollection implements ChildCollection<AccountResource, AccountResource.Email> {
private final DynamicMap<RestView<AccountResource.Email>> views;
private final GetEmails list;
private final Provider<CurrentUser> self;
private final PermissionBackend permissionBackend;
private final CreateEmail.Factory createEmailFactory;
@Inject
EmailsCollection(
DynamicMap<RestView<AccountResource.Email>> views,
GetEmails list,
Provider<CurrentUser> self,
PermissionBackend permissionBackend,
CreateEmail.Factory createEmailFactory) {
PermissionBackend permissionBackend) {
this.views = views;
this.list = list;
this.self = self;
this.permissionBackend = permissionBackend;
this.createEmailFactory = createEmailFactory;
}
@Override
@@ -85,9 +79,4 @@ public class EmailsCollection
public DynamicMap<RestView<Email>> views() {
return views;
}
@Override
public CreateEmail create(AccountResource parent, IdString email) {
return createEmailFactory.create(email.get());
}
}

View File

@@ -43,6 +43,7 @@ public class Module extends RestApiModule {
DynamicMap.mapOf(binder(), STARRED_CHANGE_KIND);
DynamicMap.mapOf(binder(), STAR_KIND);
create(ACCOUNT_KIND).to(CreateAccount.class);
put(ACCOUNT_KIND).to(PutAccount.class);
get(ACCOUNT_KIND).to(GetAccount.class);
get(ACCOUNT_KIND, "detail").to(GetDetail.class);
@@ -58,6 +59,7 @@ public class Module extends RestApiModule {
put(ACCOUNT_KIND, "active").to(PutActive.class);
delete(ACCOUNT_KIND, "active").to(DeleteActive.class);
child(ACCOUNT_KIND, "emails").to(EmailsCollection.class);
create(EMAIL_KIND).to(CreateEmail.class);
get(EMAIL_KIND).to(GetEmail.class);
put(EMAIL_KIND).to(PutEmail.class);
delete(EMAIL_KIND).to(DeleteEmail.class);
@@ -93,6 +95,7 @@ public class Module extends RestApiModule {
put(ACCOUNT_KIND, "agreements").to(PutAgreement.class);
child(ACCOUNT_KIND, "starred.changes").to(StarredChanges.class);
create(STARRED_CHANGE_KIND).to(StarredChanges.Create.class);
put(STARRED_CHANGE_KIND).to(StarredChanges.Put.class);
delete(STARRED_CHANGE_KIND).to(StarredChanges.Delete.class);
bind(StarredChanges.Create.class);
@@ -104,8 +107,6 @@ public class Module extends RestApiModule {
get(ACCOUNT_KIND, "external.ids").to(GetExternalIds.class);
post(ACCOUNT_KIND, "external.ids:delete").to(DeleteExternalIds.class);
factory(CreateAccount.Factory.class);
factory(CreateEmail.Factory.class);
factory(AccountsUpdate.Factory.class);
}

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.server.restapi.account;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -25,6 +24,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
@@ -49,24 +49,20 @@ import java.io.IOException;
@Singleton
public class StarredChanges
implements ChildCollection<AccountResource, AccountResource.StarredChange>,
AcceptsCreate<AccountResource> {
implements ChildCollection<AccountResource, AccountResource.StarredChange> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ChangesCollection changes;
private final DynamicMap<RestView<AccountResource.StarredChange>> views;
private final Provider<Create> createProvider;
private final StarredChangesUtil starredChangesUtil;
@Inject
StarredChanges(
ChangesCollection changes,
DynamicMap<RestView<AccountResource.StarredChange>> views,
Provider<Create> createProvider,
StarredChangesUtil starredChangesUtil) {
this.changes = changes;
this.views = views;
this.createProvider = createProvider;
this.starredChangesUtil = starredChangesUtil;
}
@@ -101,42 +97,40 @@ public class StarredChanges
};
}
@Override
public RestModifyView<AccountResource, EmptyInput> create(AccountResource parent, IdString id)
throws RestApiException {
try {
return createProvider.get().setChange(changes.parse(TopLevelResource.INSTANCE, id));
} catch (ResourceNotFoundException e) {
throw new UnprocessableEntityException(String.format("change %s not found", id.get()));
} catch (OrmException | PermissionBackendException | IOException e) {
logger.atSevere().withCause(e).log("cannot resolve change");
throw new UnprocessableEntityException("internal server error");
}
}
@Singleton
public static class Create implements RestModifyView<AccountResource, EmptyInput> {
public static class Create
implements RestCreateView<AccountResource, AccountResource.StarredChange, EmptyInput> {
private final Provider<CurrentUser> self;
private final ChangesCollection changes;
private final StarredChangesUtil starredChangesUtil;
private ChangeResource change;
@Inject
Create(Provider<CurrentUser> self, StarredChangesUtil starredChangesUtil) {
Create(
Provider<CurrentUser> self,
ChangesCollection changes,
StarredChangesUtil starredChangesUtil) {
this.self = self;
this.changes = changes;
this.starredChangesUtil = starredChangesUtil;
}
public Create setChange(ChangeResource change) {
this.change = change;
return this;
}
@Override
public Response<?> apply(AccountResource rsrc, EmptyInput in)
public Response<?> apply(AccountResource rsrc, IdString id, EmptyInput in)
throws RestApiException, OrmException, IOException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
throw new AuthException("not allowed to add starred change");
}
ChangeResource change;
try {
change = changes.parse(TopLevelResource.INSTANCE, id);
} catch (ResourceNotFoundException e) {
throw new UnprocessableEntityException(String.format("change %s not found", id.get()));
} catch (OrmException | PermissionBackendException | IOException e) {
logger.atSevere().withCause(e).log("cannot resolve change");
throw new UnprocessableEntityException("internal server error");
}
try {
starredChangesUtil.star(
self.get().getAccountId(),

View File

@@ -19,7 +19,6 @@ import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AcceptsDelete;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -33,6 +32,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
@@ -72,11 +72,9 @@ import org.kohsuke.args4j.Option;
@Singleton
public class ChangeEdits
implements ChildCollection<ChangeResource, ChangeEditResource>,
AcceptsCreate<ChangeResource>,
AcceptsPost<ChangeResource>,
AcceptsDelete<ChangeResource> {
private final DynamicMap<RestView<ChangeEditResource>> views;
private final Create.Factory createFactory;
private final DeleteFile.Factory deleteFileFactory;
private final Provider<Detail> detail;
private final ChangeEditUtil editUtil;
@@ -85,13 +83,11 @@ public class ChangeEdits
@Inject
ChangeEdits(
DynamicMap<RestView<ChangeEditResource>> views,
Create.Factory createFactory,
Provider<Detail> detail,
ChangeEditUtil editUtil,
Post post,
DeleteFile.Factory deleteFileFactory) {
this.views = views;
this.createFactory = createFactory;
this.detail = detail;
this.editUtil = editUtil;
this.post = post;
@@ -118,11 +114,6 @@ public class ChangeEdits
return new ChangeEditResource(rsrc, edit.get(), id.get());
}
@Override
public Create create(ChangeResource parent, IdString id) throws RestApiException {
return createFactory.create(id.get());
}
@Override
public Post post(ChangeResource parent) throws RestApiException {
return post;
@@ -141,26 +132,20 @@ public class ChangeEdits
return deleteFileFactory.create(id.get());
}
public static class Create implements RestModifyView<ChangeResource, Put.Input> {
interface Factory {
Create create(String path);
}
public static class Create
implements RestCreateView<ChangeResource, ChangeEditResource, Put.Input> {
private final Put putEdit;
private final String path;
@Inject
Create(Put putEdit, @Assisted String path) {
Create(Put putEdit) {
this.putEdit = putEdit;
this.path = path;
}
@Override
public Response<?> apply(ChangeResource resource, Put.Input input)
public Response<?> apply(ChangeResource resource, IdString id, Put.Input input)
throws AuthException, ResourceConflictException, IOException, OrmException,
PermissionBackendException {
putEdit.apply(resource, path, input.content);
putEdit.apply(resource, id.get(), input.content);
return Response.none();
}
}

View File

@@ -165,6 +165,7 @@ public class Module extends RestApiModule {
get(FILE_KIND, "blame").to(GetBlame.class);
child(CHANGE_KIND, "edit").to(ChangeEdits.class);
create(CHANGE_EDIT_KIND).to(ChangeEdits.Create.class);
delete(CHANGE_KIND, "edit").to(DeleteChangeEdit.class);
child(CHANGE_KIND, "edit:publish").to(PublishChangeEdit.class);
child(CHANGE_KIND, "edit:rebase").to(RebaseChangeEdit.class);
@@ -180,7 +181,6 @@ public class Module extends RestApiModule {
get(CHANGE_MESSAGE_KIND).to(GetChangeMessage.class);
factory(AccountLoader.Factory.class);
factory(ChangeEdits.Create.Factory.class);
factory(ChangeEdits.DeleteFile.Factory.class);
factory(ChangeInserter.Factory.class);
factory(ChangeResource.Factory.class);

View File

@@ -23,8 +23,10 @@ import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
@@ -211,22 +213,20 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
return result;
}
public static class PutMember implements RestModifyView<GroupResource, Input> {
public static class CreateMember implements RestCreateView<GroupResource, MemberResource, Input> {
private final AddMembers put;
private final String id;
public PutMember(AddMembers put, String id) {
@Inject
public CreateMember(AddMembers put) {
this.put = put;
this.id = id;
}
@Override
public AccountInfo apply(GroupResource resource, Input input)
public AccountInfo apply(GroupResource resource, IdString id, Input input)
throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
IOException, ConfigInvalidException {
AddMembers.Input in = new AddMembers.Input();
in._oneMember = id;
in._oneMember = id.get();
try {
List<AccountInfo> list = put.apply(resource, in);
if (list.size() == 1) {

View File

@@ -23,8 +23,10 @@ import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -127,22 +129,21 @@ public class AddSubgroups implements RestModifyView<GroupResource, Input> {
groupsUpdateProvider.get().updateGroup(parentGroupUuid, groupUpdate);
}
public static class PutSubgroup implements RestModifyView<GroupResource, Input> {
public static class CreateSubgroup
implements RestCreateView<GroupResource, SubgroupResource, Input> {
private final AddSubgroups addSubgroups;
private final String id;
public PutSubgroup(AddSubgroups addSubgroups, String id) {
@Inject
public CreateSubgroup(AddSubgroups addSubgroups) {
this.addSubgroups = addSubgroups;
this.id = id;
}
@Override
public GroupInfo apply(GroupResource resource, Input input)
public GroupInfo apply(GroupResource resource, IdString id, Input input)
throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
IOException, ConfigInvalidException {
AddSubgroups.Input in = new AddSubgroups.Input();
in.groups = ImmutableList.of(id);
in.groups = ImmutableList.of(id.get());
try {
List<GroupInfo> list = addSubgroups.apply(resource, in);
if (list.size() == 1) {

View File

@@ -26,9 +26,10 @@ import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
@@ -42,6 +43,7 @@ import com.google.gerrit.server.account.CreateGroupArgs;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupUUID;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.InternalGroupDescription;
import com.google.gerrit.server.group.SystemGroupBackend;
@@ -54,7 +56,6 @@ import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -67,11 +68,7 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
@RequiresCapability(GlobalCapability.CREATE_GROUP)
public class CreateGroup implements RestModifyView<TopLevelResource, GroupInput> {
public interface Factory {
CreateGroup create(@Assisted String name);
}
public class CreateGroup implements RestCreateView<TopLevelResource, GroupResource, GroupInput> {
private final Provider<IdentifiedUser> self;
private final PersonIdent serverIdent;
private final Provider<GroupsUpdate> groupsUpdateProvider;
@@ -82,7 +79,6 @@ public class CreateGroup implements RestModifyView<TopLevelResource, GroupInput>
private final AddMembers addMembers;
private final SystemGroupBackend systemGroupBackend;
private final boolean defaultVisibleToAll;
private final String name;
private final Sequences sequences;
@Inject
@@ -97,7 +93,6 @@ public class CreateGroup implements RestModifyView<TopLevelResource, GroupInput>
AddMembers addMembers,
SystemGroupBackend systemGroupBackend,
@GerritServerConfig Config cfg,
@Assisted String name,
Sequences sequences) {
this.self = self;
this.serverIdent = serverIdent;
@@ -109,7 +104,6 @@ public class CreateGroup implements RestModifyView<TopLevelResource, GroupInput>
this.addMembers = addMembers;
this.systemGroupBackend = systemGroupBackend;
this.defaultVisibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false);
this.name = name;
this.sequences = sequences;
}
@@ -124,10 +118,11 @@ public class CreateGroup implements RestModifyView<TopLevelResource, GroupInput>
}
@Override
public GroupInfo apply(TopLevelResource resource, GroupInput input)
public GroupInfo apply(TopLevelResource resource, IdString id, GroupInput input)
throws AuthException, BadRequestException, UnprocessableEntityException,
ResourceConflictException, OrmException, IOException, ConfigInvalidException,
ResourceNotFoundException {
String name = id.get();
if (input == null) {
input = new GroupInput();
}

View File

@@ -18,13 +18,11 @@ import com.google.common.collect.ListMultimap;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.NeedsParams;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -44,13 +42,10 @@ import com.google.inject.Provider;
import java.util.Optional;
public class GroupsCollection
implements RestCollection<TopLevelResource, GroupResource>,
AcceptsCreate<TopLevelResource>,
NeedsParams {
implements RestCollection<TopLevelResource, GroupResource>, NeedsParams {
private final DynamicMap<RestView<GroupResource>> views;
private final Provider<ListGroups> list;
private final Provider<QueryGroups> queryGroups;
private final CreateGroup.Factory createGroup;
private final GroupControl.Factory groupControlFactory;
private final GroupBackend groupBackend;
private final GroupCache groupCache;
@@ -63,7 +58,6 @@ public class GroupsCollection
DynamicMap<RestView<GroupResource>> views,
Provider<ListGroups> list,
Provider<QueryGroups> queryGroups,
CreateGroup.Factory createGroup,
GroupControl.Factory groupControlFactory,
GroupBackend groupBackend,
GroupCache groupCache,
@@ -71,7 +65,6 @@ public class GroupsCollection
this.views = views;
this.list = list;
this.queryGroups = queryGroups;
this.createGroup = createGroup;
this.groupControlFactory = groupControlFactory;
this.groupBackend = groupBackend;
this.groupCache = groupCache;
@@ -199,11 +192,6 @@ public class GroupsCollection
return null;
}
@Override
public CreateGroup create(TopLevelResource root, IdString name) throws RestApiException {
return createGroup.create(name.get());
}
@Override
public DynamicMap<RestView<GroupResource>> views() {
return views;

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.server.restapi.group;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
@@ -27,7 +26,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.group.MemberResource;
import com.google.gerrit.server.restapi.account.AccountsCollection;
import com.google.gerrit.server.restapi.group.AddMembers.PutMember;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -36,23 +34,19 @@ import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
public class MembersCollection
implements ChildCollection<GroupResource, MemberResource>, AcceptsCreate<GroupResource> {
public class MembersCollection implements ChildCollection<GroupResource, MemberResource> {
private final DynamicMap<RestView<MemberResource>> views;
private final Provider<ListMembers> list;
private final AccountsCollection accounts;
private final AddMembers put;
@Inject
MembersCollection(
DynamicMap<RestView<MemberResource>> views,
Provider<ListMembers> list,
AccountsCollection accounts,
AddMembers put) {
AccountsCollection accounts) {
this.views = views;
this.list = list;
this.accounts = accounts;
this.put = put;
}
@Override
@@ -78,11 +72,6 @@ public class MembersCollection
return group.getMembers().contains(user.getAccountId());
}
@Override
public PutMember create(GroupResource group, IdString id) {
return new PutMember(put, id.get());
}
@Override
public DynamicMap<RestView<MemberResource>> views() {
return views;

View File

@@ -24,7 +24,9 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.restapi.group.AddMembers.CreateMember;
import com.google.gerrit.server.restapi.group.AddMembers.UpdateMember;
import com.google.gerrit.server.restapi.group.AddSubgroups.CreateSubgroup;
import com.google.gerrit.server.restapi.group.AddSubgroups.UpdateSubgroup;
import com.google.gerrit.server.restapi.group.DeleteMembers.DeleteMember;
import com.google.gerrit.server.restapi.group.DeleteSubgroups.DeleteSubgroup;
@@ -40,6 +42,7 @@ public class Module extends RestApiModule {
DynamicMap.mapOf(binder(), MEMBER_KIND);
DynamicMap.mapOf(binder(), SUBGROUP_KIND);
create(GROUP_KIND).to(CreateGroup.class);
get(GROUP_KIND).to(GetGroup.class);
put(GROUP_KIND).to(PutGroup.class);
get(GROUP_KIND, "detail").to(GetDetail.class);
@@ -62,16 +65,17 @@ public class Module extends RestApiModule {
get(GROUP_KIND, "log.audit").to(GetAuditLog.class);
child(GROUP_KIND, "members").to(MembersCollection.class);
create(MEMBER_KIND).to(CreateMember.class);
get(MEMBER_KIND).to(GetMember.class);
put(MEMBER_KIND).to(UpdateMember.class);
delete(MEMBER_KIND).to(DeleteMember.class);
child(GROUP_KIND, "groups").to(SubgroupsCollection.class);
create(SUBGROUP_KIND).to(CreateSubgroup.class);
get(SUBGROUP_KIND).to(GetSubgroup.class);
put(SUBGROUP_KIND).to(UpdateSubgroup.class);
delete(SUBGROUP_KIND).to(DeleteSubgroup.class);
factory(CreateGroup.Factory.class);
factory(GroupsUpdate.Factory.class);
}

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.server.restapi.group;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
@@ -25,28 +24,23 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.group.SubgroupResource;
import com.google.gerrit.server.restapi.group.AddSubgroups.PutSubgroup;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class SubgroupsCollection
implements ChildCollection<GroupResource, SubgroupResource>, AcceptsCreate<GroupResource> {
public class SubgroupsCollection implements ChildCollection<GroupResource, SubgroupResource> {
private final DynamicMap<RestView<SubgroupResource>> views;
private final ListSubgroups list;
private final GroupsCollection groupsCollection;
private final AddSubgroups addSubgroups;
@Inject
SubgroupsCollection(
DynamicMap<RestView<SubgroupResource>> views,
ListSubgroups list,
GroupsCollection groupsCollection,
AddSubgroups addSubgroups) {
GroupsCollection groupsCollection) {
this.views = views;
this.list = list;
this.groupsCollection = groupsCollection;
this.addSubgroups = addSubgroups;
}
@Override
@@ -73,11 +67,6 @@ public class SubgroupsCollection
return parent.getSubgroups().contains(member.getGroupUUID());
}
@Override
public PutSubgroup create(GroupResource group, IdString id) {
return new PutSubgroup(addSubgroups, id.get());
}
@Override
public DynamicMap<RestView<SubgroupResource>> views() {
return views;

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
@@ -39,26 +38,22 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@Singleton
public class BranchesCollection
implements ChildCollection<ProjectResource, BranchResource>, AcceptsCreate<ProjectResource> {
public class BranchesCollection implements ChildCollection<ProjectResource, BranchResource> {
private final DynamicMap<RestView<BranchResource>> views;
private final Provider<ListBranches> list;
private final PermissionBackend permissionBackend;
private final GitRepositoryManager repoManager;
private final CreateBranch.Factory createBranchFactory;
@Inject
BranchesCollection(
DynamicMap<RestView<BranchResource>> views,
Provider<ListBranches> list,
PermissionBackend permissionBackend,
GitRepositoryManager repoManager,
CreateBranch.Factory createBranchFactory) {
GitRepositoryManager repoManager) {
this.views = views;
this.list = list;
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.createBranchFactory = createBranchFactory;
}
@Override
@@ -98,9 +93,4 @@ public class BranchesCollection
public DynamicMap<RestView<BranchResource>> views() {
return views;
}
@Override
public CreateBranch create(ProjectResource parent, IdString name) {
return createBranchFactory.create(name.get());
}
}

View File

@@ -21,8 +21,9 @@ import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.IdentifiedUser;
@@ -31,6 +32,7 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.BranchResource;
import com.google.gerrit.server.project.CreateRefControl;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectResource;
@@ -39,7 +41,6 @@ import com.google.gerrit.server.project.RefValidationHelper;
import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.Constants;
@@ -50,20 +51,15 @@ import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
public class CreateBranch implements RestModifyView<ProjectResource, BranchInput> {
public class CreateBranch implements RestCreateView<ProjectResource, BranchResource, BranchInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
CreateBranch create(String ref);
}
private final Provider<IdentifiedUser> identifiedUser;
private final PermissionBackend permissionBackend;
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated referenceUpdated;
private final RefValidationHelper refCreationValidator;
private final CreateRefControl createRefControl;
private String ref;
@Inject
CreateBranch(
@@ -72,21 +68,20 @@ public class CreateBranch implements RestModifyView<ProjectResource, BranchInput
GitRepositoryManager repoManager,
GitReferenceUpdated referenceUpdated,
RefValidationHelper.Factory refHelperFactory,
CreateRefControl createRefControl,
@Assisted String ref) {
CreateRefControl createRefControl) {
this.identifiedUser = identifiedUser;
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.referenceUpdated = referenceUpdated;
this.refCreationValidator = refHelperFactory.create(ReceiveCommand.Type.CREATE);
this.createRefControl = createRefControl;
this.ref = ref;
}
@Override
public BranchInfo apply(ProjectResource rsrc, BranchInput input)
public BranchInfo apply(ProjectResource rsrc, IdString id, BranchInput input)
throws BadRequestException, AuthException, ResourceConflictException, IOException,
PermissionBackendException, NoSuchProjectException {
String ref = id.get();
if (input == null) {
input = new BranchInput();
}

View File

@@ -0,0 +1,58 @@
// Copyright (C) 2018 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.restapi.project;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.common.SetDashboardInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.DashboardResource;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import org.kohsuke.args4j.Option;
@Singleton
public class CreateDashboard
implements RestCreateView<ProjectResource, DashboardResource, SetDashboardInput> {
private final Provider<SetDefaultDashboard> setDefault;
@Option(name = "--inherited", usage = "set dashboard inherited by children")
private boolean inherited;
@Inject
CreateDashboard(Provider<SetDefaultDashboard> setDefault) {
this.setDefault = setDefault;
}
@Override
public Response<DashboardInfo> apply(ProjectResource parent, IdString id, SetDashboardInput input)
throws RestApiException, IOException, PermissionBackendException {
parent.getProjectState().checkStatePermitsWrite();
if (!DashboardsCollection.isDefaultDashboard(id)) {
throw new ResourceNotFoundException(id);
}
SetDefaultDashboard set = setDefault.get();
set.inherited = inherited;
return set.apply(
DashboardResource.projectDefault(parent.getProjectState(), parent.getUser()), input);
}
}

View File

@@ -37,10 +37,11 @@ import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
@@ -64,13 +65,13 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectJson;
import com.google.gerrit.server.project.ProjectNameLockManager;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.group.GroupsCollection;
import com.google.gerrit.server.validators.ProjectCreationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -89,13 +90,10 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceiveCommand;
@RequiresCapability(GlobalCapability.CREATE_PROJECT)
public class CreateProject implements RestModifyView<TopLevelResource, ProjectInput> {
public class CreateProject
implements RestCreateView<TopLevelResource, ProjectResource, ProjectInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
CreateProject create(String name);
}
private final Provider<ProjectsCollection> projectsCollection;
private final Provider<GroupsCollection> groupsCollection;
private final DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners;
@@ -114,7 +112,6 @@ public class CreateProject implements RestModifyView<TopLevelResource, ProjectIn
private final AllProjectsName allProjects;
private final AllUsersName allUsers;
private final DynamicItem<ProjectNameLockManager> lockManager;
private final String name;
@Inject
CreateProject(
@@ -135,8 +132,7 @@ public class CreateProject implements RestModifyView<TopLevelResource, ProjectIn
Provider<PutConfig> putConfig,
AllProjectsName allProjects,
AllUsersName allUsers,
DynamicItem<ProjectNameLockManager> lockManager,
@Assisted String name) {
DynamicItem<ProjectNameLockManager> lockManager) {
this.projectsCollection = projectsCollection;
this.groupsCollection = groupsCollection;
this.projectCreationValidationListeners = projectCreationValidationListeners;
@@ -155,12 +151,12 @@ public class CreateProject implements RestModifyView<TopLevelResource, ProjectIn
this.allProjects = allProjects;
this.allUsers = allUsers;
this.lockManager = lockManager;
this.name = name;
}
@Override
public Response<ProjectInfo> apply(TopLevelResource resource, ProjectInput input)
public Response<ProjectInfo> apply(TopLevelResource resource, IdString id, ProjectInput input)
throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
String name = id.get();
if (input == null) {
input = new ProjectInput();
}

View File

@@ -23,10 +23,11 @@ import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestCreateView;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -38,8 +39,8 @@ import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.RefUtil;
import com.google.gerrit.server.project.RefUtil.InvalidRevisionException;
import com.google.gerrit.server.project.TagResource;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.TimeZone;
import org.eclipse.jgit.api.Git;
@@ -52,19 +53,13 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
public class CreateTag implements RestModifyView<ProjectResource, TagInput> {
public class CreateTag implements RestCreateView<ProjectResource, TagResource, TagInput> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
CreateTag create(String ref);
}
private final PermissionBackend permissionBackend;
private final GitRepositoryManager repoManager;
private final TagCache tagCache;
private final GitReferenceUpdated referenceUpdated;
private final WebLinks links;
private String ref;
@Inject
CreateTag(
@@ -72,19 +67,18 @@ public class CreateTag implements RestModifyView<ProjectResource, TagInput> {
GitRepositoryManager repoManager,
TagCache tagCache,
GitReferenceUpdated referenceUpdated,
WebLinks webLinks,
@Assisted String ref) {
WebLinks webLinks) {
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.tagCache = tagCache;
this.referenceUpdated = referenceUpdated;
this.links = webLinks;
this.ref = ref;
}
@Override
public TagInfo apply(ProjectResource resource, TagInput input)
public TagInfo apply(ProjectResource resource, IdString id, TagInput input)
throws RestApiException, IOException, PermissionBackendException, NoSuchProjectException {
String ref = id.get();
if (input == null) {
input = new TagInput();
}

View File

@@ -25,14 +25,12 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.api.projects.DashboardSectionInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Project;
@@ -60,14 +58,12 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@Singleton
public class DashboardsCollection
implements ChildCollection<ProjectResource, DashboardResource>, AcceptsCreate<ProjectResource> {
public class DashboardsCollection implements ChildCollection<ProjectResource, DashboardResource> {
public static final String DEFAULT_DASHBOARD_NAME = "default";
private final GitRepositoryManager gitManager;
private final DynamicMap<RestView<DashboardResource>> views;
private final Provider<ListDashboards> list;
private final Provider<SetDefaultDashboard.CreateDefault> createDefault;
private final PermissionBackend permissionBackend;
@Inject
@@ -75,12 +71,10 @@ public class DashboardsCollection
GitRepositoryManager gitManager,
DynamicMap<RestView<DashboardResource>> views,
Provider<ListDashboards> list,
Provider<SetDefaultDashboard.CreateDefault> createDefault,
PermissionBackend permissionBackend) {
this.gitManager = gitManager;
this.views = views;
this.list = list;
this.createDefault = createDefault;
this.permissionBackend = permissionBackend;
}
@@ -97,16 +91,6 @@ public class DashboardsCollection
return list.get();
}
@Override
public RestModifyView<ProjectResource, ?> create(ProjectResource parent, IdString id)
throws RestApiException {
parent.getProjectState().checkStatePermitsWrite();
if (isDefaultDashboard(id)) {
return createDefault.get();
}
throw new ResourceNotFoundException(id);
}
@Override
public DashboardResource parse(ProjectResource parent, IdString id)
throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {

View File

@@ -41,6 +41,7 @@ public class Module extends RestApiModule {
DynamicMap.mapOf(binder(), COMMIT_KIND);
DynamicMap.mapOf(binder(), TAG_KIND);
create(PROJECT_KIND).to(CreateProject.class);
put(PROJECT_KIND).to(PutProject.class);
get(PROJECT_KIND).to(GetProject.class);
get(PROJECT_KIND, "description").to(GetDescription.class);
@@ -69,11 +70,11 @@ public class Module extends RestApiModule {
post(PROJECT_KIND, "index").to(Index.class);
child(PROJECT_KIND, "branches").to(BranchesCollection.class);
create(BRANCH_KIND).to(CreateBranch.class);
put(BRANCH_KIND).to(PutBranch.class);
get(BRANCH_KIND).to(GetBranch.class);
delete(BRANCH_KIND).to(DeleteBranch.class);
post(PROJECT_KIND, "branches:delete").to(DeleteBranches.class);
factory(CreateBranch.Factory.class);
get(BRANCH_KIND, "mergeable").to(CheckMergeability.class);
factory(RefValidationHelper.Factory.class);
get(BRANCH_KIND, "reflog").to(GetReflog.class);
@@ -86,17 +87,17 @@ public class Module extends RestApiModule {
child(COMMIT_KIND, "files").to(FilesInCommitCollection.class);
child(PROJECT_KIND, "tags").to(TagsCollection.class);
create(TAG_KIND).to(CreateTag.class);
get(TAG_KIND).to(GetTag.class);
put(TAG_KIND).to(PutTag.class);
delete(TAG_KIND).to(DeleteTag.class);
post(PROJECT_KIND, "tags:delete").to(DeleteTags.class);
factory(CreateTag.Factory.class);
child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
create(DASHBOARD_KIND).to(CreateDashboard.class);
get(DASHBOARD_KIND).to(GetDashboard.class);
put(DASHBOARD_KIND).to(SetDashboard.class);
delete(DASHBOARD_KIND).to(DeleteDashboard.class);
factory(CreateProject.Factory.class);
get(PROJECT_KIND, "config").to(GetConfig.class);
put(PROJECT_KIND, "config").to(PutConfig.class);

View File

@@ -17,7 +17,6 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.collect.ListMultimap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
@@ -46,16 +45,13 @@ import org.eclipse.jgit.lib.Constants;
@Singleton
public class ProjectsCollection
implements RestCollection<TopLevelResource, ProjectResource>,
AcceptsCreate<TopLevelResource>,
NeedsParams {
implements RestCollection<TopLevelResource, ProjectResource>, NeedsParams {
private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<ListProjects> list;
private final Provider<QueryProjects> queryProjects;
private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final CreateProject.Factory createProjectFactory;
private boolean hasQuery;
@@ -66,7 +62,6 @@ public class ProjectsCollection
Provider<QueryProjects> queryProjects,
ProjectCache projectCache,
PermissionBackend permissionBackend,
CreateProject.Factory factory,
Provider<CurrentUser> user) {
this.views = views;
this.list = list;
@@ -74,7 +69,6 @@ public class ProjectsCollection
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
this.user = user;
this.createProjectFactory = factory;
}
@Override
@@ -179,9 +173,4 @@ public class ProjectsCollection
public DynamicMap<RestView<ProjectResource>> views() {
return views;
}
@Override
public CreateProject create(TopLevelResource parent, IdString name) throws RestApiException {
return createProjectFactory.create(name.get());
}
}

View File

@@ -49,7 +49,7 @@ class SetDefaultDashboard implements RestModifyView<DashboardResource, SetDashbo
private final PermissionBackend permissionBackend;
@Option(name = "--inherited", usage = "set dashboard inherited by children")
private boolean inherited;
boolean inherited;
@Inject
SetDefaultDashboard(
@@ -128,25 +128,4 @@ class SetDefaultDashboard implements RestModifyView<DashboardResource, SetDashbo
String.format("invalid project.config: %s", e.getMessage()));
}
}
static class CreateDefault implements RestModifyView<ProjectResource, SetDashboardInput> {
private final Provider<SetDefaultDashboard> setDefault;
@Option(name = "--inherited", usage = "set dashboard inherited by children")
private boolean inherited;
@Inject
CreateDefault(Provider<SetDefaultDashboard> setDefault) {
this.setDefault = setDefault;
}
@Override
public Response<DashboardInfo> apply(ProjectResource resource, SetDashboardInput input)
throws RestApiException, IOException, PermissionBackendException {
SetDefaultDashboard set = setDefault.get();
set.inherited = inherited;
return set.apply(
DashboardResource.projectDefault(resource.getProjectState(), resource.getUser()), input);
}
}
}

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -30,20 +29,14 @@ import com.google.inject.Singleton;
import java.io.IOException;
@Singleton
public class TagsCollection
implements ChildCollection<ProjectResource, TagResource>, AcceptsCreate<ProjectResource> {
public class TagsCollection implements ChildCollection<ProjectResource, TagResource> {
private final DynamicMap<RestView<TagResource>> views;
private final Provider<ListTags> list;
private final CreateTag.Factory createTagFactory;
@Inject
public TagsCollection(
DynamicMap<RestView<TagResource>> views,
Provider<ListTags> list,
CreateTag.Factory createTagFactory) {
public TagsCollection(DynamicMap<RestView<TagResource>> views, Provider<ListTags> list) {
this.views = views;
this.list = list;
this.createTagFactory = createTagFactory;
}
@Override
@@ -62,9 +55,4 @@ public class TagsCollection
public DynamicMap<RestView<TagResource>> views() {
return views;
}
@Override
public CreateTag create(ProjectResource resource, IdString name) {
return createTagFactory.create(name.get());
}
}

View File

@@ -20,6 +20,7 @@ import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -66,7 +67,7 @@ final class CreateAccountCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "USERNAME", usage = "name of the user account")
private String username;
@Inject private CreateAccount.Factory createAccountFactory;
@Inject private CreateAccount createAccount;
@Override
protected void run() throws OrmException, IOException, ConfigInvalidException, UnloggedFailure {
@@ -78,7 +79,7 @@ final class CreateAccountCommand extends SshCommand {
input.httpPassword = httpPassword;
input.groups = Lists.transform(groups, AccountGroup.Id::toString);
try {
createAccountFactory.create(username).apply(TopLevelResource.INSTANCE, input);
createAccount.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(username), input);
} catch (RestApiException e) {
throw die(e.getMessage());
}

View File

@@ -91,7 +91,7 @@ final class CreateGroupCommand extends SshCommand {
initialGroups.add(id);
}
@Inject private CreateGroup.Factory createGroupFactory;
@Inject private CreateGroup createGroup;
@Inject private GroupsCollection groups;
@@ -126,7 +126,8 @@ final class CreateGroupCommand extends SshCommand {
input.ownerId = String.valueOf(ownerGroupId.get());
}
GroupInfo group = createGroupFactory.create(groupName).apply(TopLevelResource.INSTANCE, input);
GroupInfo group =
createGroup.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(groupName), input);
return groups.parse(TopLevelResource.INSTANCE, IdString.fromUrl(group.id));
}

View File

@@ -29,6 +29,7 @@ import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.NameInput;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Account;
@@ -119,7 +120,7 @@ final class SetAccountCommand extends SshCommand {
@Inject private IdentifiedUser.GenericFactory genericUserFactory;
@Inject private CreateEmail.Factory createEmailFactory;
@Inject private CreateEmail createEmail;
@Inject private GetEmails getEmails;
@@ -269,7 +270,7 @@ final class SetAccountCommand extends SshCommand {
in.email = email;
in.noConfirmation = true;
try {
createEmailFactory.create(email).apply(rsrc, in);
createEmail.apply(rsrc, IdString.fromDecoded(email), in);
} catch (EmailException e) {
throw die(e.getMessage());
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2018 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.rest.account;
import static com.google.common.truth.Truth8.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import org.junit.Test;
public class CreateAccountIT extends AbstractDaemonTest {
@Test
public void createAccountRestApi() throws Exception {
AccountInput input = new AccountInput();
input.username = "foo";
assertThat(accountCache.getByUsername(input.username)).isEmpty();
RestResponse r = adminRestSession.put("/accounts/" + input.username, input);
r.assertCreated();
assertThat(accountCache.getByUsername(input.username)).isPresent();
}
}

View File

@@ -29,6 +29,7 @@ import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -42,7 +43,7 @@ import org.junit.Before;
import org.junit.Test;
public class SuggestReviewersIT extends AbstractDaemonTest {
@Inject private CreateGroup.Factory createGroupFactory;
@Inject private CreateGroup createGroup;
private InternalGroup group1;
private InternalGroup group2;
@@ -490,7 +491,8 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
}
private InternalGroup newGroup(String name) throws Exception {
GroupInfo group = createGroupFactory.create(name(name)).apply(TopLevelResource.INSTANCE, null);
GroupInfo group =
createGroup.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(name(name)), null);
return group(new AccountGroup.UUID(group.id));
}

View File

@@ -15,12 +15,14 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_HEADS;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInfo;
@@ -35,7 +37,6 @@ import com.google.gerrit.reviewdb.client.RefNames;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class CreateBranchIT extends AbstractDaemonTest {
private Branch.NameKey testBranch;
@@ -44,6 +45,19 @@ public class CreateBranchIT extends AbstractDaemonTest {
testBranch = new Branch.NameKey(project, "test");
}
@Test
public void createBranchRestApi() throws Exception {
BranchInput input = new BranchInput();
input.ref = "foo";
assertThat(gApi.projects().name(project.get()).branches().get().stream().map(i -> i.ref))
.doesNotContain(REFS_HEADS + input.ref);
RestResponse r =
adminRestSession.put("/projects/" + project.get() + "/branches/" + input.ref, input);
r.assertCreated();
assertThat(gApi.projects().name(project.get()).branches().get().stream().map(i -> i.ref))
.contains(REFS_HEADS + input.ref);
}
@Test
public void createBranch_Forbidden() throws Exception {
setApiUser(user);