@@ -395,7 +395,7 @@ private ConsumedBom consumeBom(final org.cyclonedx.proto.v1_6.Bom cdxBom) {
395395 );
396396 }
397397
398- private record ProcessedBom (
398+ record ProcessedBom (
399399 Project project ,
400400 Collection <Component > components ,
401401 Collection <ServiceComponent > services
@@ -454,7 +454,8 @@ private ProcessedBom processBom(final ProcessingContext ctx, final ConsumedBom b
454454 processServices (qm , persistentProject , bom .services (), bom .identitiesByBomRef (), bom .bomRefsByIdentity ());
455455
456456 LOGGER .info ("Processing %d dependency graph entries" .formatted (bom .dependencyGraph ().asMap ().size ()));
457- processDependencyGraph (qm , persistentProject , bom .dependencyGraph (), persistentComponentsByIdentity , bom .identitiesByBomRef ());
457+ processDependencyGraph (qm , persistentProject , bom .dependencyGraph (), persistentComponentsByIdentity ,
458+ bom .identitiesByBomRef (), bom .bomRefsByIdentity ());
458459
459460 recordBomImport (ctx , qm , persistentProject );
460461
@@ -738,7 +739,8 @@ private void processDependencyGraph(
738739 final Project project ,
739740 final MultiValuedMap <String , String > dependencyGraph ,
740741 final Map <ComponentIdentity , Component > componentsByIdentity ,
741- final Map <String , ComponentIdentity > identitiesByBomRef
742+ final Map <String , ComponentIdentity > identitiesByBomRef ,
743+ final MultiValuedMap <ComponentIdentity , String > bomRefsByIdentity
742744 ) {
743745 assertPersistent (project , "Project must be persistent" );
744746
@@ -750,7 +752,10 @@ private void processDependencyGraph(
750752 is not one of them; Graph will be incomplete because it is not possible to determine its root\
751753 """ .formatted (dependencyGraph .size ()));
752754 }
753- final String directDependenciesJson = resolveDirectDependenciesJson (project .getBomRef (), directDependencyBomRefs , identitiesByBomRef );
755+ final String directDependenciesJson = resolveDependenciesJson (
756+ List .of (project .getBomRef ()),
757+ sourceBomRef -> sourceBomRef .equals (project .getBomRef ()) ? directDependencyBomRefs : null ,
758+ identitiesByBomRef );
754759 if (!Objects .equals (directDependenciesJson , project .getDirectDependencies ())) {
755760 project .setDirectDependencies (directDependenciesJson );
756761 qm .getPersistenceManager ().flush ();
@@ -763,25 +768,12 @@ private void processDependencyGraph(
763768 }
764769 }
765770
766- for (final Map .Entry <String , ComponentIdentity > entry : identitiesByBomRef .entrySet ()) {
767- final String componentBomRef = entry .getKey ();
768- final Collection <String > directDependencyBomRefs = dependencyGraph .get (componentBomRef );
769- final String directDependenciesJson = resolveDirectDependenciesJson (componentBomRef , directDependencyBomRefs , identitiesByBomRef );
770-
771- final ComponentIdentity dependencyIdentity = identitiesByBomRef .get (entry .getKey ());
772- final Component component = componentsByIdentity .get (dependencyIdentity );
773- // TODO: Check servicesByIdentity when persistentComponent is null
774- // We do not currently store directDependencies for ServiceComponent
775- if (component != null ) {
776- assertPersistent (component , "Component must be persistent" );
777- if (!Objects .equals (directDependenciesJson , component .getDirectDependencies ())) {
778- component .setDirectDependencies (directDependenciesJson );
779- }
780- } else {
781- LOGGER .warn ("""
782- Unable to resolve component identity %s to a persistent component; \
783- As a result, the dependency graph will likely be incomplete\
784- """ .formatted (dependencyIdentity .toJSON ()));
771+ for (final Component component : componentsByIdentity .values ()) {
772+ assertPersistent (component , "Component must be persistent" );
773+ final String mergedDirectDependenciesJson = resolveMergedDirectDependenciesJson (
774+ component , dependencyGraph , identitiesByBomRef , bomRefsByIdentity );
775+ if (!Objects .equals (mergedDirectDependenciesJson , component .getDirectDependencies ())) {
776+ component .setDirectDependencies (mergedDirectDependenciesJson );
785777 }
786778 }
787779
@@ -807,34 +799,63 @@ private static void recordBomImport(final ProcessingContext ctx, final QueryMana
807799 project .setLastBomImportFormat ("%s %s" .formatted (ctx .bomFormat .getFormatShortName (), ctx .bomSpecVersion ));
808800 }
809801
810- private String resolveDirectDependenciesJson (
811- final String dependencyBomRef ,
812- final Collection <String > directDependencyBomRefs ,
802+ /**
803+ * Builds {@code directDependencies} JSON for one component by merging direct dependency edges from every BOM ref
804+ * that maps to the same deduplicated identity.
805+ * <p>
806+ * {@link #distinctComponentsByIdentity} records those refs under {@code bomRefsByIdentity} using identities from
807+ * consumption (UUID usually unset). After persist, {@link ComponentIdentity#equals(Object)} includes UUID, so we
808+ * look up with {@code new ComponentIdentity(component, true)} — same structural key as at consumption, same pattern
809+ * as matching existing rows in {@link #processComponents}.
810+ */
811+ private @ Nullable String resolveMergedDirectDependenciesJson (
812+ final Component component ,
813+ final MultiValuedMap <String , String > dependencyGraph ,
814+ final Map <String , ComponentIdentity > identitiesByBomRef ,
815+ final MultiValuedMap <ComponentIdentity , String > bomRefsByIdentity
816+ ) {
817+ final Collection <String > sourceBomRefs = bomRefsByIdentity .get (
818+ new ComponentIdentity (component , /* excludeUuid */ true ));
819+ if (sourceBomRefs == null || sourceBomRefs .isEmpty ()) {
820+ return null ;
821+ }
822+
823+ return resolveDependenciesJson (sourceBomRefs , dependencyGraph ::get , identitiesByBomRef );
824+ }
825+
826+ private @ Nullable String resolveDependenciesJson (
827+ final Collection <String > sourceBomRefs ,
828+ final Function <String , Collection <String >> directDependencyBomRefsProvider ,
813829 final Map <String , ComponentIdentity > identitiesByBomRef
814830 ) {
815- if (directDependencyBomRefs == null || directDependencyBomRefs .isEmpty ()) {
831+ if (sourceBomRefs == null || sourceBomRefs .isEmpty ()) {
816832 return null ;
817833 }
818834
819835 final var jsonDependencies = Mappers .jsonMapper ().createArrayNode ();
820836 final var directDependencyIdentitiesSeen = new HashSet <ComponentIdentity >();
821- for (final String directDependencyBomRef : directDependencyBomRefs ) {
822- final ComponentIdentity directDependencyIdentity = identitiesByBomRef .get (directDependencyBomRef );
823- if (directDependencyIdentity != null ) {
824- if (!directDependencyIdentitiesSeen .add (directDependencyIdentity )) {
825- // It's possible that multiple direct dependencies of a project or component
826- // fall victim to de-duplication. In that case, we can ironically end up with
827- // duplicate component identities (i.e. duplicate BOM refs).
828- LOGGER .debug ("Omitting duplicate direct dependency %s for BOM ref %s"
829- .formatted (directDependencyBomRef , dependencyBomRef ));
830- continue ;
837+
838+ for (final String sourceBomRef : sourceBomRefs ) {
839+ final Collection <String > directDependencyBomRefs = directDependencyBomRefsProvider .apply (sourceBomRef );
840+ if (directDependencyBomRefs == null || directDependencyBomRefs .isEmpty ()) {
841+ continue ;
842+ }
843+
844+ for (final String directDependencyBomRef : directDependencyBomRefs ) {
845+ final ComponentIdentity directDependencyIdentity = identitiesByBomRef .get (directDependencyBomRef );
846+ if (directDependencyIdentity != null ) {
847+ if (!directDependencyIdentitiesSeen .add (directDependencyIdentity )) {
848+ LOGGER .debug ("Omitting duplicate direct dependency %s for BOM ref %s"
849+ .formatted (directDependencyBomRef , sourceBomRef ));
850+ continue ;
851+ }
852+ jsonDependencies .add (directDependencyIdentity .toJSON ());
853+ } else {
854+ LOGGER .warn ("""
855+ Unable to resolve BOM ref %s to a component identity while processing direct \
856+ dependencies of BOM ref %s; As a result, the dependency graph will likely be incomplete\
857+ """ .formatted (sourceBomRef , directDependencyBomRef ));
831858 }
832- jsonDependencies .add (directDependencyIdentity .toJSON ());
833- } else {
834- LOGGER .warn ("""
835- Unable to resolve BOM ref %s to a component identity while processing direct \
836- dependencies of BOM ref %s; As a result, the dependency graph will likely be incomplete\
837- """ .formatted (dependencyBomRef , directDependencyBomRef ));
838859 }
839860 }
840861
0 commit comments