diff options
21 files changed, 763 insertions, 28 deletions
diff --git a/it/it-tests/src/test/java/it/Category4Suite.java b/it/it-tests/src/test/java/it/Category4Suite.java index c1460fc9218..620f4379cab 100644 --- a/it/it-tests/src/test/java/it/Category4Suite.java +++ b/it/it-tests/src/test/java/it/Category4Suite.java @@ -22,7 +22,8 @@ package it; import com.sonar.orchestrator.Orchestrator; import it.analysisExclusion.FileExclusionsTest; import it.analysisExclusion.IssueExclusionsTest; -import it.componentSearch.ProjectSearchTest; +import it.component.ComponentsWsTest; +import it.component.ProjectSearchTest; import it.dbCleaner.PurgeTest; import it.duplication.CrossProjectDuplicationsOnRemoveFileTest; import it.duplication.CrossProjectDuplicationsTest; @@ -50,8 +51,9 @@ import static util.ItUtils.xooPlugin; // user ForceAuthenticationTest.class, FavouriteTest.class, - // project search + // component search ProjectSearchTest.class, + ComponentsWsTest.class, // update center UpdateCenterTest.class, // analysis exclusion diff --git a/it/it-tests/src/test/java/it/component/ComponentsWsTest.java b/it/it-tests/src/test/java/it/component/ComponentsWsTest.java new file mode 100644 index 00000000000..3a518d41f04 --- /dev/null +++ b/it/it-tests/src/test/java/it/component/ComponentsWsTest.java @@ -0,0 +1,70 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package it.component; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarRunner; +import it.Category4Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.component.SearchWsRequest; +import org.sonarqube.ws.client.component.ShowWsRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class ComponentsWsTest { + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + private static final String FILE_KEY = "sample:src/main/xoo/sample/Sample.xoo"; + WsClient wsClient; + + @Before + public void inspectProject() { + orchestrator.resetData(); + orchestrator.executeBuild(SonarRunner.create(projectDir("shared/xoo-sample"))); + + wsClient = ItUtils.newAdminWsClient(orchestrator); + } + + @Test + public void show() { + WsComponents.ShowWsResponse response = wsClient.components().show(new ShowWsRequest().setKey(FILE_KEY)); + + assertThat(response).isNotNull(); + assertThat(response.getComponent().getKey()).isEqualTo(FILE_KEY); + assertThat(response.getAncestorsList()).isNotEmpty(); + } + + @Test + public void search() { + WsComponents.SearchWsResponse response = wsClient.components().search(new SearchWsRequest() + .setQualifiers(singletonList("FIL"))); + + assertThat(response).isNotNull(); + assertThat(response.getComponents(0).getKey()).isEqualTo(FILE_KEY); + } +} diff --git a/it/it-tests/src/test/java/it/componentSearch/ProjectSearchTest.java b/it/it-tests/src/test/java/it/component/ProjectSearchTest.java index e13068469af..5e5f47fedb7 100644 --- a/it/it-tests/src/test/java/it/componentSearch/ProjectSearchTest.java +++ b/it/it-tests/src/test/java/it/component/ProjectSearchTest.java @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package it.componentSearch; +package it.component; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.SonarRunner; diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java index 2b6896679dc..e9fd3333ada 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java @@ -33,6 +33,7 @@ public class ComponentsWsModule extends Module { AppAction.class, SearchAction.class, TreeAction.class, + ShowAction.class, SearchViewComponentsAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java new file mode 100644 index 00000000000..dfc19ab3c40 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java @@ -0,0 +1,160 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component.ws; + +import java.util.List; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.component.ComponentFinder.ParamNames; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.WsComponents.ShowWsResponse; +import org.sonarqube.ws.client.component.ShowWsRequest; + +import static com.google.common.base.Objects.firstNonNull; +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SHOW; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ID; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_KEY; + +public class ShowAction implements ComponentsWsAction { + private final UserSession userSession; + private final DbClient dbClient; + private final ComponentFinder componentFinder; + + public ShowAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder) { + this.userSession = userSession; + this.dbClient = dbClient; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction(ACTION_SHOW) + .setDescription(format("Returns a component (file, directory, project, view…) and its ancestors. " + + "The ancestors are ordered from the parent to the root project. " + + "The '%s' or '%s' must be provided.<br>" + + "Requires one of the following permissions:" + + "<ul>" + + "<li>'Administer System'</li>" + + "<li>'Administer' rights on the specified project</li>" + + "<li>'Browse' on the specified project</li>" + + "</ul>", + PARAM_ID, PARAM_KEY)) + .setResponseExample(getClass().getResource("show-example.json")) + .setSince("5.4") + .setHandler(this); + + action.createParam(PARAM_ID) + .setDescription("Component id") + .setExampleValue(UUID_EXAMPLE_01); + + action.createParam(PARAM_KEY) + .setDescription("Component key") + .setExampleValue("net.java.openjdk:jdk7"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + ShowWsRequest showWsRequest = toShowWsRequest(request); + ShowWsResponse showWsResponse = doHandle(showWsRequest); + + writeProtobuf(showWsResponse, request, response); + } + + private ShowWsResponse doHandle(ShowWsRequest request) { + DbSession dbSession = dbClient.openSession(false); + try { + ComponentDto component = getComponentByUuidOrKey(dbSession, request); + SnapshotDto lastSnapshot = dbClient.snapshotDao().selectLastSnapshotByComponentId(dbSession, component.getId()); + List<ComponentDto> orderedAncestors = emptyList(); + if (lastSnapshot != null) { + ShowData.Builder showDataBuilder = ShowData.builder(lastSnapshot); + List<SnapshotDto> ancestorsSnapshots = dbClient.snapshotDao().selectByIds(dbSession, showDataBuilder.getOrderedSnapshotIds()); + showDataBuilder.withAncestorsSnapshots(ancestorsSnapshots); + List<ComponentDto> ancestorComponents = dbClient.componentDao().selectByIds(dbSession, showDataBuilder.getOrderedComponentIds()); + ShowData showData = showDataBuilder.andAncestorComponents(ancestorComponents); + orderedAncestors = showData.getComponents(); + } + + return buildResponse(component, orderedAncestors); + } finally { + dbClient.closeSession(dbSession); + } + } + + private static ShowWsResponse buildResponse(ComponentDto component, List<ComponentDto> orderedAncestorComponents) { + ShowWsResponse.Builder response = ShowWsResponse.newBuilder(); + response.setComponent(componentDtoToWsComponent(component)); + + for (ComponentDto ancestor : orderedAncestorComponents) { + response.addAncestors(componentDtoToWsComponent(ancestor)); + } + + return response.build(); + } + + private static WsComponents.Component.Builder componentDtoToWsComponent(ComponentDto dto) { + WsComponents.Component.Builder wsComponent = WsComponents.Component.newBuilder() + .setId(dto.uuid()) + .setKey(dto.key()) + .setName(dto.name()) + .setQualifier(dto.qualifier()); + if (!isNullOrEmpty(dto.path())) { + wsComponent.setPath(dto.path()); + } + if (!isNullOrEmpty(dto.description())) { + wsComponent.setDescription(dto.description()); + } + + return wsComponent; + } + + private ComponentDto getComponentByUuidOrKey(DbSession dbSession, ShowWsRequest request) { + ComponentDto component = componentFinder.getByUuidOrKey(dbSession, request.getId(), request.getKey(), ParamNames.ID_AND_KEY); + String projectUuid = firstNonNull(component.projectUuid(), component.uuid()); + if (!userSession.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN) && + !userSession.hasProjectPermissionByUuid(UserRole.ADMIN, projectUuid) && + !userSession.hasProjectPermissionByUuid(UserRole.USER, projectUuid)) { + throw insufficientPrivilegesException(); + } + return component; + } + + private static ShowWsRequest toShowWsRequest(Request request) { + return new ShowWsRequest() + .setId(request.param(PARAM_ID)) + .setKey(request.param(PARAM_KEY)); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowData.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowData.java new file mode 100644 index 00000000000..6e7d447e414 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowData.java @@ -0,0 +1,104 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component.ws; + +import com.google.common.base.Function; +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentDtoFunctions; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.component.SnapshotDtoFunctions; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +class ShowData { + private final List<ComponentDto> components; + + private ShowData(List<ComponentDto> components) { + this.components = components; + } + + static Builder builder(SnapshotDto snapshot) { + return new Builder(snapshot); + } + + List<ComponentDto> getComponents() { + return components; + } + + static class Builder { + private Ordering<SnapshotDto> snapshotOrdering; + private List<Long> orderedSnapshotIds; + private List<Long> orderedComponentIds; + + private Builder(SnapshotDto snapshot) { + List<String> orderedSnapshotIdsAsString = Splitter.on(".").omitEmptyStrings().splitToList(snapshot.getPath()); + orderedSnapshotIds = Lists.transform(orderedSnapshotIdsAsString, StringToLongFunction.INSTANCE); + snapshotOrdering = Ordering + .explicit(orderedSnapshotIds) + .onResultOf(SnapshotDtoFunctions.toId()) + .reverse(); + } + + Builder withAncestorsSnapshots(List<SnapshotDto> ancestorsSnapshots) { + checkNotNull(snapshotOrdering, "Snapshot must be set before the ancestors"); + checkState(orderedSnapshotIds.size() == ancestorsSnapshots.size(), "Missing ancestor"); + + orderedComponentIds = Lists.transform( + snapshotOrdering.immutableSortedCopy(ancestorsSnapshots), + SnapshotDtoFunctions.toComponentId()); + + return this; + } + + ShowData andAncestorComponents(List<ComponentDto> ancestorComponents) { + checkNotNull(orderedComponentIds, "Snapshot ancestors must be set before the component ancestors"); + checkState(orderedComponentIds.size() == ancestorComponents.size(), "Missing ancestor"); + + return new ShowData(Ordering + .explicit(orderedComponentIds) + .onResultOf(ComponentDtoFunctions.toId()) + .immutableSortedCopy(ancestorComponents)); + } + + List<Long> getOrderedSnapshotIds() { + return orderedSnapshotIds; + } + + List<Long> getOrderedComponentIds() { + return orderedComponentIds; + } + } + + private enum StringToLongFunction implements Function<String, Long> { + INSTANCE; + + @Override + public Long apply(@Nonnull String input) { + return Long.parseLong(input); + } + } +} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/component/ws/show-example.json b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/show-example.json new file mode 100644 index 00000000000..72a9988f652 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/show-example.json @@ -0,0 +1,25 @@ +{ + "component": { + "id": "AVIF-FffA3Ax6PH2efPD", + "key": "com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl/Rule.java", + "name": "Rule.java", + "qualifier": "FIL", + "path": "src/main/java/com/sonarsource/markdown/impl/Rule.java" + }, + "ancestors": [ + { + "id": "AVIF-FfgA3Ax6PH2efPF", + "key": "com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl", + "name": "src/main/java/com/sonarsource/markdown/impl", + "qualifier": "DIR", + "path": "src/main/java/com/sonarsource/markdown/impl" + }, + { + "id": "AVIF98jgA3Ax6PH2efOW", + "key": "com.sonarsource:java-markdown", + "name": "Java Markdown", + "description": "Java Markdown Project", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java index 5e854bb1f5f..dd31885a80d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java @@ -30,6 +30,6 @@ public class ComponentsWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new ComponentsWsModule().configure(container); - assertThat(container.size()).isEqualTo(7 + 2); + assertThat(container.size()).isEqualTo(8 + 2); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ShowActionTest.java new file mode 100644 index 00000000000..5e9500f4919 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ShowActionTest.java @@ -0,0 +1,169 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component.ws; + +import com.google.common.base.Throwables; +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; +import org.sonar.test.DbTests; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.WsComponents.ShowWsResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.ComponentTesting.newDirectory; +import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ID; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_KEY; + +@Category(DbTests.class) +public class ShowActionTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone() + .setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + ComponentDbTester componentDb = new ComponentDbTester(db); + + WsActionTester ws = new WsActionTester(new ShowAction(userSession, db.getDbClient(), new ComponentFinder(db.getDbClient()))); + + @Test + public void json_example() throws IOException { + insertJsonExampleComponentsAndSnapshots(); + + String response = ws.newRequest() + .setParam("id", "AVIF-FffA3Ax6PH2efPD") + .execute() + .getInput(); + + assertJson(response).isSimilarTo(getClass().getResource("show-example.json")); + } + + @Test + public void show_by_key_with_project_permission() { + userSession.anonymous().login().addProjectUuidPermissions(UserRole.ADMIN, "project-uuid"); + componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid").setKey("project-key")); + + ShowWsResponse response = newRequest(null, "project-key"); + + assertThat(response.getAncestorsCount()).isEqualTo(0); + assertThat(response.getComponent().getKey()).isEqualTo("project-key"); + } + + @Test + public void show_with_browse_permission() { + userSession.anonymous().addProjectUuidPermissions(UserRole.USER, "project-uuid"); + componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid")); + + ShowWsResponse response = newRequest("project-uuid", null); + + assertThat(response.getComponent().getId()).isEqualTo("project-uuid"); + } + + @Test + public void show_provided_project() { + componentDb.insertComponent(newProjectDto("project-uuid")); + + ShowWsResponse response = newRequest("project-uuid", null); + + assertThat(response.getComponent().getId()).isEqualTo("project-uuid"); + } + + @Test + public void fail_if_not_enough_privilege() { + userSession.anonymous().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); + expectedException.expect(ForbiddenException.class); + componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid")); + + newRequest("project-uuid", null); + } + + @Test + public void fail_if_component_does_not_exist() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Component id 'unknown-uuid' not found"); + + newRequest("unknown-uuid", null); + } + + private ShowWsResponse newRequest(@Nullable String uuid, @Nullable String key) { + TestRequest request = ws.newRequest() + .setMediaType(MediaTypes.PROTOBUF); + + if (uuid != null) { + request.setParam(PARAM_ID, uuid); + } + if (key != null) { + request.setParam(PARAM_KEY, key); + } + + try(InputStream responseStream = request.execute().getInputStream()) { + return ShowWsResponse.parseFrom(responseStream); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + private void insertJsonExampleComponentsAndSnapshots() { + ComponentDto project = newProjectDto("AVIF98jgA3Ax6PH2efOW") + .setKey("com.sonarsource:java-markdown") + .setName("Java Markdown") + .setDescription("Java Markdown Project") + .setQualifier(Qualifiers.PROJECT); + SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + ComponentDto directory = newDirectory(project, "AVIF-FfgA3Ax6PH2efPF", "src/main/java/com/sonarsource/markdown/impl") + .setKey("com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl") + .setName("src/main/java/com/sonarsource/markdown/impl") + .setQualifier(Qualifiers.DIRECTORY); + SnapshotDto directorySnapshot = componentDb.insertComponentAndSnapshot( + directory, + projectSnapshot); + componentDb.insertComponentAndSnapshot( + newFileDto(directory, "AVIF-FffA3Ax6PH2efPD") + .setKey("com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl/Rule.java") + .setName("Rule.java") + .setPath("src/main/java/com/sonarsource/markdown/impl/Rule.java") + .setQualifier(Qualifiers.FILE), + directorySnapshot); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java index cffa000bc2f..dc0cf55afe5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java @@ -28,6 +28,7 @@ import com.google.gson.JsonParser; import java.io.IOException; import java.io.InputStream; import java.util.Date; +import javax.annotation.CheckForNull; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Rule; @@ -361,15 +362,15 @@ public class TreeActionTest { for (JsonElement componentAsJsonElement : components) { JsonObject componentAsJsonObject = componentAsJsonElement.getAsJsonObject(); componentDb.insertComponentAndSnapshot(new ComponentDto() - .setUuid(getJsonField(componentAsJsonObject, "id")) - .setKey(getJsonField(componentAsJsonObject, "key")) - .setName(getJsonField(componentAsJsonObject, "name")) - .setPath(getJsonField(componentAsJsonObject, "path")) - .setProjectUuid(project.projectUuid()) - .setQualifier(getJsonField(componentAsJsonObject, "qualifier")) - .setDescription(getJsonField(componentAsJsonObject, "description")) - .setEnabled(true) - .setCreatedAt(now), + .setUuid(getJsonField(componentAsJsonObject, "id")) + .setKey(getJsonField(componentAsJsonObject, "key")) + .setName(getJsonField(componentAsJsonObject, "name")) + .setPath(getJsonField(componentAsJsonObject, "path")) + .setProjectUuid(project.projectUuid()) + .setQualifier(getJsonField(componentAsJsonObject, "qualifier")) + .setDescription(getJsonField(componentAsJsonObject, "description")) + .setEnabled(true) + .setCreatedAt(now), projectSnapshot); } db.commit(); @@ -377,6 +378,7 @@ public class TreeActionTest { return project; } + @CheckForNull private static String getJsonField(JsonObject jsonObject, String field) { JsonElement jsonElement = jsonObject.get(field); return jsonElement == null ? null : jsonElement.getAsString(); diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentDtoFunctions.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentDtoFunctions.java index c75518ebf86..9116cc9eac0 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/ComponentDtoFunctions.java +++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentDtoFunctions.java @@ -34,6 +34,32 @@ public final class ComponentDtoFunctions { return ToKey.INSTANCE; } + public static Function<ComponentDto, Long> toId() { + return ToId.INSTANCE; + } + + public static Function<ComponentDto, String> toProjectUuid() { + return ToProjectUuid.INSTANCE; + } + + public static Function<ComponentDto, String> toUuid() { + return ToUuid.INSTANCE; + } + + public static Function<ComponentDto, Long> toCopyResourceId() { + return ToCopyResourceId.INSTANCE; + } + + private enum ToId implements Function<ComponentDto, Long> { + INSTANCE; + + @Override + public Long apply(@Nonnull ComponentDto input) { + return input.getId(); + } + } + + private enum ToKey implements Function<ComponentDto, String> { INSTANCE; @@ -43,10 +69,6 @@ public final class ComponentDtoFunctions { } } - public static Function<ComponentDto, String> toProjectUuid() { - return ToProjectUuid.INSTANCE; - } - private enum ToProjectUuid implements Function<ComponentDto, String> { INSTANCE; @@ -56,10 +78,6 @@ public final class ComponentDtoFunctions { } } - public static Function<ComponentDto, String> toUuid() { - return ToUuid.INSTANCE; - } - private enum ToUuid implements Function<ComponentDto, String> { INSTANCE; @@ -69,10 +87,6 @@ public final class ComponentDtoFunctions { } } - public static Function<ComponentDto, Long> toCopyResourceId() { - return ToCopyResourceId.INSTANCE; - } - private enum ToCopyResourceId implements Function<ComponentDto, Long> { INSTANCE; diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java index 4ef0cea7fa4..fc3dead424b 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java +++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java @@ -20,11 +20,13 @@ package org.sonar.db.component; +import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.Lists; import java.util.Collection; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.ibatis.session.RowBounds; import org.sonar.api.resources.Scopes; @@ -34,6 +36,7 @@ import org.sonar.db.RowNotFoundException; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.FluentIterable.from; +import static org.sonar.db.DatabaseUtils.executeLargeInputs; public class SnapshotDao implements Dao { @@ -54,6 +57,16 @@ public class SnapshotDao implements Dao { return value; } + public List<SnapshotDto> selectByIds(final DbSession dbSession, List<Long> snapshotIds) { + return executeLargeInputs(snapshotIds, new Function<List<Long>, List<SnapshotDto>>() { + @Nonnull + @Override + public List<SnapshotDto> apply(@Nonnull List<Long> input) { + return mapper(dbSession).selectByIds(input); + } + }); + } + @CheckForNull public SnapshotDto selectLastSnapshotByComponentId(DbSession session, long componentId) { return mapper(session).selectLastSnapshot(componentId); diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotDtoFunctions.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDtoFunctions.java new file mode 100644 index 00000000000..db46706b184 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDtoFunctions.java @@ -0,0 +1,54 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.db.component; + +import com.google.common.base.Function; +import javax.annotation.Nonnull; + +public class SnapshotDtoFunctions { + public static Function<SnapshotDto, Long> toId() { + return ToId.INSTANCE; + } + + public static Function<SnapshotDto, Long> toComponentId() { + return ToComponentId.INSTANCE; + } + + private enum ToId implements Function<SnapshotDto, Long> { + INSTANCE; + + @Override + public Long apply(@Nonnull SnapshotDto input) { + return input.getId(); + } + } + + private enum ToComponentId implements Function<SnapshotDto, Long> { + INSTANCE; + + @Override + public Long apply(@Nonnull SnapshotDto input) { + return input.getComponentId(); + } + } + + +} diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java index 043390bdf13..b6a43bd6377 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java @@ -30,6 +30,8 @@ public interface SnapshotMapper { @CheckForNull SnapshotDto selectByKey(long id); + List<SnapshotDto> selectByIds(@Param("ids") List<Long> ids); + void insert(SnapshotDto snapshot); @CheckForNull @@ -53,7 +55,7 @@ public interface SnapshotMapper { int updateSnapshotAndChildrenLastFlag(@Param(value = "root") Long rootId, @Param(value = "pathRootId") Long pathRootId, @Param(value = "path") String path, @Param(value = "isLast") boolean isLast); - + List<ViewsSnapshotDto> selectSnapshotBefore(@Param("componentId") long componentId, @Param("date") long date); ViewsSnapshotDto selectLatestSnapshot(@Param("componentId") long componentId); diff --git a/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml index a8531b0a407..1c408c558e2 100644 --- a/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml @@ -49,6 +49,17 @@ </where> </select> + <select id="selectByIds" parameterType="Long" resultType="Snapshot"> + SELECT + <include refid="snapshotColumns"/> + FROM snapshots s + WHERE + s.id in + <foreach collection="ids" item="id" separator="," open="(" close=")"> + #{id} + </foreach> + </select> + <select id="selectLastSnapshot" resultType="Snapshot"> select <include refid="snapshotColumns"/> diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java index 74932cfe65e..523bf5873e7 100644 --- a/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java +++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java @@ -65,6 +65,7 @@ public class ComponentDbTester { public ComponentDto insertComponent(ComponentDto component) { dbClient.componentDao().insert(dbSession, component); + db.commit(); return component; } diff --git a/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java index 2f8639db024..6d3f406fa0a 100644 --- a/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java @@ -30,11 +30,14 @@ import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.test.DbTests; +import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.ComponentTesting.newProjectDto; import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE; import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC; import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.DESC; @@ -48,7 +51,8 @@ public class SnapshotDaoTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); - + ComponentDbTester componentDb = new ComponentDbTester(db); + DbClient dbClient = db.getDbClient(); DbSession dbSession = db.getSession(); SnapshotDao underTest = db.getDbClient().snapshotDao(); @@ -96,6 +100,18 @@ public class SnapshotDaoTest { } @Test + public void select_by_ids() { + SnapshotDto snapshot1 = componentDb.insertProjectAndSnapshot(newProjectDto()); + SnapshotDto snapshot2 = componentDb.insertProjectAndSnapshot(newProjectDto()); + SnapshotDto snapshot3 = componentDb.insertProjectAndSnapshot(newProjectDto()); + + List<SnapshotDto> result = underTest.selectByIds(dbSession, newArrayList(snapshot1.getId(), snapshot2.getId(), snapshot3.getId(), 42L)); + + assertThat(result).hasSize(3); + assertThat(result).extracting("id").containsOnly(snapshot1.getId(), snapshot2.getId(), snapshot3.getId()); + } + + @Test public void lastSnapshot_returns_null_when_no_last_snapshot() { SnapshotDto snapshot = underTest.selectLastSnapshotByComponentId(db.getSession(), 123L); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java index fcee519bdb0..2630839acf4 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java @@ -20,11 +20,23 @@ package org.sonarqube.ws.client.component; +import com.google.common.base.Joiner; import org.sonarqube.ws.WsComponents.SearchWsResponse; +import org.sonarqube.ws.WsComponents.ShowWsResponse; +import org.sonarqube.ws.WsComponents.TreeWsResponse; import org.sonarqube.ws.client.BaseService; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsConnector; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SHOW; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_TREE; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BASE_COMPONENT_ID; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BASE_COMPONENT_KEY; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ID; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_KEY; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY; + public class ComponentsService extends BaseService { public ComponentsService(WsConnector wsConnector) { @@ -33,10 +45,30 @@ public class ComponentsService extends BaseService { public SearchWsResponse search(SearchWsRequest request) { GetRequest get = new GetRequest(path("search")) - .setParam("qualifiers", request.getQualifiers()) + .setParam("qualifiers", Joiner.on(",").join(request.getQualifiers())) .setParam("p", request.getPage()) .setParam("ps", request.getPageSize()) .setParam("q", request.getQuery()); return call(get, SearchWsResponse.parser()); } + + public TreeWsResponse tree(TreeWsRequest request) { + GetRequest get = new GetRequest(path(ACTION_TREE)) + .setParam(PARAM_BASE_COMPONENT_ID, request.getBaseComponentId()) + .setParam(PARAM_BASE_COMPONENT_KEY, request.getBaseComponentKey()) + .setParam(PARAM_QUALIFIERS, request.getQualifiers()) + .setParam(PARAM_STRATEGY, request.getStrategy()) + .setParam("p", request.getPage()) + .setParam("ps", request.getPageSize()) + .setParam("q", request.getQuery()) + .setParam("s", request.getSort()); + return call(get, TreeWsResponse.parser()); + } + + public ShowWsResponse show(ShowWsRequest request) { + GetRequest get = new GetRequest(path(ACTION_SHOW)) + .setParam(PARAM_ID, request.getId()) + .setParam(PARAM_KEY, request.getKey()); + return call(get, ShowWsResponse.parser()); + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java index b9739c4480e..02d6fd688bc 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java @@ -27,6 +27,7 @@ public class ComponentsWsParameters { //actions public static final String ACTION_TREE = "tree"; + public static final String ACTION_SHOW = "show"; // parameters public static final String PARAM_QUALIFIERS = "qualifiers"; @@ -34,4 +35,6 @@ public class ComponentsWsParameters { public static final String PARAM_BASE_COMPONENT_ID = "baseComponentId"; public static final String PARAM_BASE_COMPONENT_KEY = "baseComponentKey"; public static final String PARAM_STRATEGY = "strategy"; + public static final String PARAM_ID = "id"; + public static final String PARAM_KEY = "key"; } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ShowWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ShowWsRequest.java new file mode 100644 index 00000000000..37a7c77f040 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ShowWsRequest.java @@ -0,0 +1,49 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.ws.client.component; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class ShowWsRequest { + @CheckForNull + private String id; + @CheckForNull + private String key; + + public String getId() { + return id; + } + + public ShowWsRequest setId(@Nullable String id) { + this.id = id; + return this; + } + + public String getKey() { + return key; + } + + public ShowWsRequest setKey(@Nullable String key) { + this.key = key; + return this; + } +} diff --git a/sonar-ws/src/main/protobuf/ws-components.proto b/sonar-ws/src/main/protobuf/ws-components.proto index 03ded8c9bb7..d9e86b0e0e4 100644 --- a/sonar-ws/src/main/protobuf/ws-components.proto +++ b/sonar-ws/src/main/protobuf/ws-components.proto @@ -39,6 +39,13 @@ message TreeWsResponse { repeated Component components = 3; } +// WS api/components/show +message ShowWsResponse { + optional sonarqube.ws.commons.Paging paging = 1; + optional Component component = 2; + repeated Component ancestors = 3; +} + message Component { optional string id = 1; optional string key = 2; |