@@ -297,10 +297,36 @@ public CompletableFuture<byte[]> stackAnalysisHtml(final String manifestFile) th
297297 public CompletableFuture <AnalysisReport > stackAnalysis (final String manifestFile )
298298 throws IOException {
299299 String exClientTraceId = commonHookBeginning (false );
300+ var content = resolveStackContent (manifestFile );
301+ var uri = resolveAnalysisUri (content );
302+ var request = buildRequest (content , uri , MediaType .APPLICATION_JSON , "Stack Analysis" );
303+ if (content .batch ) {
304+ return this .client
305+ .sendAsync (request , HttpResponse .BodyHandlers .ofString ())
306+ .thenApply (
307+ response -> {
308+ RequestManager .getInstance ().addClientTraceIdToRequest (exClientTraceId );
309+ if (debugLoggingIsNeeded ()) {
310+ logExhortRequestId (response );
311+ }
312+ Map <String , AnalysisReport > reports = getBatchStackAnalysisReports (response );
313+ commonHookAfterExhortResponse ();
314+ return reports .isEmpty ()
315+ ? new AnalysisReport ()
316+ : reports .values ().iterator ().next ();
317+ })
318+ .exceptionally (
319+ exception -> {
320+ LOG .severe (
321+ String .format (
322+ "failed to invoke stackAnalysis (batch) for getting the json report,"
323+ + " received message= %s " ,
324+ exception .getMessage ()));
325+ return new AnalysisReport ();
326+ });
327+ }
300328 return this .client
301- .sendAsync (
302- this .buildStackRequest (manifestFile , MediaType .APPLICATION_JSON ),
303- HttpResponse .BodyHandlers .ofString ())
329+ .sendAsync (request , HttpResponse .BodyHandlers .ofString ())
304330 .thenApply (
305331 response ->
306332 getAnalysisReportFromResponse (response , "StackAnalysis" , "json" , exClientTraceId ))
@@ -376,9 +402,12 @@ public CompletableFuture<AnalysisReport> componentAnalysis(
376402 String exClientTraceId = commonHookBeginning (false );
377403 var manifestPath = Path .of (manifest );
378404 var provider = Ecosystem .getProvider (manifestPath );
379- var uri = buildAnalysisUri (S_API_V_5_ANALYSIS , getEndpoint ());
380405 var content = provider .provideComponent ();
381406 commonHookAfterProviderCreatedSbomAndBeforeExhort ();
407+ var uri = resolveAnalysisUri (content );
408+ if (content .batch ) {
409+ return getBatchAnalysisReportForComponent (uri , content , exClientTraceId );
410+ }
382411 return getAnalysisReportForComponent (uri , content , exClientTraceId );
383412 }
384413
@@ -420,9 +449,12 @@ public CompletableFuture<AnalysisReport> componentAnalysis(String manifestFile)
420449 String exClientTraceId = commonHookBeginning (false );
421450 var manifestPath = Path .of (manifestFile );
422451 var provider = Ecosystem .getProvider (manifestPath );
423- var uri = buildAnalysisUri (S_API_V_5_ANALYSIS , getEndpoint ());
424452 var content = provider .provideComponent ();
425453 commonHookAfterProviderCreatedSbomAndBeforeExhort ();
454+ var uri = resolveAnalysisUri (content );
455+ if (content .batch ) {
456+ return getBatchAnalysisReportForComponent (uri , content , exClientTraceId );
457+ }
426458 return getAnalysisReportForComponent (uri , content , exClientTraceId );
427459 }
428460
@@ -449,8 +481,48 @@ private CompletableFuture<AnalysisReport> getAnalysisReportForComponent(
449481 });
450482 }
451483
484+ /** Resolves the analysis URI based on whether the content is batch or single. */
485+ private URI resolveAnalysisUri (Provider .Content content ) {
486+ if (content .batch ) {
487+ return buildAnalysisUri (S_API_V_5_BATCH_ANALYSIS , getEndpoint ());
488+ }
489+ return buildAnalysisUri (S_API_V_5_ANALYSIS , getEndpoint ());
490+ }
491+
452492 /**
453- * Build an HTTP request wrapper for sending to the Backend API for Stack Analysis only.
493+ * Sends batch content to the batch-analysis endpoint and returns the first analysis report from
494+ * the batch response map.
495+ */
496+ private CompletableFuture <AnalysisReport > getBatchAnalysisReportForComponent (
497+ URI uri , Provider .Content content , String exClientTraceId ) {
498+ return this .client
499+ .sendAsync (
500+ this .buildRequest (content , uri , MediaType .APPLICATION_JSON , "Batch Component Analysis" ),
501+ HttpResponse .BodyHandlers .ofString ())
502+ .thenApply (
503+ response -> {
504+ RequestManager .getInstance ().addClientTraceIdToRequest (exClientTraceId );
505+ if (debugLoggingIsNeeded ()) {
506+ logExhortRequestId (response );
507+ }
508+ Map <String , AnalysisReport > reports = getBatchStackAnalysisReports (response );
509+ commonHookAfterExhortResponse ();
510+ return reports .isEmpty () ? new AnalysisReport () : reports .values ().iterator ().next ();
511+ })
512+ .exceptionally (
513+ exception -> {
514+ LOG .severe (
515+ String .format (
516+ "failed to invoke Batch Component Analysis for getting the json report,"
517+ + " received message= %s " ,
518+ exception .getMessage ()));
519+ return new AnalysisReport ();
520+ });
521+ }
522+
523+ /**
524+ * Build an HTTP request wrapper for sending to the Backend API for Stack Analysis only. Uses the
525+ * batch-analysis endpoint when the provider returns batch content.
454526 *
455527 * @param manifestFile the path for the manifest file
456528 * @param acceptType the type of requested content
@@ -459,13 +531,21 @@ private CompletableFuture<AnalysisReport> getAnalysisReportForComponent(
459531 */
460532 private HttpRequest buildStackRequest (final String manifestFile , final MediaType acceptType )
461533 throws IOException {
534+ var content = resolveStackContent (manifestFile );
535+ var uri = resolveAnalysisUri (content );
536+ return buildRequest (content , uri , acceptType , "Stack Analysis" );
537+ }
538+
539+ /**
540+ * Resolves the provider for the given manifest file and produces the stack content. Also tracks
541+ * timing via {@link #commonHookAfterProviderCreatedSbomAndBeforeExhort()}.
542+ */
543+ private Provider .Content resolveStackContent (String manifestFile ) throws IOException {
462544 var manifestPath = Path .of (manifestFile );
463545 var provider = Ecosystem .getProvider (manifestPath );
464- var uri = buildAnalysisUri (S_API_V_5_ANALYSIS , getEndpoint ());
465546 var content = provider .provideStack ();
466547 commonHookAfterProviderCreatedSbomAndBeforeExhort ();
467-
468- return buildRequest (content , uri , acceptType , "Stack Analysis" );
548+ return content ;
469549 }
470550
471551 @ Override
@@ -610,27 +690,29 @@ public CompletableFuture<ComponentAnalysisResult> componentAnalysisWithLicense(
610690 String exClientTraceId = commonHookBeginning (false );
611691 var manifestPath = Path .of (manifestFile );
612692 var provider = Ecosystem .getProvider (manifestPath );
613- var uri = buildAnalysisUri (S_API_V_5_ANALYSIS , getEndpoint ());
614693 var content = provider .provideComponent ();
615694 String sbomJson = new String (content .buffer );
616695 commonHookAfterProviderCreatedSbomAndBeforeExhort ();
617- return getAnalysisReportForComponent (uri , content , exClientTraceId )
618- .thenCompose (
619- report -> {
620- if (!isLicenseCheckEnabled ()) {
621- return CompletableFuture .completedFuture (new ComponentAnalysisResult (report , null ));
622- }
623- return LicenseCheck .runLicenseCheck (this , provider , manifestPath , sbomJson , report )
624- .thenApply (summary -> new ComponentAnalysisResult (report , summary ))
625- .exceptionally (
626- ex -> {
627- LOG .warning (
628- String .format (
629- "License check failed, continuing without it: %s" ,
630- ex .getMessage ()));
631- return new ComponentAnalysisResult (report , null );
632- });
633- });
696+ var uri = resolveAnalysisUri (content );
697+ CompletableFuture <AnalysisReport > reportFuture =
698+ content .batch
699+ ? getBatchAnalysisReportForComponent (uri , content , exClientTraceId )
700+ : getAnalysisReportForComponent (uri , content , exClientTraceId );
701+ return reportFuture .thenCompose (
702+ report -> {
703+ if (!isLicenseCheckEnabled () || content .batch ) {
704+ return CompletableFuture .completedFuture (new ComponentAnalysisResult (report , null ));
705+ }
706+ return LicenseCheck .runLicenseCheck (this , provider , manifestPath , sbomJson , report )
707+ .thenApply (summary -> new ComponentAnalysisResult (report , summary ))
708+ .exceptionally (
709+ ex -> {
710+ LOG .warning (
711+ String .format (
712+ "License check failed, continuing without it: %s" , ex .getMessage ()));
713+ return new ComponentAnalysisResult (report , null );
714+ });
715+ });
634716 }
635717
636718 /**
0 commit comments