diff --git a/src/it/makeAggregateBom/verify.groovy b/src/it/makeAggregateBom/verify.groovy
index 5228fda4..d235184c 100644
--- a/src/it/makeAggregateBom/verify.groovy
+++ b/src/it/makeAggregateBom/verify.groovy
@@ -56,8 +56,8 @@ assertBomEqualsNonAggregate("impls/impl-B/target/bom")
// dependencies for root component in makeAggregateBom is the list of modules
String bom = new File(basedir, 'target/bom.xml').text
-String rootDependencies = bom.substring(bom.indexOf(''), bom.indexOf('') + 13)
-assert rootDependencies.contains('')
-assert rootDependencies.contains('')
-assert rootDependencies.contains('')
+String rootDependencies = bom.substring(bom.indexOf(' components = new LinkedHashSet<>();
- final Set dependencies = new LinkedHashSet<>();
+ final Map componentMap = new LinkedHashMap<>();
+ final Map dependencyMap = new LinkedHashMap<>();
+ final Map projectIdentities = new LinkedHashMap<>();
- String analysis = extractComponentsAndDependencies(components, dependencies);
+ String analysis = extractComponentsAndDependencies(componentMap, dependencyMap, projectIdentities);
if (analysis != null) {
List scopes = new ArrayList<>();
if (includeCompileScope) scopes.add("compile");
@@ -258,17 +262,25 @@ public void execute() throws MojoExecutionException {
if (includeTestScope) scopes.add("test");
final Metadata metadata = modelConverter.convert(project, analysis + " " + String.join("+", scopes), projectType, schemaVersion(), includeLicenseText);
- projectDependenciesConverter.cleanupBomDependencies(metadata, components, dependencies);
- generateBom(analysis, metadata, components, dependencies);
+ final Component rootComponent = metadata.getComponent();
+ final String rootBomRef = projectIdentities.get(rootComponent.getPurl());
+ if (rootBomRef != null) {
+ componentMap.remove(rootBomRef);
+ metadata.getComponent().setBomRef(rootBomRef);
+ }
+
+ projectDependenciesConverter.cleanupBomDependencies(metadata, componentMap, dependencyMap);
+
+ generateBom(analysis, metadata, new ArrayList<>(componentMap.values()), new ArrayList<>(dependencyMap.values()));
}
}
- private void generateBom(String analysis, Metadata metadata, Set components, Set dependencies) throws MojoExecutionException {
+ private void generateBom(String analysis, Metadata metadata, List components, List dependencies) throws MojoExecutionException {
try {
getLog().info(String.format(MESSAGE_CREATING_BOM, schemaVersion, components.size()));
final Bom bom = new Bom();
- bom.setComponents(new ArrayList<>(components));
+ bom.setComponents(components);
if (schemaVersion().getVersion() >= 1.1 && includeBomSerialNumber) {
bom.setSerialNumber("urn:uuid:" + UUID.randomUUID());
@@ -276,7 +288,7 @@ private void generateBom(String analysis, Metadata metadata, Set comp
if (schemaVersion().getVersion() >= 1.2) {
bom.setMetadata(metadata);
- bom.setDependencies(new ArrayList<>(dependencies));
+ bom.setDependencies(dependencies);
}
/*if (schemaVersion().getVersion() >= 1.3) {
@@ -333,7 +345,7 @@ private void saveBomToFile(String bomString, String extension, Parser bomParser)
}
}
- protected Set extractBOMDependencies(MavenProject mavenProject) throws MojoExecutionException {
+ protected Map extractBOMDependencies(MavenProject mavenProject) throws MojoExecutionException {
ProjectDependenciesConverter.MavenDependencyScopes include = new ProjectDependenciesConverter.MavenDependencyScopes(includeCompileScope, includeProvidedScope, includeRuntimeScope, includeTestScope, includeSystemScope);
return projectDependenciesConverter.extractBOMDependencies(mavenProject, include, excludeTypes);
}
@@ -378,4 +390,78 @@ protected void logParameters() {
getLog().info("------------------------------------------------------------------------");
}
}
+
+ protected void populateComponents(final Map components, final Set artifacts, final Map purlToIdentity, final ProjectDependencyAnalysis dependencyAnalysis) {
+ for (Artifact artifact: artifacts) {
+ final String purl = generatePackageUrl(artifact);
+ final String identity = purlToIdentity.get(purl);
+ if (identity != null) {
+ final Component.Scope artifactScope = (dependencyAnalysis != null ? inferComponentScope(artifact, dependencyAnalysis) : null);
+ final Component component = components.get(identity);
+ if (component == null) {
+ final Component newComponent = convert(artifact);
+ newComponent.setBomRef(identity);
+ newComponent.setScope(artifactScope);
+ components.put(identity, newComponent);
+ } else {
+ component.setScope(mergeScopes(component.getScope(), artifactScope));
+ }
+ }
+ }
+ }
+
+ /**
+ * Infer BOM component scope (required/optional/excluded) based on Maven project dependency analysis.
+ *
+ * @param artifact Artifact from maven project
+ * @param projectDependencyAnalysis Maven Project Dependency Analysis data
+ *
+ * @return Component.Scope - REQUIRED: If the component is used (as detected by project dependency analysis). OPTIONAL: If it is unused
+ */
+ protected Component.Scope inferComponentScope(Artifact artifact, ProjectDependencyAnalysis projectDependencyAnalysis) {
+ if (projectDependencyAnalysis == null) {
+ return null;
+ }
+
+ Set usedDeclaredArtifacts = projectDependencyAnalysis.getUsedDeclaredArtifacts();
+ Set usedUndeclaredArtifacts = projectDependencyAnalysis.getUsedUndeclaredArtifacts();
+ Set unusedDeclaredArtifacts = projectDependencyAnalysis.getUnusedDeclaredArtifacts();
+ Set testArtifactsWithNonTestScope = projectDependencyAnalysis.getTestArtifactsWithNonTestScope();
+
+ // Is the artifact used?
+ if (usedDeclaredArtifacts.contains(artifact) || usedUndeclaredArtifacts.contains(artifact)) {
+ return Component.Scope.REQUIRED;
+ }
+
+ // Is the artifact unused or test?
+ if (unusedDeclaredArtifacts.contains(artifact) || testArtifactsWithNonTestScope.contains(artifact)) {
+ return Component.Scope.OPTIONAL;
+ }
+
+ return null;
+ }
+
+ private Component.Scope mergeScopes(final Component.Scope existing, final Component.Scope project) {
+ // If scope is null we don't know anything about the artifact, so we assume it's not optional.
+ // This is likely a result of the dependency analysis part being unable to run.
+ final Component.Scope merged;
+ if (existing == null) {
+ merged = (project == Component.Scope.REQUIRED ? Component.Scope.REQUIRED : null);
+ } else {
+ switch (existing) {
+ case REQUIRED:
+ merged = Component.Scope.REQUIRED;
+ break;
+ case OPTIONAL:
+ merged = (project == Component.Scope.REQUIRED || project == null ? project : existing);
+ break;
+ case EXCLUDED:
+ merged = (project != Component.Scope.EXCLUDED ? project : Component.Scope.EXCLUDED);
+ break;
+ default:
+ merged = project;
+ }
+ }
+ return merged;
+ }
}
diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
index fb9123a0..cfc36d8a 100644
--- a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
+++ b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
@@ -18,24 +18,19 @@
*/
package org.cyclonedx.maven;
-import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* Creates a CycloneDX aggregate BOM at build root (with dependencies from the whole multi-modules build), and eventually a BOM for each module.
@@ -106,11 +101,12 @@ protected void logAdditionalParameters() {
getLog().info("outputReactorProjects : " + outputReactorProjects);
}
- protected String extractComponentsAndDependencies(final Set components, final Set dependencies) throws MojoExecutionException {
+ @Override
+ protected String extractComponentsAndDependencies(final Map components, final Map dependencies, final Map projectIdentities) throws MojoExecutionException {
if (! getProject().isExecutionRoot()) {
// non-root project: let parent class create a module-only BOM?
if (outputReactorProjects) {
- return super.extractComponentsAndDependencies(components, dependencies);
+ return super.extractComponentsAndDependencies(components, dependencies, projectIdentities);
}
getLog().info("Skipping CycloneDX on non-execution root");
return null;
@@ -118,15 +114,6 @@ protected String extractComponentsAndDependencies(final Set component
// root project: analyze and aggregate all the modules
getLog().info((reactorProjects.size() <= 1) ? MESSAGE_RESOLVING_DEPS : MESSAGE_RESOLVING_AGGREGATED_DEPS);
- final Set componentRefs = new LinkedHashSet<>();
-
- // Perform used/unused dependencies analysis for all projects upfront
- final List projectsDependencyAnalysis = prepareMavenDependencyAnalysis();
-
- // Add reference to BOM metadata component.
- // Without this, direct dependencies of the Maven project cannot be determined.
- final Component bomComponent = convert(getProject().getArtifact());
- componentRefs.add(bomComponent.getBomRef());
for (final MavenProject mavenProject : reactorProjects) {
if (shouldExclude(mavenProject)) {
@@ -134,32 +121,24 @@ protected String extractComponentsAndDependencies(final Set component
continue;
}
- // Add reference to BOM metadata component.
- // Without this, direct dependencies of the Maven project cannot be determined.
+ final Map projectDependencies = extractBOMDependencies(mavenProject);
+
+ final Map projectPUrlToIdentity = new HashMap<>();
+ projectDependenciesConverter.normalizeDependencies(schemaVersion(), projectDependencies, projectPUrlToIdentity);
+
final Component projectBomComponent = convert(mavenProject.getArtifact());
- if (! mavenProject.isExecutionRoot()) {
- // DO NOT include root project as it's already been included as a bom metadata component
- // Also, ensure that only one project component with the same bom-ref exists in the BOM
- if (!componentRefs.contains(projectBomComponent.getBomRef())) {
- components.add(projectBomComponent);
- }
- }
- componentRefs.add(projectBomComponent.getBomRef());
+ final String identity = projectPUrlToIdentity.get(projectBomComponent.getPurl());
+ projectBomComponent.setBomRef(identity);
+ components.put(identity, projectBomComponent);
- for (final Artifact artifact : mavenProject.getArtifacts()) {
- final Component component = convert(artifact);
+ projectIdentities.put(projectBomComponent.getPurl(), projectBomComponent.getBomRef());
- // ensure that only one component with the same bom-ref exists in the BOM
- if (componentRefs.add(component.getBomRef())) {
- component.setScope(inferComponentScope(artifact, projectsDependencyAnalysis));
- components.add(component);
- }
- }
+ populateComponents(components, mavenProject.getArtifacts(), projectPUrlToIdentity, doProjectDependencyAnalysis(mavenProject));
- dependencies.addAll(extractBOMDependencies(mavenProject));
+ dependencies.putAll(projectDependencies);
}
- addMavenProjectsAsParentDependencies(reactorProjects, dependencies);
+ addMavenProjectsAsParentDependencies(reactorProjects, projectIdentities, dependencies);
return "makeAggregateBom";
}
@@ -170,53 +149,23 @@ protected String extractComponentsAndDependencies(final Set component
* code dependency, but only the build reactor.
*
* @param reactorProjects the Maven projects from the reactor
+ * @param identities reactor project identities
* @param dependencies all BOM dependencies found in reactor
*/
- private void addMavenProjectsAsParentDependencies(List reactorProjects, Set dependencies) {
- Map dependenciesByRef = new HashMap<>();
- dependencies.forEach(d -> dependenciesByRef.put(d.getRef(), d));
-
+ private void addMavenProjectsAsParentDependencies(List reactorProjects, Map identities, Map dependencies) {
for (final MavenProject project: reactorProjects) {
if (project.hasParent()) {
final String parentRef = generatePackageUrl(project.getParentArtifact());
- Dependency parentDependency = dependenciesByRef.get(parentRef);
- if (parentDependency != null) {
- final Dependency child = new Dependency(generatePackageUrl(project.getArtifact()));
- parentDependency.addDependency(child);
+ final String parentIdentity = identities.get(parentRef);
+ if (parentIdentity != null) {
+ Dependency parentDependency = dependencies.get(parentIdentity);
+ if (parentDependency != null) {
+ final String projectRef = generatePackageUrl(project.getArtifact());
+ final String projectIdentity = identities.get(projectRef);
+ parentDependency.addDependency(new Dependency(projectIdentity));
+ }
}
}
}
}
-
- private List prepareMavenDependencyAnalysis() throws MojoExecutionException {
- final List dependencyAnalysisMap = new ArrayList<>();
- for (final MavenProject mavenProject : reactorProjects) {
- if (shouldExclude(mavenProject)) {
- continue;
- }
- ProjectDependencyAnalysis dependencyAnalysis = doProjectDependencyAnalysis(mavenProject);
- if (dependencyAnalysis != null) {
- dependencyAnalysisMap.add(dependencyAnalysis);
- }
- }
- return dependencyAnalysisMap;
- }
-
- private Component.Scope inferComponentScope(Artifact artifact, List projectsDependencyAnalysis) {
- Component.Scope componentScope = null;
- for (ProjectDependencyAnalysis dependencyAnalysis : projectsDependencyAnalysis) {
- Component.Scope currentProjectScope = inferComponentScope(artifact, dependencyAnalysis);
-
- // Set scope to required if the component is used in any project
- if (Component.Scope.REQUIRED.equals(currentProjectScope)) {
- return Component.Scope.REQUIRED;
- }
-
- if (componentScope == null && currentProjectScope != null) {
- // Set optional or excluded scope
- componentScope = currentProjectScope;
- }
- }
- return componentScope;
- }
}
diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java
index d061af66..0a38fc31 100644
--- a/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java
+++ b/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java
@@ -18,7 +18,6 @@
*/
package org.cyclonedx.maven;
-import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
@@ -32,8 +31,9 @@
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
-import java.util.LinkedHashSet;
-import java.util.Set;
+
+import java.util.HashMap;
+import java.util.Map;
/**
* Creates a CycloneDX BOM for each Maven module with its dependencies.
@@ -90,61 +90,25 @@ protected ProjectDependencyAnalysis doProjectDependencyAnalysis(MavenProject mav
return null;
}
- protected String extractComponentsAndDependencies(final Set components, final Set dependencies) throws MojoExecutionException {
- final Set componentRefs = new LinkedHashSet<>();
-
+ protected String extractComponentsAndDependencies(final Map components, final Map dependencies, final Map projectIdentities) throws MojoExecutionException {
getLog().info(MESSAGE_RESOLVING_DEPS);
- if (getProject() != null && getProject().getArtifacts() != null) {
- ProjectDependencyAnalysis dependencyAnalysis = doProjectDependencyAnalysis(getProject());
-
- // Add reference to BOM metadata component.
- // Without this, direct dependencies of the Maven project cannot be determined.
- final Component bomComponent = convert(getProject().getArtifact());
- componentRefs.add(bomComponent.getBomRef());
-
- for (final Artifact artifact : getProject().getArtifacts()) {
- final Component component = convert(artifact);
- // ensure that only one component with the same bom-ref exists in the BOM
- if (componentRefs.add(component.getBomRef())) {
- component.setScope(inferComponentScope(artifact, dependencyAnalysis));
- components.add(component);
- }
- }
- }
- dependencies.addAll(extractBOMDependencies(getProject()));
+ final Map projectDependencies = extractBOMDependencies(getProject());
- return "makeBom";
- }
+ final Map projectPUrlToIdentity = new HashMap<>();
+ projectDependenciesConverter.normalizeDependencies(schemaVersion(), projectDependencies, projectPUrlToIdentity);
- /**
- * Infer BOM component scope based on Maven project dependency analysis.
- *
- * @param artifact Artifact from maven project
- * @param projectDependencyAnalysis Maven Project Dependency Analysis data
- *
- * @return Component.Scope - Required: If the component is used (as detected by project dependency analysis). Optional: If it is unused
- */
- protected Component.Scope inferComponentScope(Artifact artifact, ProjectDependencyAnalysis projectDependencyAnalysis) {
- if (projectDependencyAnalysis == null) {
- return null;
- }
+ final Component projectBomComponent = convert(getProject().getArtifact());
+ final String identity = projectPUrlToIdentity.get(projectBomComponent.getPurl());
+ projectBomComponent.setBomRef(identity);
+ components.put(identity, projectBomComponent);
- Set usedDeclaredArtifacts = projectDependencyAnalysis.getUsedDeclaredArtifacts();
- Set usedUndeclaredArtifacts = projectDependencyAnalysis.getUsedUndeclaredArtifacts();
- Set unusedDeclaredArtifacts = projectDependencyAnalysis.getUnusedDeclaredArtifacts();
- Set testArtifactsWithNonTestScope = projectDependencyAnalysis.getTestArtifactsWithNonTestScope();
+ projectIdentities.put(projectBomComponent.getPurl(), projectBomComponent.getBomRef());
- // Is the artifact used?
- if (usedDeclaredArtifacts.contains(artifact) || usedUndeclaredArtifacts.contains(artifact)) {
- return Component.Scope.REQUIRED;
- }
+ populateComponents(components, getProject().getArtifacts(), projectPUrlToIdentity, doProjectDependencyAnalysis(getProject()));
- // Is the artifact unused or test?
- if (unusedDeclaredArtifacts.contains(artifact) || testArtifactsWithNonTestScope.contains(artifact)) {
- return Component.Scope.OPTIONAL;
- }
+ dependencies.putAll(projectDependencies);
- return null;
+ return "makeBom";
}
}
diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
index 079f4386..2adc4cce 100644
--- a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
+++ b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
@@ -18,7 +18,6 @@
*/
package org.cyclonedx.maven;
-import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
@@ -29,9 +28,9 @@
import org.cyclonedx.model.Dependency;
import java.util.Arrays;
-import java.util.LinkedHashSet;
+import java.util.HashMap;
import java.util.List;
-import java.util.Set;
+import java.util.Map;
/**
* Creates a CycloneDX BOM for each Maven module with {@code war} or {@code ear} packaging.
@@ -56,8 +55,7 @@ protected boolean shouldInclude(MavenProject mavenProject) {
return Arrays.asList(new String[]{"war", "ear"}).contains(mavenProject.getPackaging());
}
- protected String extractComponentsAndDependencies(Set components, Set dependencies) throws MojoExecutionException {
- final Set componentRefs = new LinkedHashSet<>();
+ protected String extractComponentsAndDependencies(Map components, Map dependencies, final Map projectIdentities) throws MojoExecutionException {
getLog().info(MESSAGE_RESOLVING_DEPS);
for (final MavenProject mavenProject : reactorProjects) {
@@ -65,15 +63,22 @@ protected String extractComponentsAndDependencies(Set components, Set
continue;
}
getLog().info("Analyzing " + mavenProject.getArtifactId());
- for (final Artifact artifact : mavenProject.getArtifacts()) {
- final Component component = convert(artifact);
- // ensure that only one component with the same bom-ref exists in the BOM
- if (componentRefs.add(component.getBomRef())) {
- components.add(component);
- }
- }
- dependencies.addAll(extractBOMDependencies(mavenProject));
+ final Map projectDependencies = extractBOMDependencies(mavenProject);
+
+ final Map projectPUrlToIdentity = new HashMap<>();
+ projectDependenciesConverter.normalizeDependencies(schemaVersion(), projectDependencies, projectPUrlToIdentity);
+
+ final Component projectBomComponent = convert(mavenProject.getArtifact());
+ final String identity = projectPUrlToIdentity.get(projectBomComponent.getPurl());
+ projectBomComponent.setBomRef(identity);
+ components.put(identity, projectBomComponent);
+
+ projectIdentities.put(projectBomComponent.getPurl(), projectBomComponent.getBomRef());
+
+ populateComponents(components, mavenProject.getArtifacts(), projectPUrlToIdentity, null);
+
+ dependencies.putAll(projectDependencies);
}
return "makePackageBom";
diff --git a/src/main/java/org/cyclonedx/maven/DefaultProjectDependenciesConverter.java b/src/main/java/org/cyclonedx/maven/DefaultProjectDependenciesConverter.java
index 763c85aa..440de503 100644
--- a/src/main/java/org/cyclonedx/maven/DefaultProjectDependenciesConverter.java
+++ b/src/main/java/org/cyclonedx/maven/DefaultProjectDependenciesConverter.java
@@ -18,6 +18,8 @@
*/
package org.cyclonedx.maven;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
@@ -27,6 +29,7 @@
import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyCollectorBuilder;
+import org.cyclonedx.CycloneDxSchema;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
import org.cyclonedx.model.Metadata;
@@ -38,10 +41,18 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.github.packageurl.MalformedPackageURLException;
+import com.github.packageurl.PackageURL;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -65,7 +76,7 @@ public class DefaultProjectDependenciesConverter implements ProjectDependenciesC
private MavenDependencyScopes include;
@Override
- public Set extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludeTypes) throws MojoExecutionException {
+ public Map extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludeTypes) throws MojoExecutionException {
this.include = include;
excludeTypesSet = new HashSet<>(Arrays.asList(excludeTypes));
@@ -73,7 +84,7 @@ public Set extractBOMDependencies(MavenProject mavenProject, MavenDe
final Map resolvedPUrls = generateResolvedPUrls(mavenProject);
- final Map dependencies = new LinkedHashMap<>();
+ final Map dependencies = new LinkedHashMap<>();
try {
final DelegatingRepositorySystem delegateRepositorySystem = new DelegatingRepositorySystem(aetherRepositorySystem);
final DependencyCollectorBuilder dependencyCollectorBuilder = new DefaultDependencyCollectorBuilder(delegateRepositorySystem);
@@ -94,7 +105,7 @@ public Set extractBOMDependencies(MavenProject mavenProject, MavenDe
// rather than throwing an exception https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/55
logger.warn("An error occurred building dependency graph: " + e.getMessage());
}
- return dependencies.keySet();
+ return dependencies;
}
private boolean isFilteredNode(final DependencyNode node, final Set loggedFilteredArtifacts) {
@@ -143,7 +154,7 @@ private boolean isExcludedNode(final DependencyNode node) {
return ((type == null) || excludeTypesSet.contains(type));
}
- private void buildDependencyGraphNode(final Map dependencies, DependencyNode node,
+ private void buildDependencyGraphNode(final Map dependencies, DependencyNode node,
final Dependency parent, final String parentClassifierlessPUrl, final Map resolvedPUrls,
final Set loggedReplacementPUrls, final Set loggedFilteredArtifacts) {
String purl = modelConverter.generatePackageUrl(node.getArtifact());
@@ -172,7 +183,7 @@ private void buildDependencyGraphNode(final Map dependen
}
Dependency topDependency = new Dependency(purl);
- final Dependency origDependency = dependencies.putIfAbsent(topDependency, topDependency);
+ final Dependency origDependency = dependencies.putIfAbsent(purl, topDependency);
if (origDependency != null) {
topDependency = origDependency;
}
@@ -214,42 +225,114 @@ private ProjectBuildingRequest getProjectBuildingRequest(final MavenProject mave
}
@Override
- public void cleanupBomDependencies(Metadata metadata, Set components, Set dependencies) {
- // map(component ref -> component)
- final Map componentRefs = new HashMap<>();
- components.forEach(c -> componentRefs.put(c.getBomRef(), c));
-
+ public void cleanupBomDependencies(Metadata metadata, Map components, Map dependencies) {
// set(dependencies refs) and set(dependencies of dependencies)
- final Set dependencyRefs = new HashSet<>();
final Set dependsOns = new HashSet<>();
- dependencies.forEach(d -> {
- dependencyRefs.add(d.getRef());
+ dependencies.values().forEach(d -> {
if (d.getDependencies() != null) {
d.getDependencies().forEach(on -> dependsOns.add(on.getRef()));
}
});
// Check all BOM components have an associated BOM dependency
- for (Map.Entry entry: componentRefs.entrySet()) {
- if (!dependencyRefs.contains(entry.getKey())) {
+
+ for (Iterator> it = components.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry entry = it.next();
+ if (!dependencies.containsKey(entry.getKey())) {
if (logger.isDebugEnabled()) {
logger.debug("Component reference not listed in dependencies, pruning from bom components: " + entry.getKey());
}
- components.remove(entry.getValue());
+ it.remove();
} else if (!dependsOns.contains(entry.getKey())) {
logger.warn("BOM dependency listed but is not depended upon: " + entry.getKey());
}
}
- // add BOM main component
+ // include BOM main component
Component main = metadata.getComponent();
- componentRefs.put(main.getBomRef(), main);
+ final String mainBomRef = main.getBomRef();
// Check all BOM dependencies have a BOM component
- for (String dependencyRef: dependencyRefs) {
- if (!componentRefs.containsKey(dependencyRef)) {
+ for (String dependencyRef: dependencies.keySet()) {
+ if (!mainBomRef.equals(dependencyRef) && !components.containsKey(dependencyRef)) {
logger.warn("Dependency missing component entry: " + dependencyRef);
}
}
}
+
+ private String generateIdentity(final Map purlToIdentity, final Map dependencies, final String ref) {
+ final String identity = purlToIdentity.get(ref);
+ if (identity != null) {
+ return identity;
+ } else {
+ final Dependency dependency = dependencies.get(ref);
+
+ final StringBuilder sb = new StringBuilder(ref);
+
+ if (dependency.getDependencies() != null) {
+ for (Dependency child: dependency.getDependencies()) {
+ final String childIdentity = generateIdentity(purlToIdentity, dependencies, child.getRef());
+ sb.append('+').append(childIdentity);
+ }
+ }
+
+ final MessageDigest digest = DigestUtils.getSha512Digest();
+ digest.update(sb.toString().getBytes(StandardCharsets.UTF_8));
+ final String hash = Hex.encodeHexString(digest.digest());
+
+ final PackageURL purl;
+ try {
+ purl = new PackageURL(ref);
+ } catch(final MalformedPackageURLException mpurle) {
+ logger.warn("An unexpected issue occurred attempting to parse PackageURL " + ref, mpurle);
+ return ref;
+ }
+
+ purl.getQualifiers().put("hash", hash);
+
+ final String newIdentity = purl.canonicalize();
+
+ purlToIdentity.put(ref, newIdentity);
+ return newIdentity;
+ }
+ }
+
+ private void generateIdentities(final Map purlToIdentity, final Map dependencies) {
+ for(String ref: dependencies.keySet()) {
+ generateIdentity(purlToIdentity, dependencies, ref);
+ }
+ }
+
+ private Dependency normalizeDependency(final Dependency dependency, final Map purlToIdentity) {
+ final Dependency normalizedDependency = new Dependency(purlToIdentity.get(dependency.getRef()));
+ final List children = new ArrayList<>();
+ if (dependency.getDependencies() != null) {
+ for(Dependency child: dependency.getDependencies()) {
+ children.add(new Dependency(purlToIdentity.get(child.getRef())));
+ }
+ }
+ normalizedDependency.setDependencies(children);
+ return normalizedDependency;
+ }
+
+ /**
+ * Normalize the dependencies, assigning distinct references based on their purl and dependencies.
+ * The map will be modified to reflect the distinct names, with references and the map keys
+ * being updated.
+ */
+ @Override
+ public void normalizeDependencies(final CycloneDxSchema.Version schemaVersion, final Map dependencies, final Map purlToIdentity) {
+ // We only need to normalize dependencies if they are being included in the BOM, i.e. version 1.2 and above
+ if (schemaVersion.getVersion() >= 1.2) {
+ generateIdentities(purlToIdentity, dependencies);
+
+ final Map normalizedDependencies = new LinkedHashMap<>();
+ for (Dependency dependency: dependencies.values()) {
+ final Dependency normalizedDependency = normalizeDependency(dependency, purlToIdentity);
+ normalizedDependencies.put(normalizedDependency.getRef(), normalizedDependency);
+ }
+ dependencies.clear();
+ dependencies.putAll(normalizedDependencies);
+ }
+ }
}
diff --git a/src/main/java/org/cyclonedx/maven/ProjectDependenciesConverter.java b/src/main/java/org/cyclonedx/maven/ProjectDependenciesConverter.java
index 92f399d3..383a9a51 100644
--- a/src/main/java/org/cyclonedx/maven/ProjectDependenciesConverter.java
+++ b/src/main/java/org/cyclonedx/maven/ProjectDependenciesConverter.java
@@ -22,13 +22,14 @@
import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
+import org.cyclonedx.CycloneDxSchema;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
import org.cyclonedx.model.Metadata;
import java.util.Collection;
import java.util.HashSet;
-import java.util.Set;
+import java.util.Map;
/**
* Converts a Maven Project with its Maven dependencies resolution graph transformed into a SBOM dependencies list
@@ -36,13 +37,20 @@
*/
public interface ProjectDependenciesConverter {
- Set extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludeTypes) throws MojoExecutionException;
+ Map extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludes) throws MojoExecutionException;
+
+ /**
+ * Normalize the dependencies, assigning distinct references based on their purl and dependencies.
+ * The map will be modified to reflect the distinct names, with references and the map keys
+ * being updated.
+ */
+ void normalizeDependencies(CycloneDxSchema.Version schemaVersion, Map dependencies, Map purlToIdentity) ;
/**
* Check consistency between BOM components and BOM dependencies, and cleanup: drop components found while walking the
* Maven dependency resolution graph but that are finally not kept in the effective dependencies list.
*/
- void cleanupBomDependencies(Metadata metadata, Set components, Set dependencies);
+ void cleanupBomDependencies(Metadata metadata, Map components, Map dependencies);
public static class MavenDependencyScopes {
public final boolean compile;
diff --git a/src/test/java/org/cyclonedx/maven/BomDependenciesTest.java b/src/test/java/org/cyclonedx/maven/BomDependenciesTest.java
index e6980757..a6ec5b7e 100644
--- a/src/test/java/org/cyclonedx/maven/BomDependenciesTest.java
+++ b/src/test/java/org/cyclonedx/maven/BomDependenciesTest.java
@@ -1,23 +1,28 @@
package org.cyclonedx.maven;
+import static org.cyclonedx.maven.TestUtils.containsDependency;
import static org.cyclonedx.maven.TestUtils.getComponentNode;
+import static org.cyclonedx.maven.TestUtils.getComponentNodes;
import static org.cyclonedx.maven.TestUtils.getComponentReferences;
import static org.cyclonedx.maven.TestUtils.getDependencyNode;
+import static org.cyclonedx.maven.TestUtils.getDependencyNodes;
import static org.cyclonedx.maven.TestUtils.getDependencyReferences;
+import static org.cyclonedx.maven.TestUtils.getPUrlToIdentities;
import static org.cyclonedx.maven.TestUtils.readXML;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
+import java.util.Collection;
+import java.util.Map;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.w3c.dom.Document;
-import org.w3c.dom.Node;
+import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
@@ -69,6 +74,7 @@ public void testBomDependencies() throws Exception {
*/
private void checkHiddenTestArtifacts(final File projDir) throws Exception {
final Document bom = readXML(new File(projDir, "trustification/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
/* BOM should contain dependency elements for
@@ -83,7 +89,7 @@ private void checkHiddenTestArtifacts(final File projDir) throws Exception {
*/
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
/*
@@ -91,19 +97,17 @@ private void checkHiddenTestArtifacts(final File projDir) throws Exception {
*/
- final Node sharedDependency1Node = getDependencyNode(dependencies, SHARED_DEPENDENCY1);
- assertNotNull("Missing shared_dependency1 dependency", sharedDependency1Node);
+ final Element sharedDependency1Node = getDependencyNode(purlToIdentities, dependencies, SHARED_DEPENDENCY1);
// Note: there are three dependencies for shared_dependency1, however one has runtime scope and should not be discovered
final Set testSharedDependency1Dependencies = getDependencyReferences(sharedDependency1Node);
assertEquals("Invalid dependency count for shared_dependency1", 2, testSharedDependency1Dependencies.size());
- assertTrue("Missing shared_dependency2 dependency for shared_dependency1", testSharedDependency1Dependencies.contains(SHARED_DEPENDENCY2));
- assertTrue("Missing test_nested_dependency2 dependency for shared_dependency1", testSharedDependency1Dependencies.contains(TEST_NESTED_DEPENDENCY2));
+ containsDependency(purlToIdentities, testSharedDependency1Dependencies, SHARED_DEPENDENCY2);
+ containsDependency(purlToIdentities, testSharedDependency1Dependencies, TEST_NESTED_DEPENDENCY2);
/*
*/
- final Node sharedDependency2Node = getDependencyNode(dependencies, SHARED_DEPENDENCY2);
- assertNotNull("Missing shared_dependency2 dependency", sharedDependency2Node);
+ final Element sharedDependency2Node = getDependencyNode(purlToIdentities, dependencies, SHARED_DEPENDENCY2);
final Set testSharedDependency2Dependencies = getDependencyReferences(sharedDependency2Node);
assertEquals("Invalid dependency count for shared_dependency2", 0, testSharedDependency2Dependencies.size());
@@ -112,17 +116,15 @@ private void checkHiddenTestArtifacts(final File projDir) throws Exception {
*/
- final Node testNestedDependency2Node = getDependencyNode(dependencies, TEST_NESTED_DEPENDENCY2);
- assertNotNull("Missing test_nested_dependency2 dependency", testNestedDependency2Node);
+ final Element testNestedDependency2Node = getDependencyNode(purlToIdentities, dependencies, TEST_NESTED_DEPENDENCY2);
Set testNestedDependency2Dependencies = getDependencyReferences(testNestedDependency2Node);
assertEquals("Invalid dependency count for test_nested_dependency2", 1, testNestedDependency2Dependencies.size());
- assertTrue("Missing test_nested_dependency3 dependency for test_nested_dependency2", testNestedDependency2Dependencies.contains(TEST_NESTED_DEPENDENCY3));
+ containsDependency(purlToIdentities, testNestedDependency2Dependencies, TEST_NESTED_DEPENDENCY3);
/*
*/
- final Node testNestedDependency3Node = getDependencyNode(dependencies, TEST_NESTED_DEPENDENCY3);
- assertNotNull("Missing test_nested_dependency3 dependency", testNestedDependency3Node);
+ final Element testNestedDependency3Node = getDependencyNode(purlToIdentities, dependencies, TEST_NESTED_DEPENDENCY3);
Set testNestedDependency3Dependencies = getDependencyReferences(testNestedDependency3Node);
assertEquals("Invalid dependency count for test_nested_dependency3", 0, testNestedDependency3Dependencies.size());
}
@@ -133,6 +135,7 @@ private void checkHiddenTestArtifacts(final File projDir) throws Exception {
*/
private void checkHiddenRuntimeArtifacts(final File projDir) throws Exception {
final Document bom = readXML(new File(projDir, "trustification/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
/* BOM should contain dependency elements for
@@ -142,24 +145,22 @@ private void checkHiddenRuntimeArtifacts(final File projDir) throws Exception {
*/
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
/*
*/
- final Node sharedRuntimeDependency1Node = getDependencyNode(dependencies, SHARED_RUNTIME_DEPENDENCY1);
- assertNotNull("Missing shared_runtime_dependency1 dependency", sharedRuntimeDependency1Node);
+ final Element sharedRuntimeDependency1Node = getDependencyNode(purlToIdentities, dependencies, SHARED_RUNTIME_DEPENDENCY1);
final Set testSharedDependency1Dependencies = getDependencyReferences(sharedRuntimeDependency1Node);
assertEquals("Invalid dependency count for shared_runtime_dependency1", 1, testSharedDependency1Dependencies.size());
- assertTrue("Missing shared_runtime_dependency2 dependency for shared_runtime_dependency1", testSharedDependency1Dependencies.contains(SHARED_RUNTIME_DEPENDENCY2));
+ containsDependency(purlToIdentities, testSharedDependency1Dependencies, SHARED_RUNTIME_DEPENDENCY2);
/*
*/
- final Node sharedRuntimeDependency2Node = getDependencyNode(dependencies, SHARED_RUNTIME_DEPENDENCY2);
- assertNotNull("Missing shared_runtime_dependency2 dependency", sharedRuntimeDependency2Node);
+ final Element sharedRuntimeDependency2Node = getDependencyNode(purlToIdentities, dependencies, SHARED_RUNTIME_DEPENDENCY2);
final Set testSharedDependency2Dependencies = getDependencyReferences(sharedRuntimeDependency2Node);
assertEquals("Invalid dependency count for shared_runtime_dependency2", 0, testSharedDependency2Dependencies.size());
}
@@ -173,17 +174,17 @@ private void checkExtraneousComponents(final File projDir) throws Exception {
final NodeList metadataList = bom.getElementsByTagName("metadata");
assertEquals("Expected a single metadata element", 1, metadataList.getLength());
- final Node metadata = metadataList.item(0);
+ final Element metadata = (Element)metadataList.item(0);
final Set metadataComponentReferences = getComponentReferences(metadata);
final NodeList componentsList = bom.getElementsByTagName("components");
assertEquals("Expected a single components element", 1, componentsList.getLength());
- final Node components = componentsList.item(0);
+ final Element components = (Element)componentsList.item(0);
final Set componentReferences = getComponentReferences(components);
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
final Set dependencyReferences = getDependencyReferences(dependencies);
// Each dependency reference should have a component
@@ -194,7 +195,7 @@ private void checkExtraneousComponents(final File projDir) throws Exception {
// Each component reference should have a top level dependency
for (String componentRef: componentReferences) {
- assertNotNull("Missing top level dependency for component reference " + componentRef, getDependencyNode(dependencies, componentRef));
+ assertTrue("Missing dependency for component reference " + componentRef, dependencyReferences.contains(componentRef));
}
}
@@ -204,13 +205,13 @@ private void checkExtraneousComponents(final File projDir) throws Exception {
*/
private void checkTopLevelTestComponentsAsCompile(final File projDir) throws Exception {
final Document bom = readXML(new File(projDir, "trustification/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
// BOM should contain a component element for pkg:maven/com.example/test_compile_dependency@1.0.0?type=jar
final NodeList componentsList = bom.getElementsByTagName("components");
assertEquals("Expected a single components element", 1, componentsList.getLength());
- final Node components = componentsList.item(0);
- final Node testCompileDependencyNode = getComponentNode(components, TEST_COMPILE_DEPENDENCY);
- assertNotNull("Missing test_compile_dependency component", testCompileDependencyNode);
+ final Element components = (Element)componentsList.item(0);
+ getComponentNode(purlToIdentities, components, TEST_COMPILE_DEPENDENCY);
}
/**
@@ -222,55 +223,52 @@ public void testTypeExcludes() throws Exception {
final File projDir = cleanAndBuild("bom-dependencies", new String[]{"test-jar"});
final Document bom = readXML(new File(projDir, "trustification/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
final NodeList componentsList = bom.getElementsByTagName("components");
assertEquals("Expected a single components element", 1, componentsList.getLength());
- final Node components = componentsList.item(0);
+ final Element components = (Element)componentsList.item(0);
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
// BOM should not contain pkg:maven/com.example/type_dependency@1.0.0?classifier=tests&type=test-jar
// component nor top level dependency because of type test-jar
- final Node testTypeDependencyComponentNode = getComponentNode(components, TYPE_DEPENDENCY);
- assertNull("Unexpected type_dependency component discovered in BOM", testTypeDependencyComponentNode);
- final Node testTypeDependencyNode = getDependencyNode(dependencies, TYPE_DEPENDENCY);
- assertNull("Unexpected type_dependency dependency discovered in BOM", testTypeDependencyNode);
+ final Collection testTypeDependencyComponentNodes = getComponentNodes(purlToIdentities, components, TYPE_DEPENDENCY);
+ assertNull("Unexpected type_dependency component discovered in BOM", testTypeDependencyComponentNodes);
+ final Collection testTypeDependencyNodes = getDependencyNodes(purlToIdentities, dependencies, TYPE_DEPENDENCY);
+ assertNull("Unexpected type_dependency dependency discovered in BOM", testTypeDependencyNodes);
// BOM should contain pkg:maven/com.example/shared_type_dependency1@1.0.0?type=jar and shared_test_dependency2 and
// pkg:maven/com.example/shared_type_dependency2@1.0.0?type=jar components/dependencies as they are referenced by dependency2
- final Node sharedTypeDependency1ComponentNode = getComponentNode(components, SHARED_TYPE_DEPENDENCY1);
- assertNotNull("Missing shared_type_dependency1 component", sharedTypeDependency1ComponentNode);
- final Node sharedTypeDependency2ComponentNode = getComponentNode(components, SHARED_TYPE_DEPENDENCY2);
- assertNotNull("Missing shared_type_dependency2 component", sharedTypeDependency2ComponentNode);
+ getComponentNode(purlToIdentities, components, SHARED_TYPE_DEPENDENCY1);
+ getComponentNode(purlToIdentities, components, SHARED_TYPE_DEPENDENCY2);
/*
*/
- final Node sharedTypeDependency1Node = getDependencyNode(dependencies, SHARED_TYPE_DEPENDENCY1);
- assertNotNull("Missing shared_type_dependency1 dependency", sharedTypeDependency1Node);
+ final Element sharedTypeDependency1Node = getDependencyNode(purlToIdentities, dependencies, SHARED_TYPE_DEPENDENCY1);
Set sharedTypeDependency1Dependencies = getDependencyReferences(sharedTypeDependency1Node);
assertEquals("Invalid dependency count for shared_type_dependency1", 1, sharedTypeDependency1Dependencies.size());
- assertTrue("Missing shared_type_dependency2 dependency for shared_type_dependency1", sharedTypeDependency1Dependencies.contains(SHARED_TYPE_DEPENDENCY2));
+ containsDependency(purlToIdentities, sharedTypeDependency1Dependencies, SHARED_TYPE_DEPENDENCY2);
- final Node sharedTypeDependency2Node = getDependencyNode(dependencies, SHARED_TYPE_DEPENDENCY2);
- assertNotNull("Missing shared_type_dependency2 dependency", sharedTypeDependency2Node);
+ getDependencyNode(purlToIdentities, dependencies, SHARED_TYPE_DEPENDENCY2);
// BOM should not contain pkg:maven/com.example/shared_type_dependency3@1.0.0?type=jar nor
// pkg:maven/com.example/shared_type_dependency4@1.0.0?type=jar components/dependencies
// as they are only referenced via type_dependency
- final Node sharedTypeDependency3ComponentNode = getComponentNode(components, SHARED_TYPE_DEPENDENCY3);
- assertNull("Unexpected shared_type_dependency3 component discovered in BOM", sharedTypeDependency3ComponentNode);
- final Node sharedTypeDependency3Node = getDependencyNode(dependencies, SHARED_TYPE_DEPENDENCY3);
- assertNull("Unexpected shared_type_dependency3 dependency discovered in BOM", sharedTypeDependency3Node);
-
- final Node sharedTypeDependency4ComponentNode = getComponentNode(components, SHARED_TYPE_DEPENDENCY4);
- assertNull("Unexpected shared_type_dependency4 component discovered in BOM", sharedTypeDependency4ComponentNode);
- final Node sharedTypeDependency4Node = getDependencyNode(dependencies, SHARED_TYPE_DEPENDENCY4);
- assertNull("Unexpected shared_type_dependency4 dependency discovered in BOM", sharedTypeDependency4Node);
+ final Collection sharedTypeDependency3ComponentNodes = getComponentNodes(purlToIdentities, components, SHARED_TYPE_DEPENDENCY3);
+ assertNull("Unexpected shared_type_dependency3 component discovered in BOM", sharedTypeDependency3ComponentNodes);
+ final Collection sharedTypeDependency3Nodes = getDependencyNodes(purlToIdentities, dependencies, SHARED_TYPE_DEPENDENCY3);
+ assertNull("Unexpected shared_type_dependency3 dependency discovered in BOM", sharedTypeDependency3Nodes);
+
+ final Collection sharedTypeDependency4ComponentNodes = getComponentNodes(purlToIdentities, components, SHARED_TYPE_DEPENDENCY4);
+ assertNull("Unexpected shared_type_dependency4 component discovered in BOM", sharedTypeDependency4ComponentNodes);
+ final Collection sharedTypeDependency4Nodes = getDependencyNodes(purlToIdentities, dependencies, SHARED_TYPE_DEPENDENCY4);
+ assertNull("Unexpected shared_type_dependency4 dependency discovered in BOM", sharedTypeDependency4Nodes);
}
/**
@@ -280,45 +278,43 @@ public void testTypeExcludes() throws Exception {
private void testHiddenVersionedTransitiveDependencies(final File projDir) throws Exception {
// Note: checkExtraneousComponents will also catch missing versioned dependencies but doesn't check for transitive dependencies
final Document bom = readXML(new File(projDir, "trustification/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
final NodeList componentsList = bom.getElementsByTagName("components");
assertEquals("Expected a single components element", 1, componentsList.getLength());
- final Node components = componentsList.item(0);
+ final Element components = (Element)componentsList.item(0);
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
// BOM should not contain pkg:maven/com.example/versioned_dependency@1.0.0?type=jar
- final Node testVersionedDependency1ComponentNode = getComponentNode(components, VERSIONED_DEPENDENCY1);
- assertNull("Unexpected versioned_dependency:1.0.0 component discovered in BOM", testVersionedDependency1ComponentNode);
- final Node testVersionedDependency1Node = getDependencyNode(dependencies, VERSIONED_DEPENDENCY1);
- assertNull("Unexpected versioned_dependency:1.0.0 dependency discovered in BOM", testVersionedDependency1Node);
+ final Collection testVersionedDependency1ComponentNodes = getComponentNodes(purlToIdentities, components, VERSIONED_DEPENDENCY1);
+ assertNull("Unexpected versioned_dependency:1.0.0 component discovered in BOM", testVersionedDependency1ComponentNodes);
+ final Collection testVersionedDependency1Nodes = getDependencyNodes(purlToIdentities, dependencies, VERSIONED_DEPENDENCY1);
+ assertNull("Unexpected versioned_dependency:1.0.0 dependency discovered in BOM", testVersionedDependency1Nodes);
// BOM should contain pkg:maven/com.example/versioned_dependency@2.0.0?type=jar
- final Node testVersionedDependency2ComponentNode = getComponentNode(components, VERSIONED_DEPENDENCY2);
- assertNotNull("Missing versioned_dependency:2.0.0 component component", testVersionedDependency2ComponentNode);
+ getComponentNode(purlToIdentities, components, VERSIONED_DEPENDENCY2);
/*
*/
- final Node providedDependencyNode = getDependencyNode(dependencies, PROVIDED_DEPENDENCY);
- assertNotNull("Missing provided_dependency dependency", providedDependencyNode);
+ final Element providedDependencyNode = getDependencyNode(purlToIdentities, dependencies, PROVIDED_DEPENDENCY);
Set providedDependencyDependencies = getDependencyReferences(providedDependencyNode);
assertEquals("Invalid dependency count for provided_dependency", 1, providedDependencyDependencies.size());
- assertTrue("Missing versioned_dependency:2.0.0 dependency for provided_dependency", providedDependencyDependencies.contains(VERSIONED_DEPENDENCY2));
+ containsDependency(purlToIdentities, providedDependencyDependencies, VERSIONED_DEPENDENCY2);
/*
*/
- final Node versionedDependencyNode = getDependencyNode(dependencies, VERSIONED_DEPENDENCY2);
- assertNotNull("Missing versioned_dependency dependency", versionedDependencyNode);
+ final Element versionedDependencyNode = getDependencyNode(purlToIdentities, dependencies, VERSIONED_DEPENDENCY2);
Set versionedDependencyDependencies = getDependencyReferences(versionedDependencyNode);
assertEquals("Invalid dependency count for versioned_dependency", 1, versionedDependencyDependencies.size());
- assertTrue("Missing dependency1 dependency for versioned_dependency", versionedDependencyDependencies.contains(DEPENDENCY1));
+ containsDependency(purlToIdentities, versionedDependencyDependencies, DEPENDENCY1);
}
}
diff --git a/src/test/java/org/cyclonedx/maven/CyclicTest.java b/src/test/java/org/cyclonedx/maven/CyclicTest.java
index 66427c40..a0a7d3e3 100644
--- a/src/test/java/org/cyclonedx/maven/CyclicTest.java
+++ b/src/test/java/org/cyclonedx/maven/CyclicTest.java
@@ -1,23 +1,24 @@
package org.cyclonedx.maven;
import java.io.File;
+import java.util.Collection;
+import java.util.Map;
import java.util.Set;
+import static org.cyclonedx.maven.TestUtils.containsDependency;
import static org.cyclonedx.maven.TestUtils.getComponentNode;
import static org.cyclonedx.maven.TestUtils.getDependencyNode;
import static org.cyclonedx.maven.TestUtils.getDependencyReferences;
+import static org.cyclonedx.maven.TestUtils.getPUrlToIdentities;
import static org.cyclonedx.maven.TestUtils.readXML;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.w3c.dom.Document;
-import org.w3c.dom.Node;
+import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
@@ -50,26 +51,24 @@ public void testCyclicDependency() throws Exception {
}
final Document bom = readXML(new File(projDir, "target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
final NodeList componentsList = bom.getElementsByTagName("components");
assertEquals("Expected a single components element", 1, componentsList.getLength());
- final Node components = componentsList.item(0);
+ final Element components = (Element)componentsList.item(0);
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
// BOM should contain pkg:maven/com.example.cyclic/cyclic_A@1.0.0?classifier=classifier_1&type=jar
- final Node cyclicAClassifier1ComponentNode = getComponentNode(components, CYCLIC_A_DEPENDENCY_CLASSIFIER_1);
- assertNotNull("Missing cyclic_A:classifier_1:1.0.0 component", cyclicAClassifier1ComponentNode);
+ getComponentNode(purlToIdentities, components, CYCLIC_A_DEPENDENCY_CLASSIFIER_1);
// BOM should contain pkg:maven/com.example.cyclic/cyclic_A@1.0.0?classifier=classifier_2&type=jar
- final Node cyclicAClassifier2ComponentNode = getComponentNode(components, CYCLIC_A_DEPENDENCY_CLASSIFIER_2);
- assertNotNull("Missing cyclic_A:classifier_2:1.0.0 component", cyclicAClassifier2ComponentNode);
+ getComponentNode(purlToIdentities, components, CYCLIC_A_DEPENDENCY_CLASSIFIER_2);
// BOM should contain pkg:maven/com.example.cyclic/cyclic_A@1.0.0?classifier=classifier_3&type=jar
- final Node cyclicAClassifier3ComponentNode = getComponentNode(components, CYCLIC_A_DEPENDENCY_CLASSIFIER_3);
- assertNotNull("Missing cyclic_A:classifier_3:1.0.0 component", cyclicAClassifier3ComponentNode);
+ getComponentNode(purlToIdentities, components, CYCLIC_A_DEPENDENCY_CLASSIFIER_3);
/*
@@ -78,35 +77,31 @@ public void testCyclicDependency() throws Exception {
*/
- final Node cyclicADependencyNode = getDependencyNode(dependencies, CYCLIC_A_DEPENDENCY);
- assertNotNull("Missing cyclic_A:1.0.0 dependency", cyclicADependencyNode);
+ final Element cyclicADependencyNode = getDependencyNode(purlToIdentities, dependencies, CYCLIC_A_DEPENDENCY);
Set cyclicADependencies = getDependencyReferences(cyclicADependencyNode);
assertEquals("Invalid dependency count for cyclic_A:1.0.0", 3, cyclicADependencies.size());
- assertTrue("Missing cyclic_A:classifier_1:1.0.0 dependency for cyclic_A:1.0.0", cyclicADependencies.contains(CYCLIC_A_DEPENDENCY_CLASSIFIER_1));
- assertTrue("Missing cyclic_A:classifier_2:1.0.0 dependency for cyclic_A:1.0.0", cyclicADependencies.contains(CYCLIC_A_DEPENDENCY_CLASSIFIER_2));
- assertTrue("Missing cyclic_A:classifier_3:1.0.0 dependency for cyclic_A:1.0.0", cyclicADependencies.contains(CYCLIC_A_DEPENDENCY_CLASSIFIER_3));
+ containsDependency(purlToIdentities, cyclicADependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_1);
+ containsDependency(purlToIdentities, cyclicADependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_2);
+ containsDependency(purlToIdentities, cyclicADependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_3);
/*
*/
- final Node cyclicAClassifier1DependencyNode = getDependencyNode(dependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_1);
- assertNotNull("Missing cyclic_A:classifier_1:1.0.0 dependency", cyclicAClassifier1DependencyNode);
+ final Element cyclicAClassifier1DependencyNode = getDependencyNode(purlToIdentities, dependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_1);
Set cyclicAClassifier1Dependencies = getDependencyReferences(cyclicAClassifier1DependencyNode);
assertEquals("Invalid dependency count for cyclic_A:classifier_1:1.0.0", 0, cyclicAClassifier1Dependencies.size());
/*
*/
- final Node cyclicAClassifier2DependencyNode = getDependencyNode(dependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_2);
- assertNotNull("Missing cyclic_A:classifier_2:1.0.0 dependency", cyclicAClassifier2DependencyNode);
+ final Element cyclicAClassifier2DependencyNode = getDependencyNode(purlToIdentities, dependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_2);
Set cyclicAClassifier2Dependencies = getDependencyReferences(cyclicAClassifier2DependencyNode);
assertEquals("Invalid dependency count for cyclic_A:classifier_2:1.0.0", 0, cyclicAClassifier2Dependencies.size());
/*
*/
- final Node cyclicAClassifier3DependencyNode = getDependencyNode(dependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_3);
- assertNotNull("Missing cyclic_A:classifier_3:1.0.0 dependency", cyclicAClassifier3DependencyNode);
+ final Element cyclicAClassifier3DependencyNode = getDependencyNode(purlToIdentities, dependencies, CYCLIC_A_DEPENDENCY_CLASSIFIER_3);
Set cyclicAClassifier3Dependencies = getDependencyReferences(cyclicAClassifier3DependencyNode);
assertEquals("Invalid dependency count for cyclic_A:classifier_3:1.0.0", 0, cyclicAClassifier3Dependencies.size());
}
diff --git a/src/test/java/org/cyclonedx/maven/DependencyTreeTest.java b/src/test/java/org/cyclonedx/maven/DependencyTreeTest.java
new file mode 100644
index 00000000..535779e8
--- /dev/null
+++ b/src/test/java/org/cyclonedx/maven/DependencyTreeTest.java
@@ -0,0 +1,199 @@
+package org.cyclonedx.maven;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import static org.cyclonedx.maven.TestUtils.containsDependency;
+import static org.cyclonedx.maven.TestUtils.getComponentNode;
+import static org.cyclonedx.maven.TestUtils.getComponentNodes;
+import static org.cyclonedx.maven.TestUtils.getDependencyNode;
+import static org.cyclonedx.maven.TestUtils.getDependencyNodeByIdentity;
+import static org.cyclonedx.maven.TestUtils.getDependencyReferences;
+import static org.cyclonedx.maven.TestUtils.getPUrlToIdentities;
+import static org.cyclonedx.maven.TestUtils.readXML;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
+import io.takari.maven.testing.executor.MavenVersions;
+import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner;
+
+/**
+ * Fix BOM handling of conflicting dependency tree graphs
+ */
+@RunWith(MavenJUnitTestRunner.class)
+@MavenVersions({"3.6.3"})
+public class DependencyTreeTest extends BaseMavenVerifier {
+
+ private static final String EXCLUSION_DEPENDENCY_A = "pkg:maven/com.example.dependency_trees.exclusion/dependency_A@1.0.0?type=jar";
+ private static final String EXCLUSION_DEPENDENCY_B = "pkg:maven/com.example.dependency_trees.exclusion/dependency_B@1.0.0?type=jar";
+ private static final String EXCLUSION_DEPENDENCY_C = "pkg:maven/com.example.dependency_trees.exclusion/dependency_C@1.0.0?type=jar";
+ private static final String EXCLUSION_DEPENDENCY_D = "pkg:maven/com.example.dependency_trees.exclusion/dependency_D@1.0.0?type=jar";
+ private static final String EXCLUSION_DEPENDENCY_E = "pkg:maven/com.example.dependency_trees.exclusion/dependency_E@1.0.0?type=jar";
+ private static final String EXCLUSION_DEPENDENCY_F = "pkg:maven/com.example.dependency_trees.exclusion/dependency_F@1.0.0?type=jar";
+
+ private static final String MANAGED_DEPENDENCY_A = "pkg:maven/com.example.dependency_trees.managed/dependency_A@1.0.0?type=jar";
+ private static final String MANAGED_DEPENDENCY_B = "pkg:maven/com.example.dependency_trees.managed/dependency_B@1.0.0?type=jar";
+ private static final String MANAGED_DEPENDENCY_C1 = "pkg:maven/com.example.dependency_trees.managed/dependency_C@1.0.0?type=jar";
+ private static final String MANAGED_DEPENDENCY_C2 = "pkg:maven/com.example.dependency_trees.managed/dependency_C@2.0.0?type=jar";
+ private static final String MANAGED_DEPENDENCY_D = "pkg:maven/com.example.dependency_trees.managed/dependency_D@1.0.0?type=jar";
+ private static final String MANAGED_DEPENDENCY_E = "pkg:maven/com.example.dependency_trees.managed/dependency_E@1.0.0?type=jar";
+
+ public DependencyTreeTest(MavenRuntimeBuilder runtimeBuilder) throws Exception {
+ super(runtimeBuilder);
+ }
+
+ @Test
+ public void testDependencyTrees() throws Exception {
+ final File projDir = cleanAndBuild("dependency_trees", null);
+ checkExclusion(projDir);
+ checkManaged(projDir);
+ }
+
+ /**
+ * Test for alternative dependency trees generated through exclusions in the hierarchy
+ */
+ public void checkExclusion(final File projDir) throws Exception {
+ final Document bom = readXML(new File(projDir, "target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
+
+ final NodeList componentsList = bom.getElementsByTagName("components");
+ assertEquals("Expected a single components element", 1, componentsList.getLength());
+ final Element components = (Element)componentsList.item(0);
+
+ final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
+ assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
+ final Element dependencies = (Element)dependenciesList.item(0);
+
+ /*
+ We create an aggregated BOM containing two dependency hierarchies which deviate at dependency_B.
+
+ The first graph is rooted at dependency_A and includes dependency_E as below
+
+ com.example.dependency_trees.exclusion:dependency_A:jar:1.0.0
+ \- com.example.dependency_trees.exclusion:dependency_B:jar:1.0.0:compile
+ +- com.example.dependency_trees.exclusion:dependency_C:jar:1.0.0:compile
+ | \- com.example.dependency_trees.exclusion:dependency_D:jar:1.0.0:compile
+ \- com.example.dependency_trees.exclusion:dependency_E:jar:1.0.0:compile
+
+ The second graph is rooted at dependency_F and excludes dependency_E as below
+
+ com.example.dependency_trees.exclusion:dependency_F:jar:1.0.0
+ \- com.example.dependency_trees.exclusion:dependency_B:jar:1.0.0:compile
+ \- com.example.dependency_trees.exclusion:dependency_C:jar:1.0.0:compile
+ \- com.example.dependency_trees.exclusion:dependency_D:jar:1.0.0:compile
+ */
+
+ // Ensure there are two components for Dependency_B
+ final Collection dependencyBNodes = getComponentNodes(purlToIdentities, components, EXCLUSION_DEPENDENCY_B);
+ assertNotNull("Could not find components for dependency_B", dependencyBNodes);
+ assertEquals("Incorrect component count for dependency_B", 2, dependencyBNodes.size());
+ // Ensure there is a single component for Dependency_C
+ getComponentNode(purlToIdentities, components, EXCLUSION_DEPENDENCY_C);
+ // Ensure there is a single component for Dependency_D
+ getComponentNode(purlToIdentities, components, EXCLUSION_DEPENDENCY_D);
+
+ // Check the first graph
+
+ final Element firstDepA = getDependencyNode(purlToIdentities, dependencies, EXCLUSION_DEPENDENCY_A);
+ final Element firstDepADepB = getDependencyNode(purlToIdentities, firstDepA, EXCLUSION_DEPENDENCY_B);
+
+ final String firstDepBPUrl = firstDepADepB.getAttribute("ref");
+ final Element firstDepB = getDependencyNodeByIdentity(dependencies, firstDepBPUrl);
+ final Set firstDepBDependencies = getDependencyReferences(firstDepB);
+ assertEquals("Invalid dependency count for dependency_B", 2, firstDepBDependencies.size());
+ containsDependency(purlToIdentities, firstDepBDependencies, EXCLUSION_DEPENDENCY_C);
+ containsDependency(purlToIdentities, firstDepBDependencies, EXCLUSION_DEPENDENCY_E);
+
+ // Check the second graph
+ final Element secondDepF = getDependencyNode(purlToIdentities, dependencies, EXCLUSION_DEPENDENCY_F);
+ final Element secondDepFDepB = getDependencyNode(purlToIdentities, secondDepF, EXCLUSION_DEPENDENCY_B);
+
+ final String secondDepBPUrl = secondDepFDepB.getAttribute("ref");
+ final Element secondDepB = getDependencyNodeByIdentity(dependencies, secondDepBPUrl);
+ final Set secondDepBDependencies = getDependencyReferences(secondDepB);
+ assertEquals("Invalid dependency count for dependency_B", 1, secondDepBDependencies.size());
+ containsDependency(purlToIdentities, secondDepBDependencies, EXCLUSION_DEPENDENCY_C);
+
+ // Assert dependencies have different purls
+ assertNotEquals("Dependency B purls should be distinct", firstDepBPUrl, secondDepBPUrl);
+ }
+
+ /**
+ * Test for alternative dependency trees generated through managed dependencies in the hierarchy
+ */
+ public void checkManaged(final File projDir) throws Exception {
+ final Document bom = readXML(new File(projDir, "target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
+
+ final NodeList componentsList = bom.getElementsByTagName("components");
+ assertEquals("Expected a single components element", 1, componentsList.getLength());
+ final Element components = (Element)componentsList.item(0);
+
+ final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
+ assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
+ final Element dependencies = (Element)dependenciesList.item(0);
+
+ /*
+ We create an aggregated BOM containing two dependency hierarchies which deviate at dependency_B.
+
+ The first graph is rooted at dependency_A and includes dependency_C with version 1.0.0 as below
+
+ com.example.dependency_trees.managed:dependency_A:jar:1.0.0
+ \- com.example.dependency_trees.managed:dependency_B:jar:1.0.0:compile
+ \- com.example.dependency_trees.managed:dependency_C:jar:1.0.0:compile
+ \- com.example.dependency_trees.managed:dependency_D:jar:1.0.0:compile
+
+ The second graph is rooted at dependency_E and includes dependency_C with version 2.0.0 as below
+
+ com.example.dependency_trees.managed:dependency_E:jar:1.0.0
+ \- com.example.dependency_trees.managed:dependency_B:jar:1.0.0:compile
+ \- com.example.dependency_trees.managed:dependency_C:jar:2.0.0:compile
+ \- com.example.dependency_trees.managed:dependency_D:jar:1.0.0:compile
+ */
+
+ // Ensure there are two components for Dependency_B
+ final Collection dependencyBNodes = getComponentNodes(purlToIdentities, components, MANAGED_DEPENDENCY_B);
+ assertNotNull("Could not find components for dependency_B", dependencyBNodes);
+ assertEquals("Incorrect component count for dependency_B", 2, dependencyBNodes.size());
+ // Ensure there are two components for Dependency_C
+ getComponentNode(purlToIdentities, components, MANAGED_DEPENDENCY_C1);
+ getComponentNode(purlToIdentities, components, MANAGED_DEPENDENCY_C2);
+ // Ensure there is a single component for Dependency_D
+ getComponentNode(purlToIdentities, components, MANAGED_DEPENDENCY_D);
+
+ // Check the first graph
+
+ final Element firstDepA = getDependencyNode(purlToIdentities, dependencies, MANAGED_DEPENDENCY_A);
+ final Element firstDepADepB = getDependencyNode(purlToIdentities, firstDepA, MANAGED_DEPENDENCY_B);
+
+ final String firstDepBPUrl = firstDepADepB.getAttribute("ref");
+ final Element firstDepB = getDependencyNodeByIdentity(dependencies, firstDepBPUrl);
+ final Set firstDepBDependencies = getDependencyReferences(firstDepB);
+ assertEquals("Invalid dependency count for dependency_B", 1, firstDepBDependencies.size());
+ containsDependency(purlToIdentities, firstDepBDependencies, MANAGED_DEPENDENCY_C1);
+
+ // Check the second graph
+ final Element secondDepE = getDependencyNode(purlToIdentities, dependencies, MANAGED_DEPENDENCY_E);
+ final Element secondDepEDepB = getDependencyNode(purlToIdentities, secondDepE, MANAGED_DEPENDENCY_B);
+
+ final String secondDepBPUrl = secondDepEDepB.getAttribute("ref");
+ final Element secondDepB = getDependencyNodeByIdentity(dependencies, secondDepBPUrl);
+ final Set secondDepBDependencies = getDependencyReferences(secondDepB);
+ assertEquals("Invalid dependency count for dependency_B", 1, secondDepBDependencies.size());
+ containsDependency(purlToIdentities, secondDepBDependencies, MANAGED_DEPENDENCY_C2);
+
+ // Assert dependencies have different purls
+ assertNotEquals("Dependency B purls should be distinct", firstDepBPUrl, secondDepBPUrl);
+ }
+}
diff --git a/src/test/java/org/cyclonedx/maven/Issue116Test.java b/src/test/java/org/cyclonedx/maven/Issue116Test.java
index f71cf894..6bc969d1 100644
--- a/src/test/java/org/cyclonedx/maven/Issue116Test.java
+++ b/src/test/java/org/cyclonedx/maven/Issue116Test.java
@@ -38,7 +38,7 @@ public void testPluginWithActiviti() throws Exception {
// 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, "");
+ int matches = StringUtils.countMatches(bomContents, "> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
final NodeList componentsList = bom.getElementsByTagName("components");
assertEquals("Expected a single components element", 1, componentsList.getLength());
- final Node components = componentsList.item(0);
+ final Element components = (Element)componentsList.item(0);
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
// BOM should not contain pkg:maven/com.example/issue284_provided_dependency@1.0.0?type=jar
- final Node testIssue284ProvidedDependency1ComponentNode = getComponentNode(components, ISSUE284_PROVIDED_DEPENDENCY);
- assertNull("Unexpected provided_dependency:1.0.0 component discovered in BOM", testIssue284ProvidedDependency1ComponentNode);
+ final Collection testIssue284ProvidedDependency1ComponentNodes = getComponentNodes(purlToIdentities, components, ISSUE284_PROVIDED_DEPENDENCY);
+ assertNull("Unexpected provided_dependency:1.0.0 component discovered in BOM", testIssue284ProvidedDependency1ComponentNodes);
/*
*/
- final Node issue284Dependency2Node = getDependencyNode(dependencies, ISSUE284_DEPENDENCY2);
- assertNotNull("Missing issue284_dependency2 dependency", issue284Dependency2Node);
+ final Element issue284Dependency2Node = getDependencyNode(purlToIdentities, dependencies, ISSUE284_DEPENDENCY2);
Set issue284Dependency2Dependencies = getDependencyReferences(issue284Dependency2Node);
assertEquals("Invalid dependency count for issue284_dependency2", 1, issue284Dependency2Dependencies.size());
- assertTrue("Missing issue284_shared_dependency1 dependency for issue284_dependency2", issue284Dependency2Dependencies.contains(ISSUE284_SHARED_DEPENDENCY1));
+ containsDependency(purlToIdentities, issue284Dependency2Dependencies, ISSUE284_SHARED_DEPENDENCY1);
/*
*/
- final Node issue284SharedDependency1Node = getDependencyNode(dependencies, ISSUE284_SHARED_DEPENDENCY1);
- assertNotNull("Missing issue284_shared_dependency1 dependency", issue284SharedDependency1Node);
+ final Element issue284SharedDependency1Node = getDependencyNode(purlToIdentities, dependencies, ISSUE284_SHARED_DEPENDENCY1);
Set issue284SharedDependency1Dependencies = getDependencyReferences(issue284SharedDependency1Node);
assertEquals("Invalid dependency count for issue284_shared_dependency1", 1, issue284SharedDependency1Dependencies.size());
- assertTrue("Missing issue284_shared_dependency2 dependency for issue284_shared_dependency1", issue284SharedDependency1Dependencies.contains(ISSUE284_SHARED_DEPENDENCY2));
+ containsDependency(purlToIdentities, issue284SharedDependency1Dependencies, ISSUE284_SHARED_DEPENDENCY2);
}
}
diff --git a/src/test/java/org/cyclonedx/maven/RuntimeTest.java b/src/test/java/org/cyclonedx/maven/RuntimeTest.java
index 1b7d627b..9b8da7a6 100644
--- a/src/test/java/org/cyclonedx/maven/RuntimeTest.java
+++ b/src/test/java/org/cyclonedx/maven/RuntimeTest.java
@@ -1,20 +1,22 @@
package org.cyclonedx.maven;
import java.io.File;
+import java.util.Collection;
+import java.util.Map;
import java.util.Set;
+import static org.cyclonedx.maven.TestUtils.containsDependency;
import static org.cyclonedx.maven.TestUtils.getDependencyNode;
import static org.cyclonedx.maven.TestUtils.getDependencyReferences;
+import static org.cyclonedx.maven.TestUtils.getPUrlToIdentities;
import static org.cyclonedx.maven.TestUtils.readXML;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.w3c.dom.Document;
-import org.w3c.dom.Node;
+import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
@@ -35,7 +37,7 @@ public class RuntimeTest extends BaseMavenVerifier {
private static final String RUNTIME_PROVIDED = "pkg:maven/com.example/runtime_provided@1.0.0?type=jar";
private static final String RUNTIME_TEST = "pkg:maven/com.example/runtime_test@1.0.0?type=jar";
private static final String RUNTIME_SHARED_DEPENDENCY1 = "pkg:maven/com.example/runtime_shared_dependency1@1.0.0?type=jar";
- private static final Object RUNTIME_SHARED_DEPENDENCY2 = "pkg:maven/com.example/runtime_shared_dependency2@1.0.0?type=jar";
+ private static final String RUNTIME_SHARED_DEPENDENCY2 = "pkg:maven/com.example/runtime_shared_dependency2@1.0.0?type=jar";
public RuntimeTest(MavenRuntimeBuilder runtimeBuilder) throws Exception {
super(runtimeBuilder);
@@ -51,10 +53,11 @@ public void testRuntime() throws Exception {
public void checkRuntimeCompile(final File projDir) throws Exception {
final Document bom = readXML(new File(projDir, "runtime_compile/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
/*
@@ -62,18 +65,16 @@ public void checkRuntimeCompile(final File projDir) throws Exception {
*/
- final Node runtimeCompileNode = getDependencyNode(dependencies, RUNTIME_COMPILE);
- assertNotNull("Missing runtime_compile dependency", runtimeCompileNode);
+ final Element runtimeCompileNode = getDependencyNode(purlToIdentities, dependencies, RUNTIME_COMPILE);
Set runtimeCompileDependencies = getDependencyReferences(runtimeCompileNode);
assertEquals("Invalid dependency count for runtime_compile", 2, runtimeCompileDependencies.size());
- assertTrue("Missing runtime_runtime_dependency dependency for runtime_compile", runtimeCompileDependencies.contains(RUNTIME_RUNTIME_DEPENDENCY));
- assertTrue("Missing runtime_dependency dependency for runtime_compile", runtimeCompileDependencies.contains(RUNTIME_DEPENDENCY));
+ containsDependency(purlToIdentities, runtimeCompileDependencies, RUNTIME_RUNTIME_DEPENDENCY);
+ containsDependency(purlToIdentities, runtimeCompileDependencies, RUNTIME_DEPENDENCY);
/*
*/
- final Node runtimeRuntimeDependencyNode = getDependencyNode(dependencies, RUNTIME_RUNTIME_DEPENDENCY);
- assertNotNull("Missing runtime_runtime_dependency dependency", runtimeRuntimeDependencyNode);
+ final Element runtimeRuntimeDependencyNode = getDependencyNode(purlToIdentities, dependencies, RUNTIME_RUNTIME_DEPENDENCY);
Set runtimeRuntimeDependencyDependencies = getDependencyReferences(runtimeRuntimeDependencyNode);
assertEquals("Invalid dependency count for runtime_runtime_dependency", 0, runtimeRuntimeDependencyDependencies.size());
@@ -82,30 +83,29 @@ public void checkRuntimeCompile(final File projDir) throws Exception {
*/
- final Node runtimeDependencyNode = getDependencyNode(dependencies, RUNTIME_DEPENDENCY);
- assertNotNull("Missing runtime_dependency dependency", runtimeDependencyNode);
+ final Element runtimeDependencyNode = getDependencyNode(purlToIdentities, dependencies, RUNTIME_DEPENDENCY);
Set runtimeDependencyDependencies = getDependencyReferences(runtimeDependencyNode);
assertEquals("Invalid dependency count for runtime_dependency", 1, runtimeDependencyDependencies.size());
- assertTrue("Missing runtime_shared_dependency1 dependency for runtime_dependency", runtimeDependencyDependencies.contains(RUNTIME_SHARED_DEPENDENCY1));
+ containsDependency(purlToIdentities, runtimeDependencyDependencies, RUNTIME_SHARED_DEPENDENCY1);
/*
*/
- final Node runtimeSharedDependency1Node = getDependencyNode(dependencies, RUNTIME_SHARED_DEPENDENCY1);
- assertNotNull("Missing runtime_shared_dependency1 dependency", runtimeSharedDependency1Node);
+ final Element runtimeSharedDependency1Node = getDependencyNode(purlToIdentities, dependencies, RUNTIME_SHARED_DEPENDENCY1);
Set runtimeSharedDependency1Dependencies = getDependencyReferences(runtimeSharedDependency1Node);
assertEquals("Invalid dependency count for runtime_shared_dependency1", 1, runtimeSharedDependency1Dependencies.size());
- assertTrue("Missing runtime_shared_dependency2 dependency for runtime_shared_dependency1", runtimeSharedDependency1Dependencies.contains(RUNTIME_SHARED_DEPENDENCY2));
+ containsDependency(purlToIdentities, runtimeSharedDependency1Dependencies, RUNTIME_SHARED_DEPENDENCY2);
}
public void checkRuntimeProvided(final File projDir) throws Exception {
final Document bom = readXML(new File(projDir, "runtime_provided/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
/*
@@ -113,39 +113,37 @@ public void checkRuntimeProvided(final File projDir) throws Exception {
*/
- final Node runtimeProvidedNode = getDependencyNode(dependencies, RUNTIME_PROVIDED);
- assertNotNull("Missing runtime_provided dependency", runtimeProvidedNode);
+ final Element runtimeProvidedNode = getDependencyNode(purlToIdentities, dependencies, RUNTIME_PROVIDED);
Set runtimeProvidedDependencies = getDependencyReferences(runtimeProvidedNode);
assertEquals("Invalid dependency count for runtime_provided", 2, runtimeProvidedDependencies.size());
- assertTrue("Missing runtime_shared_dependency1 dependency for runtime_provided", runtimeProvidedDependencies.contains(RUNTIME_SHARED_DEPENDENCY1));
- assertTrue("Missing runtime_runtime_dependency dependency for runtime_provided", runtimeProvidedDependencies.contains(RUNTIME_RUNTIME_DEPENDENCY));
+ containsDependency(purlToIdentities, runtimeProvidedDependencies, RUNTIME_SHARED_DEPENDENCY1);
+ containsDependency(purlToIdentities, runtimeProvidedDependencies, RUNTIME_RUNTIME_DEPENDENCY);
/*
*/
- final Node runtimeSharedDependency1Node = getDependencyNode(dependencies, RUNTIME_SHARED_DEPENDENCY1);
- assertNotNull("Missing runtime_shared_dependency1 dependency", runtimeSharedDependency1Node);
+ final Element runtimeSharedDependency1Node = getDependencyNode(purlToIdentities, dependencies, RUNTIME_SHARED_DEPENDENCY1);
Set runtimeSharedDependency1Dependencies = getDependencyReferences(runtimeSharedDependency1Node);
assertEquals("Invalid dependency count for runtime_shared_dependency1", 1, runtimeSharedDependency1Dependencies.size());
- assertTrue("Missing runtime_shared_dependency2 dependency for runtime_shared_dependency1", runtimeSharedDependency1Dependencies.contains(RUNTIME_SHARED_DEPENDENCY2));
+ containsDependency(purlToIdentities, runtimeSharedDependency1Dependencies, RUNTIME_SHARED_DEPENDENCY2);
/*
*/
- final Node runtimeRuntimeDependencyNode = getDependencyNode(dependencies, RUNTIME_RUNTIME_DEPENDENCY);
- assertNotNull("Missing runtime_runtime_dependency dependency", runtimeRuntimeDependencyNode);
+ final Element runtimeRuntimeDependencyNode = getDependencyNode(purlToIdentities, dependencies, RUNTIME_RUNTIME_DEPENDENCY);
Set runtimeRuntimeDependencyDependencies = getDependencyReferences(runtimeRuntimeDependencyNode);
assertEquals("Invalid dependency count for runtime_runtime_dependency", 0, runtimeRuntimeDependencyDependencies.size());
}
public void checkRuntimeTest(final File projDir) throws Exception {
final Document bom = readXML(new File(projDir, "runtime_test/target/bom.xml"));
+ final Map> purlToIdentities = getPUrlToIdentities(bom.getDocumentElement());
final NodeList dependenciesList = bom.getElementsByTagName("dependencies");
assertEquals("Expected a single dependencies element", 1, dependenciesList.getLength());
- final Node dependencies = dependenciesList.item(0);
+ final Element dependencies = (Element)dependenciesList.item(0);
/*
@@ -153,29 +151,26 @@ public void checkRuntimeTest(final File projDir) throws Exception {
*/
- final Node runtimeTestNode = getDependencyNode(dependencies, RUNTIME_TEST);
- assertNotNull("Missing runtime_test dependency", runtimeTestNode);
+ final Element runtimeTestNode = getDependencyNode(purlToIdentities, dependencies, RUNTIME_TEST);
Set runtimeTestDependencies = getDependencyReferences(runtimeTestNode);
assertEquals("Invalid dependency count for runtime_test", 2, runtimeTestDependencies.size());
- assertTrue("Missing runtime_shared_dependency1 dependency for runtime_test", runtimeTestDependencies.contains(RUNTIME_SHARED_DEPENDENCY1));
- assertTrue("Missing runtime_runtime_dependency dependency for runtime_test", runtimeTestDependencies.contains(RUNTIME_RUNTIME_DEPENDENCY));
+ containsDependency(purlToIdentities, runtimeTestDependencies, RUNTIME_SHARED_DEPENDENCY1);
+ containsDependency(purlToIdentities, runtimeTestDependencies, RUNTIME_RUNTIME_DEPENDENCY);
/*
*/
- final Node runtimeSharedDependency1Node = getDependencyNode(dependencies, RUNTIME_SHARED_DEPENDENCY1);
- assertNotNull("Missing runtime_shared_dependency1 dependency", runtimeSharedDependency1Node);
+ final Element runtimeSharedDependency1Node = getDependencyNode(purlToIdentities, dependencies, RUNTIME_SHARED_DEPENDENCY1);
Set runtimeSharedDependency1Dependencies = getDependencyReferences(runtimeSharedDependency1Node);
assertEquals("Invalid dependency count for runtime_shared_dependency1", 1, runtimeSharedDependency1Dependencies.size());
- assertTrue("Missing runtime_shared_dependency2 dependency for runtime_shared_dependency1", runtimeSharedDependency1Dependencies.contains(RUNTIME_SHARED_DEPENDENCY2));
+ containsDependency(purlToIdentities, runtimeSharedDependency1Dependencies, RUNTIME_SHARED_DEPENDENCY2);
/*
*/
- final Node runtimeRuntimeDependencyNode = getDependencyNode(dependencies, RUNTIME_RUNTIME_DEPENDENCY);
- assertNotNull("Missing runtime_runtime_dependency dependency", runtimeRuntimeDependencyNode);
+ final Element runtimeRuntimeDependencyNode = getDependencyNode(purlToIdentities, dependencies, RUNTIME_RUNTIME_DEPENDENCY);
Set runtimeRuntimeDependencyDependencies = getDependencyReferences(runtimeRuntimeDependencyNode);
assertEquals("Invalid dependency count for runtime_runtime_dependency", 0, runtimeRuntimeDependencyDependencies.size());
}
diff --git a/src/test/java/org/cyclonedx/maven/TestUtils.java b/src/test/java/org/cyclonedx/maven/TestUtils.java
index bb9731b4..51ec4a2b 100644
--- a/src/test/java/org/cyclonedx/maven/TestUtils.java
+++ b/src/test/java/org/cyclonedx/maven/TestUtils.java
@@ -2,7 +2,11 @@
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
@@ -10,58 +14,113 @@
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
+import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
class TestUtils {
- static Node getDependencyNode(final Node dependencies, final String ref) {
- return getChildElement(dependencies, ref, "dependency", "ref");
+ static Element getDependencyNode(final Map> purlToIdentities, final Element dependencies, final String purl) throws Exception {
+ int numElements = 0;
+ Collection elements = getDependencyNodes(purlToIdentities, dependencies, purl);
+ if (elements != null) {
+ numElements = elements.size();
+ if (numElements == 1) {
+ return elements.iterator().next();
+ }
+ }
+ throw new Exception("Expected a single dependency for purl " + purl + ", found " + numElements + " dependencies");
}
- static Node getComponentNode(final Node components, final String ref) {
- return getChildElement(components, ref, "component", "bom-ref");
+ static Element getComponentNode(final Map> purlToIdentities, final Element components, final String purl) throws Exception {
+ int numElements = 0;
+ Collection elements = getComponentNodes(purlToIdentities, components, purl);
+ if (elements != null) {
+ numElements = elements.size();
+ if (numElements == 1) {
+ return elements.iterator().next();
+ }
+ }
+ throw new Exception("Expected a single component for purl " + purl + ", found " + numElements + " components");
}
- private static Node getChildElement(final Node parent, final String ref, final String elementName, final String attrName) {
+ static Collection getDependencyNodes(final Map> purlToIdentities, final Element dependencies, final String purl) {
+ return getChildElements(purlToIdentities, dependencies, purl, "dependency", "ref");
+ }
+
+ static Collection getComponentNodes(final Map> purlToIdentities, final Element components, final String purl) {
+ return getChildElements(purlToIdentities, components, purl, "component", "bom-ref");
+ }
+
+ static Element getDependencyNodeByIdentity(final Element dependencies, final String identity) throws Exception {
+ int numElements = 0;
+ Collection elements = getChildElementsByIdentity(dependencies, Arrays.asList(identity), "dependency", "ref");
+ if (elements != null) {
+ numElements = elements.size();
+ if (numElements == 1) {
+ return elements.iterator().next();
+ }
+ }
+ throw new Exception("Expected a single dependency for identity " + identity + ", found " + numElements + " dependencies");
+ }
+
+ static Element getComponentNodeByIdentity(final Element components, final String identity) throws Exception {
+ int numElements = 0;
+ Collection elements = getChildElementsByIdentity(components, Arrays.asList(identity), "component", "bom-ref");
+ if (elements != null) {
+ numElements = elements.size();
+ if (numElements == 1) {
+ return elements.iterator().next();
+ }
+ }
+ throw new Exception("Expected a single compoennt for identity " + identity + ", found " + numElements + " components");
+ }
+
+ private static Collection getChildElements(final Map> purlToIdentities, final Element parent, final String purl, final String elementName, final String attrName) {
+ final Collection identities = purlToIdentities.get(purl);
+ return getChildElementsByIdentity(parent, identities, elementName, attrName);
+ }
+
+ private static Collection getChildElementsByIdentity(final Element parent, final Collection identities, final String elementName, final String attrName) {
+ if (identities == null) {
+ return null;
+ }
+ final Collection childElements = new HashSet<>();
+
final NodeList children = parent.getChildNodes();
final int numChildNodes = children.getLength();
for (int index = 0 ; index < numChildNodes ; index++) {
final Node child = children.item(index);
if ((child.getNodeType() == Node.ELEMENT_NODE) && elementName.equals(child.getNodeName())) {
final Node refNode = child.getAttributes().getNamedItem(attrName);
- if (ref.equals(refNode.getNodeValue())) {
- return child;
+ if (identities.contains(refNode.getNodeValue())) {
+ childElements.add((Element)child);
}
}
}
- return null;
+ return childElements;
}
- static Set getComponentReferences(final Node parent) {
+ static Set getComponentReferences(final Element parent) {
return getReferences(null, parent, "component", "bom-ref");
}
- static Set getDependencyReferences(final Node parent) {
+ static Set getDependencyReferences(final Element parent) {
return getReferences(null, parent, "dependency", "ref");
}
- private static Set getReferences(Set references, final Node rootNode, final String elementName, final String attrName) {
+ private static Set getReferences(Set references, final Element root, final String elementName, final String attrName) {
if (references == null) {
references = new HashSet<>();
}
- final NodeList children = rootNode.getChildNodes();
- final int numChildNodes = children.getLength();
- for (int index = 0 ; index < numChildNodes ; index++) {
- final Node child = children.item(index);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- if (elementName.equals(child.getNodeName())) {
- final Node refNode = child.getAttributes().getNamedItem(attrName);
- if (refNode != null) {
- references.add(refNode.getNodeValue());
- }
- }
- getReferences(references, child, elementName, attrName);
+
+ final NodeList components = root.getElementsByTagName(elementName);
+ final int numComponents = components.getLength();
+ for (int index = 0 ; index < numComponents ; index++) {
+ final Element component = (Element) components.item(index);
+ final String value = component.getAttribute(attrName);
+ if (value != null) {
+ references.add(value);
}
}
return references;
@@ -77,4 +136,35 @@ static Document readXML(File file) throws IOException, SAXException, ParserConfi
final DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(file);
}
+
+ static Map> getPUrlToIdentities(final Element root) {
+ final Map> purlToIdentities = new HashMap<>();
+ final NodeList components = root.getElementsByTagName("component");
+ for (int index = 0 ; index < components.getLength() ; index++) {
+ final Element component = (Element)components.item(index);
+ final String bomRef = component.getAttribute("bom-ref");
+ final String purl = component.getElementsByTagName("purl").item(0).getTextContent();
+ final Collection identities = purlToIdentities.get(purl);
+ if (identities != null) {
+ identities.add(bomRef);
+ } else {
+ final Collection newIdentities = new HashSet<>();
+ newIdentities.add(bomRef);
+ purlToIdentities.put(purl, newIdentities);
+ }
+ }
+ return purlToIdentities;
+ }
+
+ static boolean containsDependency(final Map> purlToIdentities, final Set dependencies, final String purl) throws Exception {
+ final Collection identities = purlToIdentities.get(purl);
+ int numIdentities = 0;
+ if (identities != null) {
+ numIdentities = identities.size();
+ if (numIdentities == 1) {
+ return dependencies.contains(identities.iterator().next());
+ }
+ }
+ throw new Exception("Expected a single identity for purl " + purl + ", found " + numIdentities + " identities");
+ }
}
diff --git a/src/test/resources/dependency_trees/exclusion/dependency_A/pom.xml b/src/test/resources/dependency_trees/exclusion/dependency_A/pom.xml
new file mode 100644
index 00000000..a4a2d7f4
--- /dev/null
+++ b/src/test/resources/dependency_trees/exclusion/dependency_A/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.exclusion
+ exclusion_parent
+ 1.0.0
+
+
+ dependency_A
+
+ Dependency A
+
+
+
+ com.example.dependency_trees.exclusion
+ dependency_B
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/exclusion/dependency_B/pom.xml b/src/test/resources/dependency_trees/exclusion/dependency_B/pom.xml
new file mode 100644
index 00000000..9c8d9429
--- /dev/null
+++ b/src/test/resources/dependency_trees/exclusion/dependency_B/pom.xml
@@ -0,0 +1,32 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.exclusion
+ exclusion_parent
+ 1.0.0
+
+
+ dependency_B
+
+ Dependency B
+
+
+
+ com.example.dependency_trees.exclusion
+ dependency_C
+ 1.0.0
+ compile
+
+
+ com.example.dependency_trees.exclusion
+ dependency_E
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/exclusion/dependency_C/pom.xml b/src/test/resources/dependency_trees/exclusion/dependency_C/pom.xml
new file mode 100644
index 00000000..fcf8d10e
--- /dev/null
+++ b/src/test/resources/dependency_trees/exclusion/dependency_C/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.exclusion
+ exclusion_parent
+ 1.0.0
+
+
+ dependency_C
+
+ Dependency C
+
+
+
+ com.example.dependency_trees.exclusion
+ dependency_D
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/exclusion/dependency_D/pom.xml b/src/test/resources/dependency_trees/exclusion/dependency_D/pom.xml
new file mode 100644
index 00000000..36fde135
--- /dev/null
+++ b/src/test/resources/dependency_trees/exclusion/dependency_D/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.exclusion
+ exclusion_parent
+ 1.0.0
+
+
+ dependency_D
+
+ Dependency D
+
diff --git a/src/test/resources/dependency_trees/exclusion/dependency_E/pom.xml b/src/test/resources/dependency_trees/exclusion/dependency_E/pom.xml
new file mode 100644
index 00000000..197610be
--- /dev/null
+++ b/src/test/resources/dependency_trees/exclusion/dependency_E/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.exclusion
+ exclusion_parent
+ 1.0.0
+
+
+ dependency_E
+
+ Dependency E
+
diff --git a/src/test/resources/dependency_trees/exclusion/dependency_F/pom.xml b/src/test/resources/dependency_trees/exclusion/dependency_F/pom.xml
new file mode 100644
index 00000000..bda260c3
--- /dev/null
+++ b/src/test/resources/dependency_trees/exclusion/dependency_F/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.exclusion
+ exclusion_parent
+ 1.0.0
+
+
+ dependency_F
+
+ Dependency F
+
+
+
+
+ com.example.dependency_trees.exclusion
+ dependency_B
+ 1.0.0
+
+
+ com.example.dependency_trees.exclusion
+ dependency_E
+
+
+
+
+
+
+
+
+ com.example.dependency_trees.exclusion
+ dependency_B
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/exclusion/pom.xml b/src/test/resources/dependency_trees/exclusion/pom.xml
new file mode 100644
index 00000000..fdef4e30
--- /dev/null
+++ b/src/test/resources/dependency_trees/exclusion/pom.xml
@@ -0,0 +1,28 @@
+
+
+
+ 4.0.0
+
+ com.example.dependency_trees.exclusion
+ exclusion_parent
+ pom
+ 1.0.0
+
+ Exclusion Tests Parent
+
+
+ dependency_A
+ dependency_B
+ dependency_C
+ dependency_D
+ dependency_E
+ dependency_F
+
+
+
+ UTF-8
+
+
+
diff --git a/src/test/resources/dependency_trees/managed/dependency_A/pom.xml b/src/test/resources/dependency_trees/managed/dependency_A/pom.xml
new file mode 100644
index 00000000..f2d3c3b5
--- /dev/null
+++ b/src/test/resources/dependency_trees/managed/dependency_A/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.managed
+ managed_parent
+ 1.0.0
+
+
+ dependency_A
+
+ Dependency A
+
+
+
+ com.example.dependency_trees.managed
+ dependency_B
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/managed/dependency_B/pom.xml b/src/test/resources/dependency_trees/managed/dependency_B/pom.xml
new file mode 100644
index 00000000..827daa8d
--- /dev/null
+++ b/src/test/resources/dependency_trees/managed/dependency_B/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.managed
+ managed_parent
+ 1.0.0
+
+
+ dependency_B
+
+ Dependency B
+
+
+
+ com.example.dependency_trees.managed
+ dependency_C
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/managed/dependency_C1/pom.xml b/src/test/resources/dependency_trees/managed/dependency_C1/pom.xml
new file mode 100644
index 00000000..799d8025
--- /dev/null
+++ b/src/test/resources/dependency_trees/managed/dependency_C1/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.managed
+ managed_parent
+ 1.0.0
+
+
+ dependency_C
+
+ Dependency C
+
+
+
+ com.example.dependency_trees.managed
+ dependency_D
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/managed/dependency_C2/pom.xml b/src/test/resources/dependency_trees/managed/dependency_C2/pom.xml
new file mode 100644
index 00000000..49eb8d11
--- /dev/null
+++ b/src/test/resources/dependency_trees/managed/dependency_C2/pom.xml
@@ -0,0 +1,27 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.managed
+ managed_parent
+ 1.0.0
+
+
+ dependency_C
+ 2.0.0
+
+ Dependency C
+
+
+
+ com.example.dependency_trees.managed
+ dependency_D
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/managed/dependency_D/pom.xml b/src/test/resources/dependency_trees/managed/dependency_D/pom.xml
new file mode 100644
index 00000000..aad385f0
--- /dev/null
+++ b/src/test/resources/dependency_trees/managed/dependency_D/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.managed
+ managed_parent
+ 1.0.0
+
+
+ dependency_D
+
+ Dependency D
+
diff --git a/src/test/resources/dependency_trees/managed/dependency_E/pom.xml b/src/test/resources/dependency_trees/managed/dependency_E/pom.xml
new file mode 100644
index 00000000..d9eabf0c
--- /dev/null
+++ b/src/test/resources/dependency_trees/managed/dependency_E/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+ 4.0.0
+
+
+ com.example.dependency_trees.managed
+ managed_parent
+ 1.0.0
+
+
+ dependency_E
+
+ Dependency E
+
+
+
+
+ com.example.dependency_trees.managed
+ dependency_C
+ 2.0.0
+
+
+
+
+
+
+ com.example.dependency_trees.managed
+ dependency_B
+ 1.0.0
+ compile
+
+
+
diff --git a/src/test/resources/dependency_trees/managed/pom.xml b/src/test/resources/dependency_trees/managed/pom.xml
new file mode 100644
index 00000000..622300f9
--- /dev/null
+++ b/src/test/resources/dependency_trees/managed/pom.xml
@@ -0,0 +1,28 @@
+
+
+
+ 4.0.0
+
+ com.example.dependency_trees.managed
+ managed_parent
+ pom
+ 1.0.0
+
+ Managed Dependency Tests Parent
+
+
+ dependency_A
+ dependency_B
+ dependency_C1
+ dependency_C2
+ dependency_D
+ dependency_E
+
+
+
+ UTF-8
+
+
+
diff --git a/src/test/resources/dependency_trees/pom.xml b/src/test/resources/dependency_trees/pom.xml
new file mode 100644
index 00000000..103ec4bd
--- /dev/null
+++ b/src/test/resources/dependency_trees/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ 4.0.0
+
+ com.example.dependency_trees
+ dependency_trees_parent
+ pom
+ 1.0.0
+
+ Dependency Trees Tests Parent
+
+
+ exclusion
+ managed
+
+
+
+
+
+ org.cyclonedx
+ cyclonedx-maven-plugin
+ ${current.version}
+
+
+ package
+
+ makeAggregateBom
+
+
+
+
+ library
+ 1.3
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ xml
+
+
+
+
+
+
+ maven-jar-plugin
+ 3.3.0
+
+
+
+
+
+
+ UTF-8
+
+