From 90afae1acb238433d0e0e783168c93308ea3e7a5 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Mon, 1 Jun 2015 13:32:17 +0200 Subject: [PATCH] SONAR-6259 Fix validation of project and module keys --- .../server/component/db/ComponentDao.java | 24 +- .../computation/step/ComputationSteps.java | 1 + .../computation/step/ValidateProjectStep.java | 188 +++++++++++ .../server/component/db/ComponentDaoTest.java | 32 +- .../step/ComputationStepsTest.java | 7 +- .../step/ValidateProjectStepTest.java | 299 ++++++++++++++++++ .../core/component/db/ComponentMapper.java | 18 +- .../core/component/db/ComponentMapper.xml | 17 +- 8 files changed, 525 insertions(+), 61 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java index fdebb5a6f69..52169547e35 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java @@ -22,6 +22,12 @@ package org.sonar.server.component.db; import com.google.common.base.Function; import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.apache.ibatis.session.RowBounds; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Scopes; @@ -36,14 +42,6 @@ import org.sonar.core.persistence.DbSession; import org.sonar.server.es.SearchOptions; import org.sonar.server.exceptions.NotFoundException; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import static com.google.common.collect.Maps.newHashMapWithExpectedSize; /** @@ -82,10 +80,6 @@ public class ComponentDao implements DaoComponent { return mapper(session).countById(id) > 0; } - public List selectModulesByProject(String projectKey, DbSession session) { - return mapper(session).selectModulesByProject(projectKey); - } - public List selectSubProjectsByComponentUuids(DbSession session, Collection keys) { if (keys.isEmpty()) { return Collections.emptyList(); @@ -137,7 +131,11 @@ public class ComponentDao implements DaoComponent { } public List selectComponentsFromProjectKey(DbSession session, String projectKey) { - return mapper(session).selectComponentsFromProjectKey(projectKey); + return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, null); + } + + public List selectModulesFromProjectKey(DbSession session, String projectKey) { + return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT); } public List selectByKeys(DbSession session, Collection keys) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java index 9f3368cff76..c1052d65fdf 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java @@ -37,6 +37,7 @@ public class ComputationSteps { public static List> orderedStepClasses() { return Arrays.asList( PopulateComponentsUuidAndKeyStep.class, + ValidateProjectStep.class, // Read report ParseReportStep.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java new file mode 100644 index 00000000000..92409fc2acf --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java @@ -0,0 +1,188 @@ +/* + * 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.computation.step; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Maps; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.batch.protocol.output.BatchReportReader; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.component.ComponentKeys; +import org.sonar.core.persistence.DbSession; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.computation.ComputationContext; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; +import org.sonar.server.db.DbClient; + +/** + * Validate project and modules. It will fail in the following cases : + *
    + *
  1. property {@link org.sonar.api.CoreProperties#CORE_PREVENT_AUTOMATIC_PROJECT_CREATION} is set to true and project does not exists
  2. + *
  3. branch is not valid
  4. + *
  5. project or module key is not valid
  6. + *
  7. module key already exists in another project (same module key cannot exists in different projects)
  8. + *
  9. module key is already used as a project key
  10. + *
+ */ +public class ValidateProjectStep implements ComputationStep { + + private static final Joiner MESSAGES_JOINER = Joiner.on("\n o "); + + private final DbClient dbClient; + private final Settings settings; + + public ValidateProjectStep(DbClient dbClient, Settings settings) { + this.dbClient = dbClient; + this.settings = settings; + } + + @Override + public void execute(ComputationContext context) { + DbSession session = dbClient.openSession(false); + try { + List modules = dbClient.componentDao().selectModulesFromProjectKey(session, context.getRoot().getKey()); + Map modulesByKey = Maps.uniqueIndex(modules, new Function() { + @Override + public String apply(@Nonnull ComponentDto input) { + return input.key(); + } + }); + ValidateProjectsVisitor visitor = new ValidateProjectsVisitor(session, dbClient.componentDao(), context.getReportMetadata(), + context.getReportReader(), settings.getBoolean(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION), modulesByKey); + visitor.visit(context.getRoot()); + + if (!visitor.validationMessages.isEmpty()) { + throw new IllegalArgumentException("Validation of project failed:\n o " + MESSAGES_JOINER.join(visitor.validationMessages)); + } + } finally { + session.close(); + } + } + + @Override + public String getDescription() { + return "Validate project and modules keys"; + } + + private static class ValidateProjectsVisitor extends DepthTraversalTypeAwareVisitor { + private final DbSession session; + private final ComponentDao componentDao; + private final BatchReport.Metadata reportMetadata; + private final BatchReportReader batchReportReader; + private final boolean preventAutomaticProjectCreation; + private final Map modulesByKey; + private final List validationMessages = new ArrayList<>(); + + private Component root; + + public ValidateProjectsVisitor(DbSession session, ComponentDao componentDao, BatchReport.Metadata reportMetadata, BatchReportReader batchReportReader, + boolean preventAutomaticProjectCreation, Map modulesByKey) { + super(Component.Type.MODULE, Order.PRE_ORDER); + this.session = session; + this.componentDao = componentDao; + this.reportMetadata = reportMetadata; + this.batchReportReader = batchReportReader; + + this.preventAutomaticProjectCreation = preventAutomaticProjectCreation; + this.modulesByKey = modulesByKey; + } + + @Override + public void visitProject(Component project) { + this.root = project; + validateBranch(); + validateBatchKey(project); + + String projectKey = project.getKey(); + ComponentDto projectDto = loadComponent(projectKey); + if (projectDto == null) { + if (preventAutomaticProjectCreation) { + validationMessages.add(String.format("Unable to scan non-existing project '%s'", projectKey)); + } + } else if (!projectDto.projectUuid().equals(projectDto.uuid())) { + // Project key is already used as a module of another project + ComponentDto anotherProject = componentDao.selectByUuid(session, projectDto.projectUuid()); + validationMessages.add(String.format("The project \"%s\" is already defined in SonarQube but as a module of project \"%s\". " + + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".", + projectKey, anotherProject.key(), anotherProject.key(), projectKey)); + } + } + + @Override + public void visitModule(Component module) { + String projectKey = root.getKey(); + String moduleKey = module.getKey(); + validateBatchKey(module); + + ComponentDto moduleDto = loadComponent(moduleKey); + if (moduleDto == null) { + return; + } + if (moduleDto.projectUuid().equals(moduleDto.uuid())) { + // module is actually a project + validationMessages.add(String.format("The project \"%s\" is already defined in SonarQube but not as a module of project \"%s\". " + + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".", + moduleKey, projectKey, moduleKey, projectKey)); + } else if (!moduleDto.projectUuid().equals(root.getUuid())) { + ComponentDto projectModule = componentDao.selectByUuid(session, moduleDto.projectUuid()); + validationMessages.add(String.format("Module \"%s\" is already part of project \"%s\"", moduleKey, projectModule.key())); + } + } + + private void validateBatchKey(Component component) { + String batchKey = batchReportReader.readComponent(component.getRef()).getKey(); + if (!ComponentKeys.isValidModuleKey(batchKey)) { + validationMessages.add(String.format("\"%s\" is not a valid project or module key. " + + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", batchKey)); + } + } + + @CheckForNull + private void validateBranch() { + if (!reportMetadata.hasBranch()) { + return; + } + String branch = reportMetadata.getBranch(); + if (!ComponentKeys.isValidBranch(branch)) { + validationMessages.add(String.format("\"%s\" is not a valid branch name. " + + "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch)); + } + } + + private ComponentDto loadComponent(String componentKey) { + ComponentDto componentDto = modulesByKey.get(componentKey); + if (componentDto == null) { + // Load component from key to be able to detect issue (try to analyze a module, etc.) + return componentDao.selectNullableByKey(session, componentKey); + } + return componentDto; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java index d64ef797efa..396c5c8d33e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java @@ -20,6 +20,8 @@ package org.sonar.server.component.db; +import java.util.Collections; +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; @@ -34,9 +36,6 @@ import org.sonar.core.persistence.DbTester; import org.sonar.server.es.SearchOptions; import org.sonar.server.exceptions.NotFoundException; -import java.util.Collections; -import java.util.List; - import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; @@ -310,23 +309,6 @@ public class ComponentDaoTest { assertThat(sut.existsById(111L, session)).isFalse(); } - @Test - public void find_modules_by_project() { - db.prepareDbUnit(getClass(), "multi-modules.xml"); - - List results = sut.selectModulesByProject("org.struts:struts", session); - assertThat(results).hasSize(1); - assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-core"); - - results = sut.selectModulesByProject("org.struts:struts-core", session); - assertThat(results).hasSize(1); - assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-data"); - - assertThat(sut.selectModulesByProject("org.struts:struts-data", session)).isEmpty(); - - assertThat(sut.selectModulesByProject("unknown", session)).isEmpty(); - } - @Test public void find_sub_projects_by_component_keys() { db.prepareDbUnit(getClass(), "multi-modules.xml"); @@ -463,6 +445,16 @@ public class ComponentDaoTest { assertThat(sut.selectComponentsFromProjectKey(session, "UNKNOWN")).isEmpty(); } + @Test + public void select_modules_from_project() { + db.prepareDbUnit(getClass(), "multi-modules.xml"); + + List components = sut.selectModulesFromProjectKey(session, "org.struts:struts"); + assertThat(components).hasSize(3); + + assertThat(sut.selectModulesFromProjectKey(session, "UNKNOWN")).isEmpty(); + } + @Test public void select_views_and_sub_views() { db.prepareDbUnit(getClass(), "shared_views.xml"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java index 7ed17968743..188773c5c41 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java @@ -52,12 +52,13 @@ public class ComputationStepsTest { mock(PopulateComponentsUuidAndKeyStep.class), mock(PersistComponentsStep.class), mock(IndexTestsStep.class), - mock(QualityProfileEventsStep.class) + mock(QualityProfileEventsStep.class), + mock(ValidateProjectStep.class) ); - assertThat(registry.orderedSteps()).hasSize(20); + assertThat(registry.orderedSteps()).hasSize(21); assertThat(registry.orderedSteps().get(0)).isInstanceOf(PopulateComponentsUuidAndKeyStep.class); - assertThat(registry.orderedSteps().get(19)).isInstanceOf(SendIssueNotificationsStep.class); + assertThat(registry.orderedSteps().get(20)).isInstanceOf(SendIssueNotificationsStep.class); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java new file mode 100644 index 00000000000..8bfcf5a067a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java @@ -0,0 +1,299 @@ +/* + * 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.computation.step; + +import java.io.File; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.batch.protocol.Constants; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.batch.protocol.output.BatchReportReader; +import org.sonar.batch.protocol.output.BatchReportWriter; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.server.component.ComponentTesting; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.computation.ComputationContext; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.ComponentTreeBuilders; +import org.sonar.server.computation.component.DumbComponent; +import org.sonar.server.db.DbClient; + +public class ValidateProjectStepTest { + + private static final String PROJECT_KEY = "PROJECT_KEY"; + private static final String MODULE_KEY = "MODULE_KEY"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @ClassRule + public static DbTester dbTester = new DbTester(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + DbClient dbClient; + + DbSession dbSession; + + Settings settings; + + File reportDir; + BatchReportWriter writer; + + ValidateProjectStep sut; + + @Before + public void setUp() throws Exception { + dbTester.truncateTables(); + dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new ComponentDao()); + dbSession = dbClient.openSession(false); + settings = new Settings(); + reportDir = temp.newFolder(); + writer = new BatchReportWriter(reportDir); + + sut = new ValidateProjectStep(dbClient, settings); + } + + @After + public void tearDown() throws Exception { + dbSession.close(); + } + + @Test + public void not_fail_if_provisioning_enforced_and_project_exists() throws Exception { + writer.writeMetadata(BatchReport.Metadata.newBuilder().build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .build()); + + settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "true"); + dbClient.componentDao().insert(dbSession, ComponentTesting.newProjectDto("ABCD").setKey(PROJECT_KEY)); + dbSession.commit(); + + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY)), null)); + } + + @Test + public void fail_if_provisioning_enforced_and_project_does_not_exists() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Unable to scan non-existing project '" + PROJECT_KEY + "'"); + + writer.writeMetadata(BatchReport.Metadata.newBuilder().build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .build()); + + settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "true"); + + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY)), null)); + } + + @Test + public void fail_if_provisioning_not_enforced_and_project_does_not_exists() throws Exception { + writer.writeMetadata(BatchReport.Metadata.newBuilder().build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .build()); + + settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "false"); + + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY)), null)); + } + + @Test + public void not_fail_on_valid_branch() throws Exception { + writer.writeMetadata(BatchReport.Metadata.newBuilder() + .setBranch("origin/master") + .build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .build()); + + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY + ":origin/master")), null)); + } + + @Test + public void fail_on_invalid_branch() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Validation of project failed:\n" + + " o \"bran#ch\" is not a valid branch name. Allowed characters are alphanumeric, '-', '_', '.' and '/'."); + + writer.writeMetadata(BatchReport.Metadata.newBuilder() + .setBranch("bran#ch") + .build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .build()); + + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY + ":bran#ch")), null)); + } + + @Test + public void fail_on_invalid_key() throws Exception { + String invalidProjectKey = "Project\\Key"; + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Validation of project failed:\n" + + " o \"Project\\Key\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.\n" + + " o \"Module$Key\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit"); + + writer.writeMetadata(BatchReport.Metadata.newBuilder().build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(invalidProjectKey) + .addChildRef(2) + .build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(2) + .setType(Constants.ComponentType.MODULE) + .setKey("Module$Key") + .build()); + + DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", invalidProjectKey, + new DumbComponent(Component.Type.MODULE, 2, "BCDE", "Module$Key")); + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, ComponentTreeBuilders.from(root), null)); + } + + @Test + public void fail_if_module_key_is_already_used_as_project_key() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Validation of project failed:\n" + + " o The project \"" + MODULE_KEY + "\" is already defined in SonarQube but not as a module of project \"" + PROJECT_KEY + "\". " + + "If you really want to stop directly analysing project \"" + MODULE_KEY + "\", please first delete it from SonarQube and then relaunch the analysis of project \"" + + PROJECT_KEY + "\"."); + + writer.writeMetadata(BatchReport.Metadata.newBuilder().build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .addChildRef(2) + .build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(2) + .setType(Constants.ComponentType.MODULE) + .setKey(MODULE_KEY) + .build()); + + ComponentDto project = ComponentTesting.newProjectDto("ABCD").setKey(MODULE_KEY); + dbClient.componentDao().insert(dbSession, project); + dbSession.commit(); + + DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY, + new DumbComponent(Component.Type.MODULE, 2, "BCDE", MODULE_KEY)); + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(root), null)); + } + + @Test + public void fail_if_module_key_already_exists_in_another_project() throws Exception { + String anotherProjectKey = "ANOTHER_PROJECT_KEY"; + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Validation of project failed:\n" + + " o Module \"" + MODULE_KEY + "\" is already part of project \"" + anotherProjectKey + "\""); + + writer.writeMetadata(BatchReport.Metadata.newBuilder().build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .addChildRef(2) + .build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(2) + .setType(Constants.ComponentType.MODULE) + .setKey(MODULE_KEY) + .build()); + + ComponentDto project = ComponentTesting.newProjectDto("ABCD").setKey(PROJECT_KEY); + ComponentDto anotherProject = ComponentTesting.newProjectDto().setKey(anotherProjectKey); + dbClient.componentDao().insert(dbSession, project, anotherProject); + ComponentDto module = ComponentTesting.newModuleDto("BCDE", anotherProject).setKey(MODULE_KEY); + dbClient.componentDao().insert(dbSession, module); + dbSession.commit(); + + DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY, + new DumbComponent(Component.Type.MODULE, 2, "BCDE", MODULE_KEY)); + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(root), null)); + } + + @Test + public void fail_if_project_key_already_exists_as_module() throws Exception { + String anotherProjectKey = "ANOTHER_PROJECT_KEY"; + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Validation of project failed:\n" + + " o The project \"" + PROJECT_KEY + "\" is already defined in SonarQube but as a module of project \"" + anotherProjectKey + "\". " + + "If you really want to stop directly analysing project \"" + anotherProjectKey + "\", please first delete it from SonarQube and then relaunch the analysis of project \"" + + PROJECT_KEY + "\"."); + + writer.writeMetadata(BatchReport.Metadata.newBuilder().build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(1) + .setType(Constants.ComponentType.PROJECT) + .setKey(PROJECT_KEY) + .addChildRef(2) + .build()); + writer.writeComponent(BatchReport.Component.newBuilder() + .setRef(2) + .setType(Constants.ComponentType.MODULE) + .setKey(MODULE_KEY) + .build()); + + ComponentDto anotherProject = ComponentTesting.newProjectDto().setKey(anotherProjectKey); + dbClient.componentDao().insert(dbSession, anotherProject); + ComponentDto module = ComponentTesting.newModuleDto("ABCD", anotherProject).setKey(PROJECT_KEY); + dbClient.componentDao().insert(dbSession, module); + dbSession.commit(); + + DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY, + new DumbComponent(Component.Type.MODULE, 2, "BCDE", MODULE_KEY)); + sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, + ComponentTreeBuilders.from(root), null)); + } + +} diff --git a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java index 424b8a269bf..7eac6cd994c 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java @@ -20,18 +20,17 @@ package org.sonar.core.component.db; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.session.RowBounds; import org.sonar.core.component.ComponentDto; import org.sonar.core.component.FilePathWithHashDto; import org.sonar.core.component.UuidWithProjectUuidDto; -import javax.annotation.CheckForNull; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - /** * @since 4.3 */ @@ -46,11 +45,6 @@ public interface ComponentMapper { @CheckForNull ComponentDto selectByUuid(String uuid); - /** - * Return direct modules from a project/module - */ - List selectModulesByProject(@Param("projectKey") String projectKey); - /** * Return sub project of component keys */ @@ -96,7 +90,7 @@ public interface ComponentMapper { /** * Return all components of a project */ - List selectComponentsFromProjectKey(@Param("projectKey") String projectKey); + List selectComponentsFromProjectKeyAndScope(@Param("projectKey") String projectKey, @Nullable @Param("scope") String scope); /** * Return technical projects from a view or a sub-view diff --git a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml index ad195fe0815..8c58c026256 100644 --- a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml @@ -64,18 +64,6 @@ - - - SELECT FROM projects p INNER JOIN projects root ON root.uuid=p.project_uuid AND root.kee=#{projectKey} AND p.enabled=${_true} + + AND p.scope=#{scope} + -- 2.39.5