From 4425c746eb1337fdb563b69dcdde542ab78c4ba6 Mon Sep 17 00:00:00 2001 From: Edwin Kempin Date: Mon, 18 Mar 2013 13:23:00 +0100 Subject: [PATCH] Support listing child projects via REST It is now possible to list the direct child projects of a project via REST by GET on /projects/*/children/. Change-Id: If57b44118c6742f7d0bef751f2be0b8abebfc0a2 Signed-off-by: Edwin Kempin --- Documentation/rest-api-projects.txt | 52 ++++++++++ .../google/gerrit/acceptance/git/GitUtil.java | 17 +++- .../rest/project/ListChildProjectsIT.java | 99 +++++++++++++++++++ .../rest/project/ProjectAssert.java | 18 ++++ .../acceptance/rest/project/ProjectInfo.java | 5 + .../server/project/ChildProjectResource.java | 34 +++++++ .../project/ChildProjectsCollection.java | 54 ++++++++++ .../server/project/ListChildProjects.java | 55 +++++++++++ .../google/gerrit/server/project/Module.java | 4 + .../server/project/ProjectResource.java | 4 + 10 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt index f207d430b0..f169dcd6a1 100644 --- a/Documentation/rest-api-projects.txt +++ b/Documentation/rest-api-projects.txt @@ -483,6 +483,58 @@ The response is the streamed output of the garbage collection. done. ---- +[[child-project-endpoints]] +Child Project Endpoints +----------------------- + +[[list-child-projects]] +List Child Projects +~~~~~~~~~~~~~~~~~~~ +[verse] +'GET /projects/link:#project-name[\{project-name\}]/children/' + +List the direct child projects of a project. + +.Request +---- + GET /projects/Public-Plugins/children/ HTTP/1.0 +---- + +As result a list of link:#project-info[ProjectInfo] entries is +returned that describe the child projects. + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + [ + { + "kind": "gerritcodereview#project", + "id": "plugins%2Freplication", + "name": "plugins/replication", + "parent": "Public-Plugins", + "description": "Copies to other servers using the Git protocol" + }, + { + "kind": "gerritcodereview#project", + "id": "plugins%2Freviewnotes", + "name": "plugins/reviewnotes", + "parent": "Public-Plugins", + "description": "Annotates merged commits using notes on refs/notes/review." + }, + { + "kind": "gerritcodereview#project", + "id": "plugins%2Fsingleusergroup", + "name": "plugins/singleusergroup", + "parent": "Public-Plugins", + "description": "GroupBackend enabling users to be directly added to access rules" + } + ] +---- + [[dashboard-endpoints]] Dashboard Endpoints ------------------- diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java index fbf25762ad..1fb9bb53a7 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java @@ -18,6 +18,7 @@ import com.google.common.collect.Iterables; import com.google.gerrit.acceptance.SshSession; import com.google.gerrit.acceptance.TempFileUtil; import com.google.gerrit.acceptance.TestAccount; +import com.google.gerrit.reviewdb.client.Project; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; @@ -74,7 +75,21 @@ public class GitUtil { public static void createProject(SshSession s, String name) throws JSchException, IOException { - s.exec("gerrit create-project --empty-commit --name \"" + name + "\""); + createProject(s, name, null); + } + + public static void createProject(SshSession s, String name, Project.NameKey parent) + throws JSchException, IOException { + StringBuilder b = new StringBuilder(); + b.append("gerrit create-project --empty-commit --name \""); + b.append(name); + b.append("\""); + if (parent != null) { + b.append(" --parent \""); + b.append(parent.get()); + b.append("\""); + } + s.exec(b.toString()); } public static Git cloneProject(String url) throws GitAPIException, IOException { diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java new file mode 100644 index 0000000000..227f905d7b --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java @@ -0,0 +1,99 @@ +// 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.acceptance.rest.project; + +import static com.google.gerrit.acceptance.git.GitUtil.createProject; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjects; + +import com.google.gson.reflect.TypeToken; +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.AccountCreator; +import com.google.gerrit.acceptance.RestResponse; +import com.google.gerrit.acceptance.RestSession; +import com.google.gerrit.acceptance.SshSession; +import com.google.gerrit.acceptance.TestAccount; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gson.Gson; +import com.google.inject.Inject; + +import com.jcraft.jsch.JSchException; + +import org.apache.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class ListChildProjectsIT extends AbstractDaemonTest { + + @Inject + private AccountCreator accounts; + + @Inject + private AllProjectsName allProjects; + + private TestAccount admin; + private RestSession session; + + @Before + public void setUp() throws Exception { + admin = + accounts.create("admin", "admin@example.com", "Administrator", + "Administrators"); + session = new RestSession(admin); + } + + @Test + public void listChildrenOfNonExistingProject_NotFound() throws IOException { + assertEquals(HttpStatus.SC_NOT_FOUND, + GET("/projects/non-existing/children/").getStatusCode()); + } + + @Test + public void listNoChildren() throws IOException { + RestResponse r = GET("/projects/" + allProjects.get() + "/children/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + List children = + (new Gson()).fromJson(r.getReader(), + new TypeToken>() {}.getType()); + assertTrue(children.isEmpty()); + } + + @Test + public void listChildren() throws IOException, JSchException { + SshSession sshSession = new SshSession(admin); + Project.NameKey child1 = new Project.NameKey("p1"); + createProject(sshSession, child1.get()); + Project.NameKey child2 = new Project.NameKey("p2"); + createProject(sshSession, child2.get()); + createProject(sshSession, "p1.1", child1); + + RestResponse r = GET("/projects/" + allProjects.get() + "/children/"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + List children = + (new Gson()).fromJson(r.getReader(), + new TypeToken>() {}.getType()); + assertProjects(Arrays.asList(child1, child2), children); + } + + private RestResponse GET(String endpoint) throws IOException { + return session.get(endpoint); + } +} diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java index 25ccbee94c..224d59d9f8 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java @@ -15,18 +15,36 @@ package com.google.gerrit.acceptance.rest.project; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.google.common.base.Predicate; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.project.ProjectState; +import java.util.List; import java.util.Set; public class ProjectAssert { + public static void assertProjects(Iterable expected, + List actual) { + for (final Project.NameKey p : expected) { + ProjectInfo info = Iterables.find(actual, new Predicate() { + @Override + public boolean apply(ProjectInfo info) { + return new Project.NameKey(info.name).equals(p); + }}, null); + assertNotNull("missing project: " + p, info); + actual.remove(info); + } + assertTrue("unexpected projects: " + actual, actual.isEmpty()); + } + public static void assertProjectInfo(Project project, ProjectInfo info) { if (info.name != null) { // 'name' is not set if returned in a map diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java index 72dd2d6596..2e209d18aa 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java @@ -19,4 +19,9 @@ public class ProjectInfo { public String name; public String parent; public String description; + + @Override + public String toString() { + return name; + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java new file mode 100644 index 0000000000..5e5695fe48 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java @@ -0,0 +1,34 @@ +// 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.RestView; +import com.google.inject.TypeLiteral; + +public class ChildProjectResource extends ProjectResource { + public static final TypeLiteral> CHILD_PROJECT_KIND = + new TypeLiteral>() {}; + + private final ProjectControl child; + + ChildProjectResource(ProjectResource project, ProjectControl child) { + super(project); + this.child = child; + } + + public ProjectControl getChild() { + return child; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java new file mode 100644 index 0000000000..3d1c143af7 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java @@ -0,0 +1,54 @@ +// 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.registration.DynamicMap; +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.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.inject.Inject; +import com.google.inject.Provider; + +public class ChildProjectsCollection implements + ChildCollection { + private final Provider list; + private final DynamicMap> views; + + @Inject + ChildProjectsCollection(Provider list, + DynamicMap> views) { + this.list = list; + this.views = views; + } + + @Override + public RestView list() throws ResourceNotFoundException, + AuthException { + return list.get(); + } + + @Override + public ChildProjectResource parse(ProjectResource parent, IdString id) + throws ResourceNotFoundException { + throw new ResourceNotFoundException(id); + } + + @Override + public DynamicMap> views() { + return views; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java new file mode 100644 index 0000000000..e3019214ab --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java @@ -0,0 +1,55 @@ +// 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.collect.Lists; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gerrit.server.project.ProjectJson.ProjectInfo; +import com.google.inject.Inject; + +import java.util.List; + +public class ListChildProjects implements RestReadView { + + private final ProjectCache projectCache; + private final AllProjectsName allProjects; + private final ProjectJson json; + + @Inject + ListChildProjects(ProjectCache projectCache, AllProjectsName allProjects, + ProjectJson json) { + this.projectCache = projectCache; + this.allProjects = allProjects; + this.json = json; + } + + @Override + public List apply(ProjectResource rsrc) { + List childProjects = Lists.newArrayList(); + for (Project.NameKey projectName : projectCache.all()) { + ProjectState e = projectCache.get(projectName); + if (e == null) { + // If we can't get it from the cache, pretend it's not present. + continue; + } + if (rsrc.getNameKey().equals(e.getProject().getParent(allProjects))) { + childProjects.add(json.format(e.getProject())); + } + } + return childProjects; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java index 1c61d96879..a6e8a3698e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java @@ -14,6 +14,7 @@ package com.google.gerrit.server.project; +import static com.google.gerrit.server.project.ChildProjectResource.CHILD_PROJECT_KIND; import static com.google.gerrit.server.project.DashboardResource.DASHBOARD_KIND; import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND; @@ -29,6 +30,7 @@ public class Module extends RestApiModule { bind(DashboardsCollection.class); DynamicMap.mapOf(binder(), PROJECT_KIND); + DynamicMap.mapOf(binder(), CHILD_PROJECT_KIND); DynamicMap.mapOf(binder(), DASHBOARD_KIND); put(PROJECT_KIND).to(PutProject.class); @@ -40,6 +42,8 @@ public class Module extends RestApiModule { get(PROJECT_KIND, "parent").to(GetParent.class); put(PROJECT_KIND, "parent").to(SetParent.class); + child(PROJECT_KIND, "children").to(ChildProjectsCollection.class); + get(PROJECT_KIND, "HEAD").to(GetHead.class); put(PROJECT_KIND, "HEAD").to(SetHead.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java index aeca0e8f99..39a51878e8 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java @@ -29,6 +29,10 @@ public class ProjectResource implements RestResource { this.control = control; } + ProjectResource(ProjectResource rsrc) { + this.control = rsrc.getControl(); + } + public String getName() { return control.getProject().getName(); }