Repository: CycloneDX/cyclonedx-maven-plugin (java)

Issue:
Dependency Graph missing transitive dependencies when dependency has multiple sources
When cyclonedx-maven-plugin 2.5.1 generates a BOM for a project that has direct dependencies X and Y and X and Y *both* have Z as a transitive dependency, then the dependency graph will describe Z as only being a dependency of X and will ignore it also being a dependency of Y.

This causes problems when Z has a vulnerability and X has an update available that contains a fixed version of Z, but Y does not.  The dependency graph would suggest that updating X is sufficient to deal with the vulnerability.  But it is not...  and only AFTER the upgrade is completed and a new BOM generated will the dependency graph now show the dependency of Y on Z.

The problem can be illustrated by setting up a simple project with the following two dependencies
```
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-broker</artifactId>
    <version>5.16.2</version>
</dependency>
<dependency>
    <groupId>org.apache.cassandra</groupId>
    <artifactId>cassandra-all</artifactId>
    <version>4.0.0</version>
</dependency>
```

Both have `com.fasterxml.jackson.core:jackson-databind` 2.9.10.8 as a dependency.

In plugin 2.5.1 BOM graph, jackson-databind is only reported as being a dependency of activemq-broker

Pull request description:
Allow dependencies to appear in graph multiple times
Resolves #116 by updating the CycloneDX MOJOs to use the same mechanism `mvn dependency:tree -Dverbose` does when building the dependency graph.

This PR also moves to the latest version of `maven-dependency-tree` to address [a bug](https://issues.apache.org/jira/browse/MSHARED-994) the previous version contained that would otherwise result in this change breaking the graphs for WARs.



Patch:
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index cd8da25..50af92d 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -17,7 +17,6 @@ jobs:
     - name: Build with Maven
       run: mvn package plugin:report site --file pom.xml
     - name: Deploy documentation
-      if: ${{ github.ref == 'refs/heads/master' }}
       uses: JamesIves/github-pages-deploy-action@releases/v3
       with:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/pom.xml b/pom.xml
index c6237c4..b38a7cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -129,7 +129,7 @@
         <dependency>
             <groupId>org.apache.maven.shared</groupId>
             <artifactId>maven-dependency-tree</artifactId>
-            <version>3.1.0</version>
+            <version>3.1.1</version>
         </dependency>
         <dependency>
             <groupId>org.apache.maven.shared</groupId>
@@ -156,7 +156,7 @@
             <version>3.6.4</version>
             <scope>provided</scope>
         </dependency>
-        
+
         <dependency>
             <groupId>io.takari.maven.plugins</groupId>
             <artifactId>takari-plugin-integration-testing</artifactId>
diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
index 4c58063..3d6ce87 100644
--- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
+++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
@@ -41,8 +41,8 @@ import org.apache.maven.project.MavenProjectHelper;
 import org.apache.maven.project.ProjectBuildingRequest;
 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
 import org.apache.maven.shared.dependency.graph.DependencyNode;
 import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
 import org.codehaus.plexus.context.Context;
@@ -156,7 +156,7 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo implements Contextu
     private MavenProjectHelper mavenProjectHelper;
 
     @org.apache.maven.plugins.annotations.Component(hint = "default")
-    private DependencyGraphBuilder dependencyGraphBuilder;
+    private DependencyCollectorBuilder dependencyCollectorBuilder;
 
     @SuppressWarnings("CanBeFinal")
     @Parameter(property = "cyclonedx.skip", defaultValue = "false", required = false)
@@ -901,14 +901,14 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo implements Contextu
             buildingRequest.setProject(this.project);
         }
         try {
-            final DependencyNode rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, artifactFilter);
+            final DependencyNode rootNode = dependencyCollectorBuilder.collectDependencyGraph(buildingRequest, artifactFilter);
             buildDependencyGraphNode(componentRefs, dependencies, rootNode, null);
             final CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor();
             rootNode.accept(visitor);
             for (final DependencyNode dependencyNode : visitor.getNodes()) {
                 buildDependencyGraphNode(componentRefs, dependencies, dependencyNode, null);
             }
-        } catch (DependencyGraphBuilderException e) {
+        } catch (DependencyCollectorBuilderException e) {
             if (mavenProject != null) {
                 // When executing makeAggregateBom, some projects may not yet be built. Workaround is to warn on this
                 // rather than throwing an exception https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/55
diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
index 116cecf..7e815c6 100644
--- a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
+++ b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
@@ -62,7 +62,7 @@ public class CycloneDxAggregateMojo extends BaseCycloneDxMojo {
             getLog().info("Skipping CycloneDX");
             return;
         }
-        logParameters();
+
         final Set<Component> components = new LinkedHashSet<>();
         final Set<String> componentRefs = new LinkedHashSet<>();
         final Map<String, ProjectDependencyAnalysis> dependencyAnalysisMap = new LinkedHashMap<>();
diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
index cdf03b9..7a8a152 100644
--- a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
+++ b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
@@ -53,7 +53,7 @@ public class CycloneDxPackageMojo extends BaseCycloneDxMojo {
             getLog().info("Skipping CycloneDX");
             return;
         }
-        logParameters();
+
         final Set<Component> components = new LinkedHashSet<>();
         final Set<String> componentRefs = new LinkedHashSet<>();
 


Test patch:
diff --git a/src/test/java/org/cyclonedx/maven/Issue116Test.java b/src/test/java/org/cyclonedx/maven/Issue116Test.java
new file mode 100644
index 0000000..466adc8
--- /dev/null
+++ b/src/test/java/org/cyclonedx/maven/Issue116Test.java
@@ -0,0 +1,83 @@
+package org.cyclonedx.maven;
+
+import io.takari.maven.testing.TestResources;
+import io.takari.maven.testing.executor.MavenRuntime;
+import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
+import io.takari.maven.testing.executor.MavenVersions;
+import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner;
+import java.io.*;
+import java.util.Properties;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+@RunWith(MavenJUnitTestRunner.class)
+@MavenVersions({"3.6.3"})
+public class Issue116Test {
+
+    @Rule
+    public final TestResources resources = new TestResources(
+            "target/test-classes",
+            "target/test-classes/transformed-projects"
+    );
+
+    public final MavenRuntime verifier;
+
+    public Issue116Test(MavenRuntimeBuilder runtimeBuilder)
+            throws Exception {
+        this.verifier = runtimeBuilder.build(); //.withCliOptions(opts) // //
+    }
+
+    @Test
+    public void testPluginWithActiviti() throws Exception {
+        File projectDirTransformed = new File(
+                "target/test-classes/transformed-projects/issue-116"
+        );
+        if (projectDirTransformed.exists()) {
+            FileUtils.cleanDirectory(projectDirTransformed);
+            projectDirTransformed.delete();
+        }
+
+        File projDir = resources.getBasedir("issue-116");
+
+        Properties props = new Properties();
+
+        props.load(Issue116Test.class.getClassLoader().getResourceAsStream("test.properties"));
+        String projectVersion = (String) props.get("project.version");
+        verifier
+                .forProject(projDir) //
+                .withCliOption("-Dtest.input.version=" + projectVersion) // debug
+                .withCliOption("-X") // debug
+                .withCliOption("-B")
+                .execute("clean", "package")
+                .assertErrorFreeLog();
+
+        // assert commons-lang3 has appeared in the dependency graph multiple times
+        String bomContents = fileRead(new File(projDir, "target/bom.xml"), true);
+        int matches = StringUtils.countMatches(bomContents, "<dependency ref=\"pkg:maven/org.apache.commons/commons-lang3@3.1?type=jar\"/>");
+        assertEquals(4, matches); // 1 for the definition, 3 for each of its usages
+    }
+
+    // source: https://github.com/takari/takari-plugin-testing-project/blob/master/takari-plugin-testing/src/main/java/io/takari/maven/testing/AbstractTestResources.java#L103
+    private static String fileRead(File file, boolean normalizeEOL) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
+            if (normalizeEOL) {
+                String str;
+                while ((str = r.readLine()) != null) {
+                    sb.append(str).append('\n');
+                }
+            } else {
+                int ch;
+                while ((ch = r.read()) != -1) {
+                    sb.append((char) ch);
+                }
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/test/resources/issue-116/pom.xml b/src/test/resources/issue-116/pom.xml
index fc51a36..03c99a8 100644
--- a/src/test/resources/issue-116/pom.xml
+++ b/src/test/resources/issue-116/pom.xml
@@ -28,14 +28,9 @@
 
     <dependencies>
         <dependency>
-            <groupId>org.apache.activemq</groupId>
-            <artifactId>activemq-broker</artifactId>
-            <version>5.16.2</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.cassandra</groupId>
-            <artifactId>cassandra-all</artifactId>
-            <version>4.0.0</version>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-engine</artifactId>
+            <version>5.14</version>
         </dependency>
     </dependencies>
     <build>
@@ -43,7 +38,7 @@
             <plugin>
                 <groupId>org.cyclonedx</groupId>
                 <artifactId>cyclonedx-maven-plugin</artifactId>
-                <version>2.5.1</version>
+                <version>${test.input.version}</version>
                 <executions>
                     <execution>
                         <phase>package</phase>