diff --git a/src/main/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParser.java b/src/main/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParser.java index 421acdc0..543d15c5 100644 --- a/src/main/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParser.java +++ b/src/main/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParser.java @@ -20,15 +20,18 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.cyclonedx.model.Bom; import org.cyclonedx.model.Component; +import org.cyclonedx.model.Dependency; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +42,7 @@ import com.redhat.exhort.integration.backend.sbom.SbomParser; import com.redhat.exhort.model.DependencyTree; import com.redhat.exhort.model.DirectDependency; +import com.redhat.exhort.model.DirectDependency.Builder; import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.core.Response; @@ -75,49 +79,43 @@ protected DependencyTree buildTree(InputStream input) { Optional rootComponent = Optional.ofNullable(bom.getMetadata().getComponent()); if (rootComponent.isPresent()) { treeBuilder.root(PackageRef.builder().purl(rootComponent.get().getPurl()).build()); + if (!componentPurls.containsKey(rootComponent.get().getBomRef())) { + componentPurls.put( + rootComponent.get().getBomRef(), new PackageRef(rootComponent.get().getPurl())); + } } else { // rootless SBOM treeBuilder.root(DependencyTree.getDefaultRoot(packageManager)); } - bom.getDependencies().stream() - .forEach( + if (rootComponent.isPresent()) { + Map> indexedDeps = + bom.getDependencies().stream() + .collect( + Collectors.toMap( + d -> d.getRef(), + d -> + Optional.ofNullable(d.getDependencies()) + .orElse(Collections.emptyList()))); + List directDeps = indexedDeps.get(rootComponent.get().getBomRef()); + if (directDeps != null) { + directDeps.forEach( d -> { - if (rootComponent.isEmpty()) { - PackageRef ref = componentPurls.get(d.getRef()); - direct.put(ref, DirectDependency.builder().ref(ref)); - } else if (d.getRef().equals(rootComponent.get().getBomRef())) { - d.getDependencies() - .forEach( - rootDep -> { - PackageRef ref = componentPurls.get(rootDep.getRef()); - direct.put( - ref, - DirectDependency.builder().ref(ref).transitive(new HashSet<>())); - }); - } else if (d.getDependencies() != null) { - PackageRef source = componentPurls.get(d.getRef()); - DirectDependency.Builder directBuilder = direct.get(source); - if (directBuilder == null) { - direct.values().stream() - .filter(v -> v.transitive.contains(source)) - .forEach( - v -> - d.getDependencies() - .forEach( - t -> { - PackageRef target = componentPurls.get(t.getRef()); - v.transitive.add(target); - })); - } else { - d.getDependencies() - .forEach( - t -> { - PackageRef target = componentPurls.get(t.getRef()); - directBuilder.transitive.add(target); - }); - } - } + PackageRef pkgRef = componentPurls.get(d.getRef()); + direct.put(pkgRef, new Builder().ref(pkgRef).transitive(new HashSet<>())); + addDependencies(d.getRef(), indexedDeps, direct.get(pkgRef), componentPurls); }); + } + } else { + // Rootless sboms are expected to be flat. i.e. no transitive dependencies + bom.getDependencies().stream() + .map(d -> componentPurls.get(d.getRef())) + .filter(Objects::nonNull) + .forEach( + c -> + direct.put( + c, + new DirectDependency.Builder().ref(c).transitive(Collections.emptySet()))); + } Map deps = direct.entrySet().stream() .map(e -> Map.entry(e.getKey(), e.getValue().build())) @@ -129,4 +127,20 @@ protected DependencyTree buildTree(InputStream input) { "Unable to parse received CycloneDX SBOM file", Response.Status.BAD_REQUEST); } } + + private void addDependencies( + String bomRef, + Map> indexedDeps, + Builder builder, + Map componentPurls) { + List deps = indexedDeps.get(bomRef); + if (deps == null || deps.isEmpty()) { + return; + } + deps.forEach( + d -> { + builder.transitive.add(componentPurls.get(d.getRef())); + addDependencies(d.getRef(), indexedDeps, builder, componentPurls); + }); + } }