aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2015-05-29 14:37:37 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2015-05-29 14:38:08 +0200
commita8e2ff5ad049dc21a9922dca08ef3c2825605c1b (patch)
tree9274a041e3d354f3244acda5fc86423983938f5d
parent528993fd651a0d046456a1dbec69f408f8676a9b (diff)
downloadsonarqube-a8e2ff5ad049dc21a9922dca08ef3c2825605c1b.tar.gz
sonarqube-a8e2ff5ad049dc21a9922dca08ef3c2825605c1b.zip
SONAR-6259 Validate project and module keys
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java24
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java183
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java32
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java222
-rw-r--r--sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java18
-rw-r--r--sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml17
8 files changed, 443 insertions, 61 deletions
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<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) {
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<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
index 00000000000..120b507a6de
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java
@@ -0,0 +1,183 @@
+/*
+ * 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.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(),
+ 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 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, boolean preventAutomaticProjectCreation,
+ Map<String, ComponentDto> modulesByKey) {
+ super(Component.Type.MODULE, Order.PRE_ORDER);
+ this.session = session;
+ this.componentDao = componentDao;
+ this.reportMetadata = reportMetadata;
+ this.preventAutomaticProjectCreation = preventAutomaticProjectCreation;
+ this.modulesByKey = modulesByKey;
+ }
+
+ @Override
+ public void visitProject(Component project) {
+ this.root = project;
+ validateBranch();
+
+ 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));
+ }
+ validateKey(projectKey);
+ }
+
+ @Override
+ public void visitModule(Component module) {
+ String moduleKey = module.getKey();
+ String projectKey = root.getKey();
+ validateKey(moduleKey);
+
+ 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 validateKey(String moduleKey) {
+ if (!ComponentKeys.isValidModuleKey(moduleKey)) {
+ validationMessages.add(String.format("\"%s\" is not a valid project or module key. "
+ + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", moduleKey));
+ }
+ }
+
+ @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;
@@ -311,23 +310,6 @@ public class ComponentDaoTest {
}
@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");
@@ -464,6 +446,16 @@ public class ComponentDaoTest {
}
@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");
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..434c3dfca86
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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 java.io.IOException;
+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.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;
+
+ 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();
+
+ 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 {
+ 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(createBasicBatchReportReader(), 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 + "'");
+
+ settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "true");
+
+ sut.execute(new ComputationContext(createBasicBatchReportReader(), 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 {
+ settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "false");
+
+ sut.execute(new ComputationContext(createBasicBatchReportReader(), 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 {
+ File reportDir = temp.newFolder();
+ BatchReportWriter writer = new BatchReportWriter(reportDir);
+ writer.writeMetadata(BatchReport.Metadata.newBuilder()
+ .setBranch("origin/master")
+ .build());
+
+ 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_on_invalid_branch() throws Exception {
+ File reportDir = temp.newFolder();
+ 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 '/'.");
+
+ BatchReportWriter writer = new BatchReportWriter(reportDir);
+ writer.writeMetadata(BatchReport.Metadata.newBuilder()
+ .setBranch("bran#ch")
+ .build());
+
+ 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_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");
+
+ DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", invalidProjectKey,
+ new DumbComponent(Component.Type.MODULE, 2, "BCDE", "Module$Key"));
+ sut.execute(new ComputationContext(createBasicBatchReportReader(), 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 + "\".");
+
+ 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(createBasicBatchReportReader(), 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 + "\"");
+
+ 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(createBasicBatchReportReader(), 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 + "\".");
+
+ 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(createBasicBatchReportReader(), null, null, null,
+ ComponentTreeBuilders.from(root), null));
+ }
+
+ private BatchReportReader createBasicBatchReportReader() throws IOException {
+ File reportDir = temp.newFolder();
+ BatchReportWriter writer = new BatchReportWriter(reportDir);
+ writer.writeMetadata(BatchReport.Metadata.newBuilder()
+ .build());
+ return new BatchReportReader(reportDir);
+ }
+}
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
*/
@@ -47,11 +46,6 @@ public interface ComponentMapper {
ComponentDto selectByUuid(String uuid);
/**
- * Return direct modules from a project/module
- */
- List<ComponentDto> selectModulesByProject(@Param("projectKey") String projectKey);
-
- /**
* Return sub project of component keys
*/
List<ComponentDto> selectSubProjectsByComponentUuids(@Param("uuids") Collection<String> uuids);
@@ -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
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 @@
</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
@@ -213,12 +201,15 @@
</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>