Merge "Add REST API for 'create project'"

This commit is contained in:
David Pursehouse
2013-03-08 04:15:09 +00:00
committed by Gerrit Code Review
15 changed files with 433 additions and 40 deletions

View File

@@ -91,6 +91,7 @@ import com.google.gerrit.server.patch.PatchListCacheImpl;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.AccessControlModule;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.PerformCreateProject;
import com.google.gerrit.server.project.PermissionCollection;
import com.google.gerrit.server.project.ProjectCacheImpl;
import com.google.gerrit.server.project.ProjectControl;
@@ -185,6 +186,7 @@ public class GerritGlobalModule extends FactoryModule {
factory(ProjectState.Factory.class);
factory(RebasedPatchSetSender.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
factory(PerformCreateProject.Factory.class);
bind(PermissionCollection.Factory.class);
bind(AccountVisibility.class)
.toProvider(AccountVisibilityProvider.class)

View File

@@ -23,11 +23,9 @@ import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
import com.google.gerrit.server.changedetail.PublishDraft;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.SubmoduleOp;
import com.google.gerrit.server.patch.RemoveReviewer;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.PerformCreateProject;
import com.google.gerrit.server.project.PerRequestProjectControlCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SuggestParentCandidates;
@@ -40,7 +38,6 @@ public class GerritRequestModule extends FactoryModule {
bind(RequestCleanup.class).in(RequestScoped.class);
bind(RequestScopedReviewDbProvider.class);
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
bind(MetaDataUpdate.User.class).in(RequestScoped.class);
bind(ApprovalsUtil.class);
bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
@@ -56,7 +53,6 @@ public class GerritRequestModule extends FactoryModule {
factory(DeleteDraftPatchSet.Factory.class);
factory(PublishDraft.Factory.class);
factory(RemoveReviewer.Factory.class);
factory(PerformCreateProject.Factory.class);
factory(SuggestParentCandidates.Factory.class);
factory(BanCommit.Factory.class);
}

View File

@@ -0,0 +1,136 @@
// Copyright (C) 2013 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.project;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.ProjectCreationFailedException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
import com.google.gerrit.reviewdb.client.Project.SubmitType;
import com.google.gerrit.server.group.GroupsCollection;
import com.google.gerrit.server.project.CreateProject.Input;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import java.util.List;
@RequiresCapability(GlobalCapability.CREATE_PROJECT)
class CreateProject implements RestModifyView<TopLevelResource, Input> {
static class Input {
String name;
String parent;
String description;
boolean permissionsOnly;
boolean createEmptyCommit;
SubmitType submitType;
List<String> branches;
List<String> owners;
InheritableBoolean useContributorAgreements;
InheritableBoolean useSignedOffBy;
InheritableBoolean useContentMerge;
InheritableBoolean requireChangeId;
}
static interface Factory {
CreateProject create(String name);
}
private final PerformCreateProject.Factory createProjectFactory;
private final Provider<ProjectsCollection> projectsCollection;
private final Provider<GroupsCollection> groupsCollection;
private final ProjectJson json;
private final String name;
@Inject
CreateProject(PerformCreateProject.Factory performCreateProjectFactory,
Provider<ProjectsCollection> projectsCollection,
Provider<GroupsCollection> groupsCollection, ProjectJson json,
@Assisted String name) {
this.createProjectFactory = performCreateProjectFactory;
this.projectsCollection = projectsCollection;
this.groupsCollection = groupsCollection;
this.json = json;
this.name = name;
}
@Override
public Object apply(TopLevelResource resource, Input input)
throws BadRequestException, UnprocessableEntityException,
ProjectCreationFailedException {
if (input == null) {
input = new Input();
}
if (input.name != null && !name.equals(input.name)) {
throw new BadRequestException("name must match URL");
}
final CreateProjectArgs args = new CreateProjectArgs();
args.setProjectName(name);
if (!Strings.isNullOrEmpty(input.parent)) {
try {
args.newParent =
projectsCollection.get().parse(input.parent).getControl();
} catch (ResourceNotFoundException e) {
throw new UnprocessableEntityException(String.format(
"parent project \"%s\" not found", input.parent));
}
}
args.createEmptyCommit = input.createEmptyCommit;
args.permissionsOnly = input.permissionsOnly;
args.projectDescription = Strings.emptyToNull(input.description);
args.submitType =
Objects.firstNonNull(input.submitType, SubmitType.MERGE_IF_NECESSARY);
args.branch = input.branches;
if (input.owners != null) {
List<AccountGroup.UUID> ownerIds =
Lists.newArrayListWithCapacity(input.owners.size());
for (String owner : input.owners) {
try {
ownerIds.add(groupsCollection.get().parse(owner).getGroupUUID());
} catch (ResourceNotFoundException e) {
throw new UnprocessableEntityException(String.format(
"group \"%s\" not found", owner));
}
}
args.ownerIds = ownerIds;
}
args.contributorAgreements =
Objects.firstNonNull(input.useContributorAgreements,
InheritableBoolean.INHERIT);
args.signedOffBy =
Objects.firstNonNull(input.useSignedOffBy, InheritableBoolean.INHERIT);
args.contentMerge =
input.submitType == SubmitType.FAST_FORWARD_ONLY
? InheritableBoolean.FALSE : Objects.firstNonNull(
input.useContentMerge, InheritableBoolean.INHERIT);
args.changeIdRequired =
Objects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
Project p = createProjectFactory.create(args).createProject();
return Response.created(json.format(p));
}
}

View File

@@ -41,6 +41,7 @@ public class CreateProjectArgs {
signedOffBy = InheritableBoolean.INHERIT;
contentMerge = InheritableBoolean.INHERIT;
changeIdRequired = InheritableBoolean.INHERIT;
submitType = SubmitType.MERGE_IF_NECESSARY;
}
public Project.NameKey getProject() {

View File

@@ -14,43 +14,20 @@
package com.google.gerrit.server.project;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.inject.Inject;
class GetProject implements RestReadView<ProjectResource> {
private final AllProjectsName allProjects;
private final ProjectJson json;
@Inject
GetProject(AllProjectsName allProjects) {
this.allProjects = allProjects;
GetProject(ProjectJson json) {
this.json = json;
}
@Override
public Object apply(ProjectResource resource) {
Project project = resource.getControl().getProject();
ProjectInfo info = new ProjectInfo();
info.name = resource.getName();
Project.NameKey parentName = project.getParent(allProjects);
info.parent = parentName != null ? parentName.get() : null;
info.description = Strings.emptyToNull(project.getDescription());
info.finish();
return info;
}
static class ProjectInfo {
final String kind = "gerritcodereview#project";
String id;
String name;
String parent;
String description;
void finish() {
id = Url.encode(name);
}
public Object apply(ProjectResource rsrc) {
return json.format(rsrc);
}
}

View File

@@ -19,6 +19,8 @@ import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.server.project.CreateProject;
import com.google.inject.assistedinject.FactoryModuleBuilder;
public class Module extends RestApiModule {
@Override
@@ -29,6 +31,7 @@ public class Module extends RestApiModule {
DynamicMap.mapOf(binder(), PROJECT_KIND);
DynamicMap.mapOf(binder(), DASHBOARD_KIND);
put(PROJECT_KIND).to(PutProject.class);
get(PROJECT_KIND).to(GetProject.class);
get(PROJECT_KIND, "description").to(GetDescription.class);
put(PROJECT_KIND, "description").to(PutDescription.class);
@@ -41,5 +44,6 @@ public class Module extends RestApiModule {
get(DASHBOARD_KIND).to(GetDashboard.class);
put(DASHBOARD_KIND).to(SetDashboard.class);
delete(DASHBOARD_KIND).to(DeleteDashboard.class);
install(new FactoryModuleBuilder().build(CreateProject.Factory.class));
}
}

View File

@@ -99,7 +99,7 @@ public class PerformCreateProject {
this.metaDataUpdateFactory = metaDataUpdateFactory;
}
public void createProject() throws ProjectCreationFailedException {
public Project createProject() throws ProjectCreationFailedException {
validateParameters();
final Project.NameKey nameKey = createProjectArgs.getProject();
try {
@@ -133,6 +133,8 @@ public class PerformCreateProject {
&& createProjectArgs.createEmptyCommit) {
createEmptyCommits(repo, nameKey, createProjectArgs.branch);
}
return projectCache.get(nameKey).getProject();
} finally {
repo.close();
}
@@ -150,6 +152,7 @@ public class PerformCreateProject {
if (repo.getObjectDatabase().exists()) {
throw new ProjectCreationFailedException("project \"" + nameKey + "\" exists");
}
throw err;
} finally {
repo.close();
}

View File

@@ -0,0 +1,57 @@
// Copyright (C) 2013 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.project;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.inject.Inject;
public class ProjectJson {
private final AllProjectsName allProjects;
@Inject
ProjectJson(AllProjectsName allProjects) {
this.allProjects = allProjects;
}
public ProjectInfo format(ProjectResource rsrc) {
return format(rsrc.getControl().getProject());
}
public ProjectInfo format(Project p) {
ProjectInfo info = new ProjectInfo();
info.name = p.getName();
Project.NameKey parentName = p.getParent(allProjects);
info.parent = parentName != null ? parentName.get() : null;
info.description = Strings.emptyToNull(p.getDescription());
info.finish();
return info;
}
static class ProjectInfo {
final String kind = "gerritcodereview#project";
String id;
String name;
String parent;
String description;
void finish() {
id = Url.encode(name);
}
}
}

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.project;
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.RestCollection;
@@ -27,21 +28,24 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
public class ProjectsCollection implements
RestCollection<TopLevelResource, ProjectResource> {
RestCollection<TopLevelResource, ProjectResource>,
AcceptsCreate<TopLevelResource> {
private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<ListProjects> list;
private final ProjectControl.GenericFactory controlFactory;
private final Provider<CurrentUser> user;
private final CreateProject.Factory createProjectFactory;
@Inject
ProjectsCollection(DynamicMap<RestView<ProjectResource>> views,
Provider<ListProjects> list,
ProjectControl.GenericFactory controlFactory,
Provider<CurrentUser> user) {
CreateProject.Factory factory, Provider<CurrentUser> user) {
this.views = views;
this.list = list;
this.controlFactory = controlFactory;
this.user = user;
this.createProjectFactory = factory;
}
@Override
@@ -52,10 +56,15 @@ public class ProjectsCollection implements
@Override
public ProjectResource parse(TopLevelResource parent, IdString id)
throws ResourceNotFoundException {
return parse(id.get());
}
public ProjectResource parse(String id)
throws ResourceNotFoundException {
ProjectControl ctl;
try {
ctl = controlFactory.controlFor(
new Project.NameKey(id.get()),
new Project.NameKey(id),
user.get());
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(id);
@@ -70,4 +79,10 @@ public class ProjectsCollection implements
public DynamicMap<RestView<ProjectResource>> views() {
return views;
}
@SuppressWarnings("unchecked")
@Override
public CreateProject create(TopLevelResource parent, IdString name) {
return createProjectFactory.create(name.get());
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2013 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.project;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.project.CreateProject.Input;
public class PutProject implements RestModifyView<ProjectResource, Input> {
@Override
public Object apply(ProjectResource resource, Input input)
throws ResourceConflictException {
throw new ResourceConflictException("Project \"" + resource.getName()
+ "\" already exists");
}
}