import org.sonar.server.exceptions.NotFoundException;
import javax.annotation.CheckForNull;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public ComponentDto getByUuid(DbSession session, String uuid) {
ComponentDto componentDto = getNullableByUuid(session, uuid);
if (componentDto == null) {
- throw new NotFoundException(String.format("Project with uuid '%s' not found", uuid));
+ throw new NotFoundException(String.format("Component with uuid '%s' not found", uuid));
}
return componentDto;
}
--- /dev/null
+/*
+ * 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.design.ws;
+
+import org.sonar.api.server.ws.WebService;
+
+public class DependenciesWs implements WebService {
+
+ private final ShowAction showAction;
+
+ public DependenciesWs(ShowAction showAction) {
+ this.showAction = showAction;
+ }
+
+ @Override
+ public void define(Context context) {
+ NewController controller = context.createController("api/dependencies")
+ .setSince("5.2");
+
+ showAction.define(controller);
+
+ controller.done();
+ }
+
+}
--- /dev/null
+/*
+ * 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.design.ws;
+
+import com.google.common.io.Resources;
+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.utils.text.JsonWriter;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.design.FileDependencyDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.user.ws.BaseUsersWsAction;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ShowAction implements BaseUsersWsAction {
+
+ private static final String PARAM_FROM_PARENT_UUID = "fromParentUuid";
+ private static final String PARAM_TO_PARENT_UUID = "toParentUuid";
+
+ private final DbClient dbClient;
+
+ public ShowAction(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void define(WebService.NewController controller) {
+ WebService.NewAction action = controller.createAction("show")
+ .setDescription("Show file dependencies between two directories")
+ .setSince("5.2")
+ .setInternal(true)
+ .setHandler(this)
+ .setResponseExample(Resources.getResource(getClass(), "example-show.json"));
+
+ action.createParam(PARAM_FROM_PARENT_UUID)
+ .setDescription("First directory uuid")
+ .setRequired(true)
+ .setExampleValue("2312cd03-b514-4acc-94f4-5c5e8e0062b2");
+
+ action.createParam(PARAM_TO_PARENT_UUID)
+ .setDescription("Second directory uuid")
+ .setRequired(true)
+ .setExampleValue("d38641a2-3166-451d-a2db-ab3b82e2d3ca");
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ String fromParentUuid = request.mandatoryParam(PARAM_FROM_PARENT_UUID);
+ String toParentUuid = request.mandatoryParam(PARAM_TO_PARENT_UUID);
+
+ DbSession session = dbClient.openSession(false);
+ try {
+ ComponentDto fromParent = dbClient.componentDao().getByUuid(session, fromParentUuid);
+ ComponentDto project = dbClient.componentDao().getByUuid(session, fromParent.projectUuid());
+ UserSession.get().checkProjectUuidPermission(UserRole.USER, project.uuid());
+
+ List<FileDependencyDto> fileDependencies = dbClient.fileDependencyDao().selectFromParents(session, fromParentUuid, toParentUuid, project.getId());
+ writeResponse(response, fileDependencies, componentsByUuid(session, fileDependencies));
+ } finally {
+ session.close();
+ }
+ }
+
+ private void writeResponse(Response response, List<FileDependencyDto> fileDependencies, Map<String, ComponentDto> componentsByUuid) {
+ JsonWriter json = response.newJsonWriter().beginObject();
+ json.name("dependencies").beginArray();
+ for (FileDependencyDto fileDependencyDto : fileDependencies) {
+ ComponentDto fromComponent = getComponent(fileDependencyDto.getFromComponentUuid(), componentsByUuid);
+ ComponentDto toComponent = getComponent(fileDependencyDto.getToComponentUuid(), componentsByUuid);
+ json.beginObject()
+ .prop("fromUuid", fromComponent.uuid())
+ .prop("fromName", fromComponent.longName())
+ .prop("toUuid", toComponent.uuid())
+ .prop("toName", toComponent.longName())
+ .prop("weight", fileDependencyDto.getWeight())
+ .endObject();
+ }
+ json.endArray().endObject().close();
+ }
+
+ private Map<String, ComponentDto> componentsByUuid(DbSession session, List<FileDependencyDto> fileDependencies) {
+ Set<String> uuids = new HashSet<>();
+ for (FileDependencyDto fileDependency : fileDependencies) {
+ uuids.add(fileDependency.getFromComponentUuid());
+ uuids.add(fileDependency.getToComponentUuid());
+ }
+ Map<String, ComponentDto> componentsByUuid = new HashMap<>();
+ for (ComponentDto componentDto : dbClient.componentDao().getByUuids(session, uuids)) {
+ componentsByUuid.put(componentDto.uuid(), componentDto);
+ }
+ return componentsByUuid;
+ }
+
+ private static ComponentDto getComponent(String uuid, Map<String, ComponentDto> componentsByUuid) {
+ ComponentDto component = componentsByUuid.get(uuid);
+ if (component == null) {
+ throw new IllegalStateException(String.format("Component with uuid '%s' does not exists", uuid));
+ }
+ return component;
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.design.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import org.sonar.server.debt.DebtModelXMLExporter;
import org.sonar.server.debt.DebtRulesXMLImporter;
import org.sonar.server.design.FileDesignWidget;
+import org.sonar.server.design.db.FileDependencyDao;
+import org.sonar.server.design.ws.DependenciesWs;
import org.sonar.server.duplication.ws.DuplicationsJsonWriter;
import org.sonar.server.duplication.ws.DuplicationsParser;
import org.sonar.server.duplication.ws.DuplicationsWs;
EventDao.class,
ActivityDao.class,
AnalysisReportDao.class,
- FileSourceDao.class
+ FileSourceDao.class,
+ FileDependencyDao.class
));
components.addAll(CorePropertyDefinitions.all());
components.addAll(MigrationSteps.CLASSES);
// Design
pico.addSingleton(FileDesignWidget.class);
+ pico.addSingleton(DependenciesWs.class);
+ pico.addSingleton(org.sonar.server.design.ws.ShowAction.class);
// System
pico.addSingletons(Arrays.asList(
--- /dev/null
+{
+ "dependencies":[
+ {
+ "fromUuid": "ae8ef766-269e-481e-b89e-0a3cf52f0feb",
+ "fromName": "src/main/java/org/sonar/core/issue/db/ActionPlanDao.java",
+ "toUuid": "5e96f736-1b2d-46d9-a071-b9a44e123438",
+ "toName": "src/main/java/org/sonar/core/persistence/DaoComponent.java",
+ "weight": 1
+ }
+ ]
+}
--- /dev/null
+/*
+ * 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.design.ws;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.ws.WsTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class DependenciesWsTest {
+
+ WebService.Controller controller;
+
+ @Before
+ public void setUp() throws Exception {
+ WsTester tester = new WsTester(new DependenciesWs(new ShowAction(mock(DbClient.class))));
+ controller = tester.controller("api/dependencies");
+ }
+
+ @Test
+ public void define_controller() throws Exception {
+ assertThat(controller).isNotNull();
+ assertThat(controller.description()).isNull();
+ assertThat(controller.since()).isEqualTo("5.2");
+ assertThat(controller.actions()).hasSize(1);
+ }
+
+ @Test
+ public void define_search_action() throws Exception {
+ WebService.Action action = controller.action("show");
+ assertThat(action).isNotNull();
+ assertThat(action.isPost()).isFalse();
+ assertThat(action.responseExampleAsString()).isNotEmpty();
+ assertThat(action.isInternal()).isTrue();
+ assertThat(action.params()).hasSize(2);
+ }
+
+}
--- /dev/null
+/*
+ * 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.design.ws;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.component.SnapshotDto;
+import org.sonar.core.design.FileDependencyDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.server.component.ComponentTesting;
+import org.sonar.server.component.SnapshotTesting;
+import org.sonar.server.component.db.ComponentDao;
+import org.sonar.server.component.db.SnapshotDao;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.design.db.FileDependencyDao;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.MockUserSession;
+import org.sonar.server.ws.WsTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
+
+public class ShowActionTest {
+
+ private static final String PROJECT_UUID = "PROJECT";
+ private static final String DIR1_UUID = "DIR1";
+ private static final String FILE1_UUID = "FILE1";
+ private static final String DIR2_UUID = "DIR2";
+ private static final String FILE2_UUID = "FILE2";
+ private static final String UNKNOWN_UUID = "UNKNOWN";
+
+ Long projectSnapshotId;
+
+ @ClassRule
+ public static DbTester dbTester = new DbTester();
+
+ DbClient dbClient;
+
+ DbSession session;
+
+ WebService.Controller controller;
+
+ WsTester tester;
+
+ @Before
+ public void setUp() throws Exception {
+ dbTester.truncateTables();
+ dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new ComponentDao(), new SnapshotDao(System2.INSTANCE), new FileDependencyDao());
+ session = dbClient.openSession(false);
+ tester = new WsTester(new DependenciesWs(new ShowAction(dbClient)));
+ controller = tester.controller("api/dependencies");
+
+ initComponents();
+ }
+
+ @After
+ public void after() {
+ session.close();
+ }
+
+ @Test
+ public void return_file_dependencies() throws Exception {
+ dbClient.fileDependencyDao().insert(session, new FileDependencyDto()
+ .setFromComponentUuid(FILE1_UUID)
+ .setToComponentUuid(FILE2_UUID)
+ .setFromParentUuid(DIR1_UUID)
+ .setToParentUuid(DIR2_UUID)
+ .setRootProjectSnapshotId(projectSnapshotId)
+ .setWeight(2)
+ .setCreatedAt(1000L));
+ session.commit();
+
+ MockUserSession.set().addProjectUuidPermissions(UserRole.USER, PROJECT_UUID);
+
+ tester.newGetRequest("api/dependencies", "show")
+ .setParam("fromParentUuid", DIR1_UUID)
+ .setParam("toParentUuid", DIR2_UUID)
+ .execute()
+ .assertJson(getClass(), "return_file_dependencies.json");
+ }
+
+ @Test
+ public void return_nothing() throws Exception {
+ MockUserSession.set().addProjectUuidPermissions(UserRole.USER, PROJECT_UUID);
+
+ tester.newGetRequest("api/dependencies", "show")
+ .setParam("fromParentUuid", DIR1_UUID)
+ .setParam("toParentUuid", DIR2_UUID)
+ .execute()
+ .assertJson(getClass(), "return_nothing.json");
+ }
+
+ @Test(expected = ForbiddenException.class)
+ public void fail_if_no_user_permission_on_project() throws Exception {
+ MockUserSession.set().addProjectUuidPermissions(UserRole.CODEVIEWER, PROJECT_UUID);
+
+ tester.newGetRequest("api/dependencies", "show")
+ .setParam("fromParentUuid", DIR1_UUID)
+ .setParam("toParentUuid", DIR2_UUID)
+ .execute()
+ .assertJson(getClass(), "return_nothing.json");
+ }
+
+ @Test
+ public void fail_if_from_parent_uuid_does_not_exists() throws Exception {
+ dbClient.fileDependencyDao().insert(session, new FileDependencyDto()
+ .setFromComponentUuid(FILE1_UUID)
+ .setToComponentUuid(FILE2_UUID)
+ .setFromParentUuid(DIR1_UUID)
+ .setToParentUuid(DIR2_UUID)
+ .setRootProjectSnapshotId(projectSnapshotId)
+ .setWeight(2)
+ .setCreatedAt(1000L));
+ session.commit();
+
+ MockUserSession.set().addProjectUuidPermissions(UserRole.USER, PROJECT_UUID);
+
+ try {
+ tester.newGetRequest("api/dependencies", "show")
+ .setParam("fromParentUuid", UNKNOWN_UUID)
+ .setParam("toParentUuid", DIR2_UUID)
+ .execute()
+ .assertJson(getClass(), "return_file_dependencies.json");
+ failBecauseExceptionWasNotThrown(NotFoundException.class);
+ } catch (NotFoundException e) {
+ assertThat(e).hasMessage("Component with uuid 'UNKNOWN' not found");
+ }
+ }
+
+ @Test
+ public void return_nothing_if_to_parent_uuid_does_not_exists() throws Exception {
+ dbClient.fileDependencyDao().insert(session, new FileDependencyDto()
+ .setFromComponentUuid(FILE1_UUID)
+ .setToComponentUuid(FILE2_UUID)
+ .setFromParentUuid(DIR1_UUID)
+ .setToParentUuid(DIR2_UUID)
+ .setRootProjectSnapshotId(projectSnapshotId)
+ .setWeight(2)
+ .setCreatedAt(1000L));
+ session.commit();
+
+ MockUserSession.set().addProjectUuidPermissions(UserRole.USER, PROJECT_UUID);
+
+ tester.newGetRequest("api/dependencies", "show")
+ .setParam("fromParentUuid", DIR1_UUID)
+ .setParam("toParentUuid", UNKNOWN_UUID)
+ .execute()
+ .assertJson(getClass(), "return_nothing.json");
+ }
+
+ @Test
+ public void fail_if_from_component_uuid_does_not_exists() throws Exception {
+ dbClient.fileDependencyDao().insert(session, new FileDependencyDto()
+ .setFromComponentUuid(UNKNOWN_UUID)
+ .setToComponentUuid(FILE2_UUID)
+ .setFromParentUuid(DIR1_UUID)
+ .setToParentUuid(DIR2_UUID)
+ .setRootProjectSnapshotId(projectSnapshotId)
+ .setWeight(2)
+ .setCreatedAt(1000L));
+ session.commit();
+
+ MockUserSession.set().addProjectUuidPermissions(UserRole.USER, PROJECT_UUID);
+
+ try {
+ tester.newGetRequest("api/dependencies", "show")
+ .setParam("fromParentUuid", DIR1_UUID)
+ .setParam("toParentUuid", DIR2_UUID)
+ .execute()
+ .assertJson(getClass(), "return_file_dependencies.json");
+ failBecauseExceptionWasNotThrown(IllegalStateException.class);
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Component with uuid 'UNKNOWN' does not exists");
+ }
+ }
+
+ @Test
+ public void fail_if_to_component_uuid_does_not_exists() throws Exception {
+ dbClient.fileDependencyDao().insert(session, new FileDependencyDto()
+ .setFromComponentUuid(FILE1_UUID)
+ .setToComponentUuid(UNKNOWN_UUID)
+ .setFromParentUuid(DIR1_UUID)
+ .setToParentUuid(DIR2_UUID)
+ .setRootProjectSnapshotId(projectSnapshotId)
+ .setWeight(2)
+ .setCreatedAt(1000L));
+ session.commit();
+
+ MockUserSession.set().addProjectUuidPermissions(UserRole.USER, PROJECT_UUID);
+
+ try {
+ tester.newGetRequest("api/dependencies", "show")
+ .setParam("fromParentUuid",DIR1_UUID)
+ .setParam("toParentUuid", DIR2_UUID)
+ .execute()
+ .assertJson(getClass(), "return_file_dependencies.json");
+ failBecauseExceptionWasNotThrown(IllegalStateException.class);
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Component with uuid 'UNKNOWN' does not exists");
+ }
+ }
+
+ private void initComponents(){
+ ComponentDto project = ComponentTesting.newProjectDto(PROJECT_UUID);
+ ComponentDto directory1 = ComponentTesting.newDirectory(project, "/src/main/java/dir1").setUuid(DIR1_UUID);
+ ComponentDto file1 = ComponentTesting.newFileDto(directory1, FILE1_UUID).setLongName("src/main/java/dir1/File1.java");
+ ComponentDto directory2 = ComponentTesting.newDirectory(project, "/src/main/java/dir2").setUuid(DIR2_UUID);
+ ComponentDto file2 = ComponentTesting.newFileDto(directory1, FILE2_UUID).setLongName("src/main/java/dir2/File2.java");
+ dbClient.componentDao().insert(session, project, directory1, directory2, file1, file2);
+
+ SnapshotDto projectSnapshot = SnapshotTesting.createForProject(project);
+ dbClient.snapshotDao().insert(session, projectSnapshot);
+
+ session.commit();
+
+ projectSnapshotId = projectSnapshot.getId();
+ }
+
+}
--- /dev/null
+{
+ "dependencies":[
+ {
+ "fromUuid": "FILE1",
+ "fromName": "src/main/java/dir1/File1.java",
+ "toUuid": "FILE2",
+ "toName": "src/main/java/dir2/File2.java",
+ "weight": 2
+ }
+ ]
+}
--- /dev/null
+{
+ "dependencies":[]
+}