]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6259 Fix validation of project and module keys
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 1 Jun 2015 11:32:17 +0000 (13:32 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 1 Jun 2015 11:40:57 +0000 (13:40 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java
sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml

index fdebb5a6f69e48a7024b98ca92cbd92fa07d7e2c..52169547e3513426bcbc9c5612c168e0e0168307 100644 (file)
@@ -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<ComponentDto> selectModulesByProject(String projectKey, DbSession session) {
-    return mapper(session).selectModulesByProject(projectKey);
-  }
-
   public List<ComponentDto> selectSubProjectsByComponentUuids(DbSession session, Collection<String> keys) {
     if (keys.isEmpty()) {
       return Collections.emptyList();
@@ -137,7 +131,11 @@ public class ComponentDao implements DaoComponent {
   }
 
   public List<ComponentDto> selectComponentsFromProjectKey(DbSession session, String projectKey) {
-    return mapper(session).selectComponentsFromProjectKey(projectKey);
+    return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, null);
+  }
+
+  public List<ComponentDto> selectModulesFromProjectKey(DbSession session, String projectKey) {
+    return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT);
   }
 
   public List<ComponentDto> selectByKeys(DbSession session, Collection<String> keys) {
index 9f3368cff76ecfd07edfa81ebf7bb9a5196303f4..c1052d65fdfb5677a19e29c342859b0494e3d9ae 100644 (file)
@@ -37,6 +37,7 @@ public class ComputationSteps {
   public static List<Class<? extends ComputationStep>> 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 (file)
index 0000000..92409fc
--- /dev/null
@@ -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 :
+ * <ol>
+ * <li>property {@link org.sonar.api.CoreProperties#CORE_PREVENT_AUTOMATIC_PROJECT_CREATION} is set to true and project does not exists</li>
+ * <li>branch is not valid</li>
+ * <li>project or module key is not valid</li>
+ * <li>module key already exists in another project (same module key cannot exists in different projects)</li>
+ * <li>module key is already used as a project key</li>
+ * </ol>
+ */
+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<ComponentDto> modules = dbClient.componentDao().selectModulesFromProjectKey(session, context.getRoot().getKey());
+      Map<String, ComponentDto> modulesByKey = Maps.uniqueIndex(modules, new Function<ComponentDto, String>() {
+        @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<String, ComponentDto> modulesByKey;
+    private final List<String> validationMessages = new ArrayList<>();
+
+    private Component root;
+
+    public ValidateProjectsVisitor(DbSession session, ComponentDao componentDao, BatchReport.Metadata reportMetadata, BatchReportReader batchReportReader,
+      boolean preventAutomaticProjectCreation, Map<String, ComponentDto> 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;
+    }
+  }
+}
index d64ef797efa168a6c1c79c3acbf84d7c74450775..396c5c8d33e0e6c8d06d5c255e50c0e2ed024730 100644 (file)
@@ -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<ComponentDto> 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<ComponentDto> 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");
index 7ed179687435fd627a1dc0956fb24b72141203b1..188773c5c410b4f9ffa2b115a578441e19a45143 100644 (file)
@@ -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 (file)
index 0000000..8bfcf5a
--- /dev/null
@@ -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));
+  }
+
+}
index 424b8a269bf366eb028fb4f3d348df19ae7ad0ac..7eac6cd994cad68e868034930745a004ca73867b 100644 (file)
 
 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<ComponentDto> 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<ComponentDto> selectComponentsFromProjectKey(@Param("projectKey") String projectKey);
+  List<ComponentDto> selectComponentsFromProjectKeyAndScope(@Param("projectKey") String projectKey, @Nullable @Param("scope") String scope);
 
   /**
    * Return technical projects from a view or a sub-view
index ad195fe08152dc7e9ea5f673293aba45fca81398..8c58c0262566a1fb495abb65ce7e55b5dccaccd8 100644 (file)
     </where>
   </select>
 
-  <select id="selectModulesByProject" parameterType="String" resultType="Component">
-    SELECT <include refid="componentColumns"/>
-    FROM projects p
-    INNER JOIN snapshots s ON s.project_id=p.id AND s.islast=${_true}
-    INNER JOIN snapshots parent_snapshots ON parent_snapshots.id=s.parent_snapshot_id AND parent_snapshots.islast=${_true}
-    INNER JOIN projects parent ON parent.id=parent_snapshots.project_id AND parent.enabled=${_true} AND parent.kee=#{projectKey}
-    <where>
-      AND p.enabled=${_true}
-      AND p.scope='PRJ'
-    </where>
-  </select>
-
   <select id="selectByKeys" parameterType="String" resultType="Component">
     select <include refid="componentColumns"/>
     from projects p
     </where>
   </select>
 
-  <select id="selectComponentsFromProjectKey" parameterType="map" resultType="Component">
+  <select id="selectComponentsFromProjectKeyAndScope" parameterType="map" resultType="Component">
     SELECT <include refid="componentColumns"/>
     FROM projects p
     INNER JOIN projects root ON root.uuid=p.project_uuid AND root.kee=#{projectKey}
     <where>
       AND p.enabled=${_true}
+      <if test="scope != null">
+        AND p.scope=#{scope}
+      </if>
     </where>
   </select>