From fecf25981b0b8ef000ff329b736e34dc57d3f5c3 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Wed, 16 Oct 2024 12:48:55 +0200 Subject: [PATCH] SONAR-23098 Persist dependencies in the compute engine --- .../PersistProjectDependenciesStepIT.java | 144 ++++++++++++++ .../step/BuildProjectDependenciesStepIT.java | 120 ++++++++++++ .../step/ReportPersistComponentsStepIT.java | 43 +++- .../step/ViewsPersistComponentsStepIT.java | 6 +- .../batch/BatchReportReader.java | 2 + .../batch/BatchReportReaderImpl.java | 6 + .../projectanalysis/component/Component.java | 4 +- ...ProjectAnalysisTaskContainerPopulator.java | 4 +- .../EmptyProjectDependenciesHolder.java | 40 ++++ .../MutableProjectDependenciesHolder.java | 34 ++++ .../PersistProjectDependenciesStep.java | 90 +++++++++ .../dependency/ProjectDependenciesHolder.java | 46 +++++ .../ProjectDependenciesHolderImpl.java | 64 ++++++ .../dependency/ProjectDependency.java | 63 ++++++ .../dependency/ProjectDependencyImpl.java | 183 ++++++++++++++++++ .../step/BuildProjectDependenciesStep.java | 111 +++++++++++ .../step/PersistComponentsStep.java | 42 +++- .../step/ReportComputationSteps.java | 5 + .../batch/BatchReportReaderRule.java | 11 ++ .../dependency/ProjectDependencyImplTest.java | 100 ++++++++++ .../step/PersistComponentsStepTest.java | 9 +- .../MutableProjectDependenciesHolderRule.java | 61 ++++++ .../db/dependency/ProjectDependenciesDao.java | 12 ++ .../dependency/ProjectDependenciesMapper.java | 4 + .../dependency/ProjectDependenciesMapper.xml | 13 ++ .../sonar/db/component/ComponentTesting.java | 12 ++ 26 files changed, 1218 insertions(+), 11 deletions(-) create mode 100644 server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStepIT.java create mode 100644 server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStepIT.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/EmptyProjectDependenciesHolder.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolder.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStep.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolder.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolderImpl.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependency.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImpl.java create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStep.java create mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImplTest.java create mode 100644 server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolderRule.java diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStepIT.java new file mode 100644 index 00000000000..57b6e007fc2 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStepIT.java @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.component.ProjectData; +import org.sonar.db.dependency.ProjectDependencyDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +class PersistProjectDependenciesStepIT { + + public static final int NOW = 1_000_000; + @RegisterExtension + private final DbTester db = DbTester.create(System2.INSTANCE); + + @RegisterExtension + private final MutableProjectDependenciesHolderRule projectDependenciesHolder = new MutableProjectDependenciesHolderRule(); + @RegisterExtension + private final TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + private final DbSession dbSession = db.getSession(); + private final DbClient dbClient = db.getDbClient(); + + private PersistProjectDependenciesStep underTest; + private ComponentDto branch; + + @BeforeEach + void setUp() { + ProjectData projectData = db.components().insertPublicProject(); + branch = db.components().insertProjectBranch(projectData.getMainBranchComponent()); + treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid(branch.uuid()).build()); + underTest = new PersistProjectDependenciesStep(dbClient, projectDependenciesHolder, treeRootHolder, Clock.fixed(Instant.ofEpochMilli(NOW), ZoneId.of("UTC"))); + } + + @Test + void getDescription_shouldReturnStepDescription() { + assertThat(underTest.getDescription()).isEqualTo("Persist project dependencies"); + } + + @Test + void execute_shouldInsertNewDependencies() { + ProjectDependency minimalDep = buildDependency("mini").build(); + ProjectDependency depWithVersion = buildDependency("version").setVersion("1.0").build(); + ProjectDependency depWithFullName = buildDependency("fullname").setFullName("fullname").build(); + ProjectDependency depWithDescription = buildDependency("desc").setDescription("Some description").build(); + ProjectDependency depWithPackageManager = buildDependency("packagemanager").setPackageManager("mvn").build(); + projectDependenciesHolder.setDependencies(List.of(minimalDep, depWithVersion, depWithFullName, depWithDescription, depWithPackageManager)); + + underTest.execute(new TestComputationStepContext()); + + assertDependencyPersistedInDatabase(minimalDep, NOW, NOW); + assertDependencyPersistedInDatabase(depWithVersion, NOW, NOW); + assertDependencyPersistedInDatabase(depWithFullName, NOW, NOW); + assertDependencyPersistedInDatabase(depWithDescription, NOW, NOW); + assertDependencyPersistedInDatabase(depWithPackageManager, NOW, NOW); + } + + private void assertDependencyPersistedInDatabase(ProjectDependency dep, long createdAt, long updatedAt) { + ProjectDependencyDto depDto = dbClient.projectDependenciesDao().selectByUuid(dbSession, dep.getUuid()) + .orElseGet(() -> fail(String.format("Dependency with uuid %s not found", dep.getUuid()))); + assertThat(depDto.uuid()).isEqualTo(dep.getUuid()); + assertThat(depDto.version()).isEqualTo(dep.getVersion()); + assertThat(depDto.packageManager()).isEqualTo(dep.getPackageManager()); + assertThat(depDto.includePaths()).isNull(); + assertThat(depDto.createdAt()).isEqualTo(createdAt); + assertThat(depDto.updatedAt()).isEqualTo(updatedAt); + } + + @Test + void execute_shouldUpdateExistingDependencies() { + db.components().insertComponent(ComponentTesting.newDependencyDto(branch, "dep-uuid-1")); + dbClient.projectDependenciesDao().insert(dbSession, new ProjectDependencyDto("dep-uuid-1", "1.0-OLD", null, "mvn-old", NOW - 10L, NOW - 10L)); + db.components().insertComponent(ComponentTesting.newDependencyDto(branch, "dep-uuid-2")); + dbClient.projectDependenciesDao().insert(dbSession, new ProjectDependencyDto("dep-uuid-2", "2.0-OLD", null, "npm-old", NOW - 10L, NOW - 10L)); + db.commit(); + ProjectDependency dep1 = buildDependency("1").build(); + ProjectDependency dep2 = buildDependency("2").setVersion("2.0").setPackageManager("npm").build(); + projectDependenciesHolder.setDependencies(List.of(dep1, dep2)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(db.countRowsOfTable(dbSession, "project_dependencies")).isEqualTo(2); + assertDependencyPersistedInDatabase(dep1, NOW - 10L, NOW); + assertDependencyPersistedInDatabase(dep2, NOW - 10L, NOW); + } + + @Test + void execute_shoudDeleteNonExistingDependenciesAndDontTouchUnchanged() { + db.components().insertComponent(ComponentTesting.newDependencyDto(branch, "dep-uuid-1")); + dbClient.projectDependenciesDao().insert(dbSession, new ProjectDependencyDto("dep-uuid-1", null, null, null, NOW - 10L, NOW - 10L)); + db.components().insertComponent(ComponentTesting.newDependencyDto(branch, "dep-uuid-2")); + dbClient.projectDependenciesDao().insert(dbSession, new ProjectDependencyDto("dep-uuid-2", "2.0-OLD", null, "npm-old", NOW - 10L, NOW - 10L)); + db.commit(); + ProjectDependency dep1 = buildDependency("1").build(); + projectDependenciesHolder.setDependencies(List.of(dep1)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(db.countRowsOfTable(dbSession, "project_dependencies")).isEqualTo(1); + assertDependencyPersistedInDatabase(dep1, NOW - 10L, NOW - 10L); + } + + private static ProjectDependencyImpl.Builder buildDependency(String suffix) { + return ProjectDependencyImpl.builder() + .setUuid("dep-uuid-" + suffix) + .setName("name-" + suffix) + .setKey("key-" + suffix); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStepIT.java new file mode 100644 index 00000000000..3cf4cc0d993 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStepIT.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.step; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl; +import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.dependency.MutableProjectDependenciesHolderRule; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; + +class BuildProjectDependenciesStepIT { + private static final String PROJECT_UUID = "PROJECT"; + private static final String PROJECT_KEY = "PROJECT_KEY"; + + @RegisterExtension + DbTester dbTester = DbTester.create(System2.INSTANCE); + @RegisterExtension + BatchReportReaderRule reportReader = new BatchReportReaderRule(); + @RegisterExtension + MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule(); + @RegisterExtension + MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule().setBranch(new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME)); + @RegisterExtension + MutableProjectDependenciesHolderRule dependencyHolder = new MutableProjectDependenciesHolderRule(); + + private final DbClient dbClient = dbTester.getDbClient(); + private final BuildProjectDependenciesStep underTest = new BuildProjectDependenciesStep(dbClient, reportReader, dependencyHolder, treeRootHolder, analysisMetadataHolder); + + @BeforeEach + void setup() { + initBasicProject(); + } + + @Test + void buildProjectDependenciesStep_whenNoDependencies_shouldBuildEmptyList() { + reportReader.putDependencies(List.of()); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + var dependencies = dependencyHolder.getDependencies(); + assertThat(dependencies).isEmpty(); + + context.getStatistics().assertValue("dependencies", 0); + } + + @Test + void buildProjectDependenciesStep_shouldBuildDependencyList() { + var reportDep1 = ScannerReport.Dependency.newBuilder() + .setKey("mvn+com.google.guava:guava$28.2-jre") + .setName("guava") + .setFullName("com.google.guava:guava") + .setPackageManager("mvn") + .setDescription("Google Core Libraries for Java") + .setVersion("28.2-jre") + .build(); + var reportDep2 = ScannerReport.Dependency.newBuilder() + .setKey("npm+react$7.0.0") + .setName("react") + .build(); + reportReader.putDependencies(List.of(reportDep1, reportDep2)); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + var dependencies = dependencyHolder.getDependencies(); + assertThat(dependencies).hasSize(2); + + var dependency1 = dependencies.get(0); + assertThat(dependency1.getKey()).isEqualTo("PROJECT_KEY:mvn+com.google.guava:guava$28.2-jre"); + assertThat(dependency1.getName()).isEqualTo("guava"); + assertThat(dependency1.getFullName()).isEqualTo("com.google.guava:guava"); + assertThat(dependency1.getDescription()).isEqualTo("Google Core Libraries for Java"); + + var dependency2 = dependencies.get(1); + assertThat(dependency2.getKey()).isEqualTo("PROJECT_KEY:npm+react$7.0.0"); + assertThat(dependency2.getName()).isEqualTo("react"); + assertThat(dependency2.getFullName()).isEqualTo("react"); + assertThat(dependency2.getDescription()).isNull(); + + context.getStatistics().assertValue("dependencies", 2); + } + + private void initBasicProject() { + ReportComponent root = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build(); + treeRootHolder.setRoots(root, root); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepIT.java index 2a09b0833e6..c1dfa5245e6 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepIT.java @@ -21,6 +21,7 @@ package org.sonar.ce.task.projectanalysis.step; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; @@ -40,6 +41,8 @@ import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHold import org.sonar.ce.task.projectanalysis.component.ProjectPersister; import org.sonar.ce.task.projectanalysis.component.ReportComponent; import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.dependency.MutableProjectDependenciesHolderRule; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependencyImpl; import org.sonar.ce.task.step.ComputationStep; import org.sonar.ce.task.step.TestComputationStepContext; import org.sonar.db.DbClient; @@ -72,6 +75,8 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); @Rule public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + @Rule + public MutableProjectDependenciesHolderRule projectDepsHolder = new MutableProjectDependenciesHolderRule(); private final System2 system2 = mock(System2.class); private final DbClient dbClient = db.getDbClient(); @@ -86,7 +91,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { BranchPersister branchPersister = mock(BranchPersister.class); ProjectPersister projectPersister = mock(ProjectPersister.class); - underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, branchPersister, projectPersister); + underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, branchPersister, projectPersister, projectDepsHolder); } @Override @@ -111,6 +116,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .addChildren(directory) .build(); treeRootHolder.setRoot(treeRoot); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -160,6 +166,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .addChildren(directory) .build(); treeRootHolder.setRoot(treeRoot); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -202,6 +209,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .setName("pom.xml") .build()) .build()); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -228,6 +236,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .build()) .build()) .build()); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -267,6 +276,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .build()) .build()) .build()); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -296,6 +306,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .build()) .build()) .build()); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -335,6 +346,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .build()) .build()) .build()); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -371,6 +383,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { treeRootHolder.setRoot( builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey()) .build()); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -410,6 +423,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .build()) .build()) .build()); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -458,6 +472,7 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { ComponentDto project = prepareProject(p -> p.setPrivate(true)); ComponentDto dir = db.components().insertComponent(newDirectory(project, "DEFG", "Directory").setKey("DIR").setPrivate(true)); treeRootHolder.setRoot(createSampleProjectComponentTree(project)); + projectDepsHolder.setDependencies(List.of()); underTest.execute(new TestComputationStepContext()); @@ -467,6 +482,32 @@ public class ReportPersistComponentsStepIT extends BaseStepTest { .isTrue()); } + @Test + public void persist_dependencies() { + ComponentDto projectDto = prepareProject(); + treeRootHolder.setRoot(asTreeRoot(projectDto).build()); + projectDepsHolder.setDependencies(List.of(ProjectDependencyImpl.builder() + .setUuid("DEFG") + .setKey(PROJECT_KEY + ":mvn+com.google.guava:guava$28.2-jre") + .setName("guava") + .setFullName("com.google.guava:guava") + .setDescription("Some long description about Guava") + .setVersion("28.2-jre") + .setPackageManager("mvn") + .build())); + + underTest.execute(new TestComputationStepContext()); + + ComponentDto depByKey = dbClient.componentDao().selectByKey(db.getSession(), PROJECT_KEY + ":mvn+com.google.guava:guava$28.2-jre").get(); + assertThat(depByKey.getKey()).isEqualTo(PROJECT_KEY + ":mvn+com.google.guava:guava$28.2-jre"); + assertThat(depByKey.name()).isEqualTo("guava"); + assertThat(depByKey.longName()).isEqualTo("com.google.guava:guava"); + assertThat(depByKey.description()).isEqualTo("Some long description about Guava"); + assertThat(depByKey.path()).isNull(); + assertThat(depByKey.qualifier()).isEqualTo("DEP"); + assertThat(depByKey.scope()).isEqualTo("DEP"); + } + private ReportComponent createSampleProjectComponentTree(ComponentDto project) { return createSampleProjectComponentTree(project.uuid(), project.getKey()); } diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepIT.java index 2ef2270e983..1215f3d873f 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepIT.java @@ -33,6 +33,7 @@ import org.sonar.api.utils.System2; import org.sonar.ce.task.projectanalysis.component.BranchPersister; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHolder; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependenciesHolder; import org.sonar.ce.task.projectanalysis.component.ProjectPersister; import org.sonar.ce.task.projectanalysis.component.ProjectViewAttributes; import org.sonar.ce.task.projectanalysis.component.SubViewAttributes; @@ -100,7 +101,8 @@ public class ViewsPersistComponentsStepIT extends BaseStepTest { BranchPersister branchPersister = mock(BranchPersister.class); ProjectPersister projectPersister = mock(ProjectPersister.class); - underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, branchPersister, projectPersister); + ProjectDependenciesHolder projectDepsHolder = mock(ProjectDependenciesHolder.class); + underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, branchPersister, projectPersister, projectDepsHolder); } @Override @@ -326,7 +328,7 @@ public class ViewsPersistComponentsStepIT extends BaseStepTest { underTest.execute(new TestComputationStepContext()); - //We create audits for the portfolio and the project, not for the technical project + // We create audits for the portfolio and the project, not for the technical project verify(auditPersister, times(2)).addComponent(any(), any()); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReader.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReader.java index 5b908c4d4a3..f59adf02fd0 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReader.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReader.java @@ -74,4 +74,6 @@ public interface BatchReportReader { CloseableIterator readCves(); CloseableIterator readTelemetryEntries(); + + CloseableIterator readDependencies(); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderImpl.java index 740d7a259d7..ee5ac203150 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderImpl.java @@ -239,4 +239,10 @@ public class BatchReportReaderImpl implements BatchReportReader { ensureInitialized(); return delegate.readTelemetryEntries(); } + + @Override + public CloseableIterator readDependencies() { + ensureInitialized(); + return delegate.readDependencies(); + } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/Component.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/Component.java index 820f0d9d039..a653c80ebc6 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/Component.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/Component.java @@ -108,10 +108,10 @@ public interface Component { ProjectAttributes getProjectAttributes(); /** - * Returns the attributes specific to components of type {@link Type#PROJECT}, {@link Type#MODULE}, + * Returns the attributes specific to components of type {@link Type#PROJECT}, * {@link Type#DIRECTORY} or {@link Type#FILE}. * - * @throws IllegalStateException when the component's type is neither {@link Type#PROJECT}, {@link Type#MODULE}, + * @throws IllegalStateException when the component's type is neither {@link Type#PROJECT}, * {@link Type#DIRECTORY} nor {@link Type#FILE}. */ ReportAttributes getReportAttributes(); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index 8f449fd604b..97ee442ea37 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -36,6 +36,7 @@ import org.sonar.ce.task.projectanalysis.component.ConfigurationRepositoryImpl; import org.sonar.ce.task.projectanalysis.component.DisabledComponentsHolderImpl; import org.sonar.ce.task.projectanalysis.component.FileStatusesImpl; import org.sonar.ce.task.projectanalysis.component.PreviousSourceHashRepositoryImpl; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependenciesHolderImpl; import org.sonar.ce.task.projectanalysis.component.ProjectPersister; import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids; import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues; @@ -52,8 +53,8 @@ import org.sonar.ce.task.projectanalysis.filemove.MutableMovedFilesRepositoryImp import org.sonar.ce.task.projectanalysis.filemove.ScoreMatrixDumperImpl; import org.sonar.ce.task.projectanalysis.filemove.SourceSimilarityImpl; import org.sonar.ce.task.projectanalysis.filesystem.ComputationTempFolderProvider; -import org.sonar.ce.task.projectanalysis.issue.AnticipatedTransitionRepositoryImpl; import org.sonar.ce.task.projectanalysis.index.IndexDiffResolverImpl; +import org.sonar.ce.task.projectanalysis.issue.AnticipatedTransitionRepositoryImpl; import org.sonar.ce.task.projectanalysis.issue.BaseIssuesLoader; import org.sonar.ce.task.projectanalysis.issue.ChangedIssuesRepository; import org.sonar.ce.task.projectanalysis.issue.CloseIssuesOnRemovedComponentsVisitor; @@ -207,6 +208,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop CrossProjectDuplicationStatusHolderImpl.class, BatchReportDirectoryHolderImpl.class, TreeRootHolderImpl.class, + ProjectDependenciesHolderImpl.class, PeriodHolderImpl.class, PrioritizedRulesHolderImpl.class, QualityGateHolderImpl.class, diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/EmptyProjectDependenciesHolder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/EmptyProjectDependenciesHolder.java new file mode 100644 index 00000000000..bfe0e9d400a --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/EmptyProjectDependenciesHolder.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import java.util.List; + +public class EmptyProjectDependenciesHolder implements ProjectDependenciesHolder { + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public List getDependencies() { + return List.of(); + } + + @Override + public int getSize() { + return 0; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolder.java new file mode 100644 index 00000000000..f6c234585e4 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolder.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import java.util.List; + +public interface MutableProjectDependenciesHolder extends ProjectDependenciesHolder { + + /** + * Sets the list of dependencies in the ProjectDependenciesHolder. + * + * @throws NullPointerException if {@code dependencies} is {@code null} + * @throws IllegalStateException if dependencies have already been set + */ + MutableProjectDependenciesHolder setDependencies(List dependencies); + +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStep.java new file mode 100644 index 00000000000..a2298f89990 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/PersistProjectDependenciesStep.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import java.time.Clock; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; +import org.sonar.ce.task.step.ComputationStep; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.dependency.ProjectDependencyDto; + +public class PersistProjectDependenciesStep implements ComputationStep { + + private final DbClient dbClient; + private final ProjectDependenciesHolder projectDependenciesHolder; + private final TreeRootHolder treeRootHolder; + private final Clock clock; + + public PersistProjectDependenciesStep(DbClient dbClient, ProjectDependenciesHolder projectDependenciesHolder, TreeRootHolder treeRootHolder, Clock clock) { + this.dbClient = dbClient; + this.projectDependenciesHolder = projectDependenciesHolder; + this.treeRootHolder = treeRootHolder; + this.clock = clock; + } + + @Override + public void execute(Context context) { + try (DbSession dbSession = dbClient.openSession(true)) { + Map existingDtosByUuids = indexExistingDtosByUuids(dbSession); + projectDependenciesHolder.getDependencies().forEach(dependency -> updateOrInsertDependency(dbSession, dependency, existingDtosByUuids)); + deleteRemainingDependencies(dbSession, existingDtosByUuids.keySet()); + dbSession.commit(); + } + } + + private void deleteRemainingDependencies(DbSession dbSession, Set uuidsToBeDeleted) { + uuidsToBeDeleted.forEach(uuid -> dbClient.projectDependenciesDao().deleteByUuid(dbSession, uuid)); + } + + private Map indexExistingDtosByUuids(DbSession dbSession) { + return dbClient.projectDependenciesDao().selectByBranchUuid(dbSession, treeRootHolder.getRoot().getUuid()).stream() + .collect(Collectors.toMap(ProjectDependencyDto::uuid, Function.identity())); + } + + private void updateOrInsertDependency(DbSession dbSession, ProjectDependency dependency, Map existingDtosByUuids) { + ProjectDependencyDto existingDependency = existingDtosByUuids.remove(dependency.getUuid()); + long now = clock.millis(); + if (existingDependency == null) { + dbClient.projectDependenciesDao().insert(dbSession, toDto(dependency, now, now)); + } else if (shouldUpdate(existingDependency, dependency)) { + dbClient.projectDependenciesDao().update(dbSession, toDto(dependency, existingDependency.createdAt(), now)); + } + } + + private static ProjectDependencyDto toDto(ProjectDependency dependency, long createdAt, long updatedAt) { + return new ProjectDependencyDto(dependency.getUuid(), dependency.getVersion(), null, dependency.getPackageManager(), createdAt, updatedAt); + } + + private static boolean shouldUpdate(ProjectDependencyDto existing, ProjectDependency target) { + return !StringUtils.equals(existing.version(), target.getVersion()) || + !StringUtils.equals(existing.packageManager(), target.getPackageManager()); + } + + @Override + public String getDescription() { + return "Persist project dependencies"; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolder.java new file mode 100644 index 00000000000..d5730ceb6c7 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolder.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import java.util.List; + +/** + * The list of project dependencies defined in the scanner report. + */ +public interface ProjectDependenciesHolder { + /** + * @return true if the holder is empty + */ + boolean isEmpty(); + + /** + * + * @throws IllegalStateException if the holder is empty (ie. there is no dependencies yet) + */ + List getDependencies(); + + /** + * Number of project dependencies. + * + * @throws IllegalStateException if the holder is empty (ie. there is no dependencies yet) + */ + int getSize(); + +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolderImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolderImpl.java new file mode 100644 index 00000000000..54aba72105e --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependenciesHolderImpl.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; + +/** + * Holds the reference to the project dependencies for the current CE run. + */ +public class ProjectDependenciesHolderImpl implements MutableProjectDependenciesHolder { + @CheckForNull + private Map dependenciesByUuid = null; + + @Override + public boolean isEmpty() { + return this.dependenciesByUuid == null; + } + + @Override + public MutableProjectDependenciesHolder setDependencies(List dependencies) { + checkState(this.dependenciesByUuid == null, "dependencies can not be set twice in holder"); + this.dependenciesByUuid = requireNonNull(dependencies, "dependencies can not be null").stream().collect(ImmutableMap.toImmutableMap(ProjectDependency::getUuid, d -> d)); + return this; + } + + @Override + public List getDependencies() { + checkInitialized(); + return List.copyOf(dependenciesByUuid.values()); + } + + @Override + public int getSize() { + checkInitialized(); + return dependenciesByUuid.size(); + } + + private void checkInitialized() { + checkState(this.dependenciesByUuid != null, "Holder has not been initialized yet"); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependency.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependency.java new file mode 100644 index 00000000000..d3cdc40ccd4 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependency.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import javax.annotation.CheckForNull; + +public interface ProjectDependency { + + /** + * Returns the dependency uuid. + */ + String getUuid(); + + /** + * Returns the dependency key. + */ + String getKey(); + + /** + * The dependency fully qualified name, without version. For Maven this is the groupId:artifactId. + */ + String getFullName(); + + /** + * The dependency name. For Maven this is only the artifactId. + */ + String getName(); + + /** + * The optional description of the dependency. + */ + @CheckForNull + String getDescription(); + + /** + * The optional version of the dependency. + */ + @CheckForNull + String getVersion(); + + /** + * The optional package manager of the dependency (e.g. mvn, npm, nuget, ...). + */ + @CheckForNull + String getPackageManager(); +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImpl.java new file mode 100644 index 00000000000..64c9a67d0d9 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImpl.java @@ -0,0 +1,183 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import com.google.common.base.MoreObjects; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.StringUtils.abbreviate; +import static org.apache.commons.lang3.StringUtils.trimToNull; +import static org.sonar.db.component.ComponentValidator.MAX_COMPONENT_DESCRIPTION_LENGTH; +import static org.sonar.db.component.ComponentValidator.MAX_COMPONENT_NAME_LENGTH; + +@Immutable +public class ProjectDependencyImpl implements ProjectDependency { + private final String fullName; + private final String name; + private final String key; + private final String uuid; + + @CheckForNull + private final String description; + @CheckForNull + private final String version; + @CheckForNull + private final String packageManager; + + private ProjectDependencyImpl(Builder builder) { + this.key = builder.key; + this.fullName = builder.fullName; + this.name = MoreObjects.firstNonNull(builder.name, builder.fullName).intern(); + this.description = builder.description; + this.uuid = builder.uuid; + this.version = builder.version; + this.packageManager = builder.packageManager; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getFullName() { + return this.fullName; + } + + @Override + public String getName() { + return this.name; + } + + @Override + @CheckForNull + public String getDescription() { + return this.description; + } + + @CheckForNull + @Override + public String getVersion() { + return version; + } + + @CheckForNull + @Override + public String getPackageManager() { + return packageManager; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private static final String KEY_CANNOT_BE_NULL = "Key can't be null"; + private static final String UUID_CANNOT_BE_NULL = "uuid can't be null"; + private static final String NAME_CANNOT_BE_NULL = "name can't be null"; + + private String uuid; + private String key; + private String fullName; + private String name; + private String description; + private String version; + private String packageManager; + + public Builder setUuid(String s) { + this.uuid = requireNonNull(s, UUID_CANNOT_BE_NULL); + return this; + } + + public Builder setKey(String key) { + this.key = requireNonNull(key, KEY_CANNOT_BE_NULL); + return this; + } + + public Builder setFullName(String fullName) { + this.fullName = abbreviate(requireNonNull(fullName, NAME_CANNOT_BE_NULL), MAX_COMPONENT_NAME_LENGTH); + return this; + } + + public Builder setName(String name) { + this.name = abbreviate(requireNonNull(name, NAME_CANNOT_BE_NULL), MAX_COMPONENT_NAME_LENGTH); + return this; + } + + public Builder setDescription(@Nullable String description) { + this.description = abbreviate(trimToNull(description), MAX_COMPONENT_DESCRIPTION_LENGTH); + return this; + } + + public Builder setVersion(@Nullable String version) { + this.version = trimToNull(version); + return this; + } + + public Builder setPackageManager(@Nullable String packageManager) { + this.packageManager = trimToNull(packageManager); + return this; + } + + public ProjectDependencyImpl build() { + requireNonNull(uuid, UUID_CANNOT_BE_NULL); + requireNonNull(key, KEY_CANNOT_BE_NULL); + requireNonNull(name, NAME_CANNOT_BE_NULL); + return new ProjectDependencyImpl(this); + } + + } + + @Override + public String toString() { + return "ComponentImpl{" + + "fullName='" + fullName + '\'' + + ", key='" + key + '\'' + + ", uuid='" + uuid + '\'' + + ", description='" + description + '\'' + + '}'; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProjectDependencyImpl component = (ProjectDependencyImpl) o; + return uuid.equals(component.uuid); + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStep.java new file mode 100644 index 00000000000..12ece101abf --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildProjectDependenciesStep.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.step; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.UnaryOperator; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; +import org.sonar.ce.task.projectanalysis.component.ComponentKeyGenerator; +import org.sonar.ce.task.projectanalysis.component.ComponentUuidFactory; +import org.sonar.ce.task.projectanalysis.component.ComponentUuidFactoryImpl; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; +import org.sonar.ce.task.projectanalysis.dependency.MutableProjectDependenciesHolder; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependency; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependencyImpl; +import org.sonar.ce.task.step.ComputationStep; +import org.sonar.core.util.CloseableIterator; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.apache.commons.lang3.StringUtils.trimToNull; + +/** + * Populates the {@link MutableProjectDependenciesHolder} from the {@link BatchReportReader} + */ +public class BuildProjectDependenciesStep implements ComputationStep { + + private final DbClient dbClient; + private final BatchReportReader reportReader; + private final MutableProjectDependenciesHolder projectDependenciesHolder; + private final TreeRootHolder treeRootHolder; + private final AnalysisMetadataHolder analysisMetadataHolder; + + public BuildProjectDependenciesStep(DbClient dbClient, BatchReportReader reportReader, MutableProjectDependenciesHolder projectDependenciesHolder, TreeRootHolder treeRootHolder, + AnalysisMetadataHolder analysisMetadataHolder) { + this.dbClient = dbClient; + this.reportReader = reportReader; + this.projectDependenciesHolder = projectDependenciesHolder; + this.treeRootHolder = treeRootHolder; + this.analysisMetadataHolder = analysisMetadataHolder; + } + + @Override + public String getDescription() { + return "Build list of dependencies"; + } + + @Override + public void execute(Context context) { + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentKeyGenerator keyGenerator = loadKeyGenerator(); + + // root key of branch, not necessarily of project + String rootKey = treeRootHolder.getRoot().getKey(); + // loads the UUIDs from database. If they don't exist, then generate new ones + ComponentUuidFactory componentUuidFactory = new ComponentUuidFactoryImpl(dbClient, dbSession, rootKey, analysisMetadataHolder.getBranch()); + + List dependencies = new ArrayList<>(); + try (CloseableIterator reportDependencies = reportReader.readDependencies()) { + while (reportDependencies.hasNext()) { + ScannerReport.Dependency reportDependency = reportDependencies.next(); + ProjectDependency dependency = buildDependency(reportDependency, componentUuidFactory::getOrCreateForKey, keyGenerator, rootKey); + dependencies.add(dependency); + } + } + + projectDependenciesHolder.setDependencies(dependencies); + + context.getStatistics().add("dependencies", projectDependenciesHolder.getSize()); + } + } + + private static ProjectDependency buildDependency(ScannerReport.Dependency reportDependency, UnaryOperator uuidSupplier, ComponentKeyGenerator keyGenerator, + String rootKey) { + String dependencyKey = keyGenerator.generateKey(rootKey, reportDependency.getKey()); + String uuid = uuidSupplier.apply(dependencyKey); + ProjectDependencyImpl.Builder builder = ProjectDependencyImpl.builder() + .setUuid(uuid) + .setKey(dependencyKey) + .setFullName(reportDependency.hasFullName() ? reportDependency.getFullName() : reportDependency.getName()) + .setName(reportDependency.getName()) + .setDescription(reportDependency.hasDescription() ? trimToNull(reportDependency.getDescription()) : null) + .setVersion(reportDependency.hasVersion() ? trimToNull(reportDependency.getVersion()) : null) + .setPackageManager(reportDependency.hasPackageManager() ? trimToNull(reportDependency.getPackageManager()): null); + return builder.build(); + } + + private ComponentKeyGenerator loadKeyGenerator() { + return analysisMetadataHolder.getBranch(); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStep.java index f6acc76ccd1..fd8af1dacfd 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStep.java @@ -37,6 +37,8 @@ import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHold import org.sonar.ce.task.projectanalysis.component.PathAwareCrawler; import org.sonar.ce.task.projectanalysis.component.PathAwareVisitor; import org.sonar.ce.task.projectanalysis.component.PathAwareVisitorAdapter; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependenciesHolder; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependency; import org.sonar.ce.task.projectanalysis.component.ProjectPersister; import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; import org.sonar.ce.task.step.ComputationStep; @@ -49,6 +51,7 @@ import static java.util.Optional.ofNullable; import static org.sonar.api.resources.Qualifiers.PROJECT; import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER; import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT; +import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR; import static org.sonar.db.component.ComponentDto.formatUuidPathFromParent; /** @@ -61,15 +64,18 @@ public class PersistComponentsStep implements ComputationStep { private final MutableDisabledComponentsHolder disabledComponentsHolder; private final BranchPersister branchPersister; private final ProjectPersister projectPersister; + private final ProjectDependenciesHolder projectDependenciesHolder; public PersistComponentsStep(DbClient dbClient, TreeRootHolder treeRootHolder, System2 system2, - MutableDisabledComponentsHolder disabledComponentsHolder, BranchPersister branchPersister, ProjectPersister projectPersister) { + MutableDisabledComponentsHolder disabledComponentsHolder, BranchPersister branchPersister, ProjectPersister projectPersister, + ProjectDependenciesHolder projectDependenciesHolder) { this.dbClient = dbClient; this.treeRootHolder = treeRootHolder; this.system2 = system2; this.disabledComponentsHolder = disabledComponentsHolder; this.branchPersister = branchPersister; this.projectPersister = projectPersister; + this.projectDependenciesHolder = projectDependenciesHolder; } @Override @@ -155,7 +161,7 @@ public class PersistComponentsStep implements ComputationStep { @Override public ComponentDtoHolder createForProjectView(Component projectView) { - // no need to create holder for file since they are always leaves of the Component tree + // no need to create holder for project views since they are always leaves of the Component tree return null; } }); @@ -166,7 +172,17 @@ public class PersistComponentsStep implements ComputationStep { @Override public void visitProject(Component project, Path path) { ComponentDto dto = createForProject(project); - path.current().setDto(persistComponent(dto)); + ComponentDto persistedProject = persistComponent(dto); + path.current().setDto(persistedProject); + + persistDependencies(persistedProject); + } + + private void persistDependencies(ComponentDto projectDto) { + for (ProjectDependency dep : projectDependenciesHolder.getDependencies()) { + ComponentDto dto = createForDependency(dep, projectDto); + persistComponent(dto, false); + } } @Override @@ -323,6 +339,26 @@ public class PersistComponentsStep implements ComputationStep { return res; } + private ComponentDto createForDependency(ProjectDependency dependency, ComponentDto projectDto) { + ComponentDto componentDto = new ComponentDto(); + componentDto.setUuid(dependency.getUuid()); + componentDto.setKey(dependency.getKey()); + componentDto.setEnabled(true); + componentDto.setCreatedAt(new Date(system2.now())); + + componentDto.setScope("DEP"); + componentDto.setQualifier("DEP"); + componentDto.setName(dependency.getName()); + componentDto.setLongName(dependency.getFullName()); + componentDto.setDescription(dependency.getDescription()); + + var projectUuid = projectDto.uuid(); + componentDto.setBranchUuid(projectUuid); + componentDto.setUuidPath(UUID_PATH_OF_ROOT + projectUuid + UUID_PATH_SEPARATOR); + + return componentDto; + } + private ComponentDto createBase(Component component) { String componentKey = component.getKey(); String componentUuid = component.getUuid(); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java index 00c21bd5357..2b03acf02aa 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java @@ -22,6 +22,7 @@ package org.sonar.ce.task.projectanalysis.step; import java.util.Arrays; import java.util.List; import org.sonar.ce.task.container.TaskContainer; +import org.sonar.ce.task.projectanalysis.dependency.PersistProjectDependenciesStep; import org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStep; import org.sonar.ce.task.projectanalysis.filemove.PullRequestFileMoveDetectionStep; import org.sonar.ce.task.projectanalysis.language.HandleUnanalyzedLanguagesStep; @@ -53,6 +54,9 @@ public class ReportComputationSteps extends AbstractComputationSteps { ValidateProjectStep.class, LoadQualityProfilesStep.class, + // Dependencies + BuildProjectDependenciesStep.class, + // Pre analysis operations PreMeasuresComputationChecksStep.class, SqUpgradeDetectionEventsStep.class, @@ -104,6 +108,7 @@ public class ReportComputationSteps extends AbstractComputationSteps { // Persist data PersistScannerAnalysisCacheStep.class, PersistComponentsStep.class, + PersistProjectDependenciesStep.class, PersistAnalysisStep.class, PersistAnalysisPropertiesStep.class, PersistProjectMeasuresStep.class, diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderRule.java index 9eda82d4f2c..a87484dcffc 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderRule.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderRule.java @@ -64,6 +64,7 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader, After private byte[] analysisCache; private List cves = new ArrayList<>(); private List telemetryEntries = new ArrayList<>(); + private List dependencies = new ArrayList<>(); @Override public Statement apply(final Statement statement, Description description) { @@ -348,4 +349,14 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader, After this.cves = cves; return this; } + + @Override + public CloseableIterator readDependencies() { + return CloseableIterator.from(dependencies.iterator()); + } + + public BatchReportReaderRule putDependencies(List dependencies) { + this.dependencies = dependencies; + return this; + } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImplTest.java new file mode 100644 index 00000000000..395681c5174 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/dependency/ProjectDependencyImplTest.java @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import org.junit.jupiter.api.Test; + +import static com.google.common.base.Strings.repeat; +import static org.assertj.core.api.Assertions.assertThat; + +class ProjectDependencyImplTest { + + static final String KEY = "KEY"; + static final String UUID = "UUID"; + + @Test + void builder_shouldSetKeyUuidAndName() { + ProjectDependencyImpl dep = buildSimpleDependency(KEY).setUuid(UUID).setName("name").build(); + + assertThat(dep.getKey()).isEqualTo(KEY); + assertThat(dep.getUuid()).isEqualTo(UUID); + assertThat(dep.getName()).isEqualTo("name"); + } + + @Test + void builder_shouldKeep500FirstCharactersOfName() { + String veryLongString = repeat("a", 3_000); + + ProjectDependencyImpl underTest = buildSimpleDependency("dep") + .setFullName(veryLongString) + .build(); + + String expectedName = repeat("a", 500 - 3) + "..."; + assertThat(underTest.getFullName()).isEqualTo(expectedName); + } + + @Test + void builder_shouldKeep2000FirstCharactersOfDescription() { + String veryLongString = repeat("a", 3_000); + + ProjectDependencyImpl underTest = buildSimpleDependency("dep") + .setDescription(veryLongString) + .build(); + + String expectedDescription = repeat("a", 2_000 - 3) + "..."; + assertThat(underTest.getDescription()).isEqualTo(expectedDescription); + } + + @Test + void builder_shouldSetOptionalVersionAndPackageManager() { + ProjectDependencyImpl underTest = buildSimpleDependency("dep") + .setVersion("1.0") + .setPackageManager("mvn") + .build(); + + assertThat(underTest.getVersion()).isEqualTo("1.0"); + assertThat(underTest.getPackageManager()).isEqualTo("mvn"); + } + + @Test + void equals_shouldComparesOnUuidOnly() { + ProjectDependencyImpl.Builder builder = buildSimpleDependency("1").setUuid(UUID); + + assertThat(builder.build()).isEqualTo(builder.build()); + assertThat(builder.build()).isEqualTo(buildSimpleDependency("2").setUuid(UUID).build()); + assertThat(builder.build()).isNotEqualTo(buildSimpleDependency("1").setUuid("otherUUid").build()); + } + + @Test + void hashCode_shouldBeHashcodeOfUuid() { + ProjectDependencyImpl.Builder builder = buildSimpleDependency("1").setUuid(UUID); + + assertThat(builder.build()).hasSameHashCodeAs(builder.build().hashCode()); + assertThat(builder.build()).hasSameHashCodeAs(buildSimpleDependency("2").setUuid(UUID).build().hashCode()); + assertThat(builder.build()).hasSameHashCodeAs(UUID.hashCode()); + } + + private static ProjectDependencyImpl.Builder buildSimpleDependency(String key) { + return ProjectDependencyImpl.builder() + .setName("name_" + key) + .setKey(key) + .setUuid("uuid_" + key); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStepTest.java index bcdefd09294..25781b68f07 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistComponentsStepTest.java @@ -24,6 +24,7 @@ import org.sonar.api.utils.System2; import org.sonar.ce.task.projectanalysis.component.BranchPersister; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHolder; +import org.sonar.ce.task.projectanalysis.dependency.ProjectDependenciesHolder; import org.sonar.ce.task.projectanalysis.component.ProjectPersister; import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; import org.sonar.ce.task.step.TestComputationStepContext; @@ -54,13 +55,17 @@ public class PersistComponentsStepTest { doReturn(componentDao).when(dbClient).componentDao(); doReturn(emptyList()).when(componentDao).selectByBranchUuid(eq(projectKey), any(DbSession.class)); - assertThatThrownBy(() -> new PersistComponentsStep( + var underTest = new PersistComponentsStep( dbClient, treeRootHolder, System2.INSTANCE, mock(MutableDisabledComponentsHolder.class), mock(BranchPersister.class), - mock(ProjectPersister.class)).execute(new TestComputationStepContext())) + mock(ProjectPersister.class), + mock(ProjectDependenciesHolder.class)); + + var context = new TestComputationStepContext(); + assertThatThrownBy(() -> underTest.execute(context)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("The project '" + projectKey + "' is not stored in the database, during a project analysis"); } diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolderRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolderRule.java new file mode 100644 index 00000000000..a483ff2a56c --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/dependency/MutableProjectDependenciesHolderRule.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ce.task.projectanalysis.dependency; + +import java.util.List; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.rules.ExternalResource; + +public class MutableProjectDependenciesHolderRule extends ExternalResource implements MutableProjectDependenciesHolder, AfterEachCallback { + + private ProjectDependenciesHolderImpl delegate = new ProjectDependenciesHolderImpl(); + + @Override + protected void after() { + delegate = new ProjectDependenciesHolderImpl(); + } + + public MutableProjectDependenciesHolderRule setDependencies(List dependencies) { + delegate.setDependencies(dependencies); + return this; + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public List getDependencies() { + return delegate.getDependencies(); + } + + @Override + public int getSize() { + return delegate.getSize(); + } + + @Override + public void afterEach(ExtensionContext extensionContext) { + after(); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesDao.java index ae518d1217c..57144fdd1ba 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesDao.java @@ -20,6 +20,7 @@ package org.sonar.db.dependency; import java.util.List; +import java.util.Optional; import org.sonar.db.Dao; import org.sonar.db.DbSession; import org.sonar.db.Pagination; @@ -38,6 +39,17 @@ public class ProjectDependenciesDao implements Dao { mapper(session).deleteByUuid(uuid); } + public Optional selectByUuid(DbSession dbSession, String uuid) { + return Optional.ofNullable(mapper(dbSession).selectByUuid(uuid)); + } + + /** + * Retrieves all dependencies with a specific branch UUID, no other filtering is done by this method. + */ + public List selectByBranchUuid(DbSession dbSession, String branchUuid) { + return mapper(dbSession).selectByBranchUuid(branchUuid); + } + public List selectByQuery(DbSession session, ProjectDependenciesQuery projectDependenciesQuery, Pagination pagination) { return mapper(session).selectByQuery(projectDependenciesQuery, pagination); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesMapper.java index e1d8a2b9bda..fc5b03aae1f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/dependency/ProjectDependenciesMapper.java @@ -28,6 +28,10 @@ public interface ProjectDependenciesMapper { void deleteByUuid(String uuid); + ProjectDependencyDto selectByUuid(String uuid); + + List selectByBranchUuid(String branchUuid); + List selectByQuery(@Param("query") ProjectDependenciesQuery query, @Param("pagination") Pagination pagination); void update(ProjectDependencyDto dto); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/dependency/ProjectDependenciesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/dependency/ProjectDependenciesMapper.xml index e48e7973767..380c6f2d14a 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/dependency/ProjectDependenciesMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/dependency/ProjectDependenciesMapper.xml @@ -33,6 +33,19 @@ where uuid = #{uuid,jdbcType=VARCHAR} + + + +