2828import java .util .concurrent .TimeUnit ;
2929import java .util .concurrent .TimeoutException ;
3030import java .util .stream .Collectors ;
31- import java .util .stream .Stream ;
3231
3332import org .slf4j .Logger ;
3433import org .slf4j .LoggerFactory ;
4443import com .fortify .cli .aviator .fpr .filter .Filter ;
4544import com .fortify .cli .aviator .fpr .filter .FilterSet ;
4645import com .fortify .cli .aviator .fpr .filter .FolderDefinition ;
47- import com .fortify .cli .aviator .fpr .filter .SearchTree ;
4846import com .fortify .cli .aviator .fpr .filter .TagDefinition ;
47+ import com .fortify .cli .aviator .fpr .filter .VulnerabilityFilterer ;
4948import com .fortify .cli .aviator .fpr .model .AuditIssue ;
5049import com .fortify .cli .aviator .fpr .model .FPRInfo ;
5150import com .fortify .cli .aviator .fpr .processor .AuditProcessor ;
@@ -279,106 +278,69 @@ private boolean isAudited(UserPrompt userPrompt) {
279278 }
280279 return false ;
281280 }
281+
282282 private List <Vulnerability > filterVulnerabilities (List <Vulnerability > allVulnerabilities , FilterSet fs ) {
283283 List <String > targetFolderNames = filterSelection .getTargetFolderNames ();
284284
285285 List <Filter > folderFilters = fs .getFilters ().stream ()
286- .filter (f -> "setFolder" .equalsIgnoreCase (f .getAction ())).collect (Collectors .toList ());
286+ .filter (f -> "setFolder" .equalsIgnoreCase (f .getAction ()))
287+ .collect (Collectors .toList ());
288+
287289 List <Filter > hideFilters = fs .getFilters ().stream ()
288- .filter (f -> "hide" .equalsIgnoreCase (f .getAction ())).collect (Collectors .toList ());
289-
290- Map <Filter , SearchTree > parsedQueries = new HashMap <>();
291- // Use a stream to populate the map
292- Stream .concat (folderFilters .stream (), hideFilters .stream ()).forEach (f -> {
293- try {
294- parsedQueries .put (f , com .fortify .cli .aviator .fpr .filter .engine .FilterParser .parse (f .getQuery ()));
295- } catch (Exception e ) {
296- LOG .error ("Failed to parse filter query: '{}'. This filter will be skipped." , f .getQuery (), e );
297- parsedQueries .put (f , new com .fortify .cli .aviator .fpr .filter .SearchTree (null ));
298- }
299- });
290+ .filter (f -> "hide" .equalsIgnoreCase (f .getAction ()))
291+ .collect (Collectors .toList ());
300292
301293 Map <String , List <Vulnerability >> folderContents = new HashMap <>();
302- for (Vulnerability vuln : allVulnerabilities ) {
294+
295+ // 1. Process "setFolder" filters
296+ if (folderFilters .isEmpty ()) {
297+ folderContents .put ("Default" , new ArrayList <>(allVulnerabilities ));
298+ } else {
303299 for (Filter folderFilter : folderFilters ) {
304- if (com .fortify .cli .aviator .fpr .filter .engine .VulnerabilityEvaluator .evaluate (parsedQueries .get (folderFilter ), vuln )) {
305- folderContents .computeIfAbsent (folderFilter .getActionParam (), k -> new ArrayList <>()).add (vuln );
306- break ;
307- }
300+ List <Vulnerability > matches = VulnerabilityFilterer .filter (allVulnerabilities , folderFilter .getQuery ());
301+ folderContents .computeIfAbsent (folderFilter .getActionParam (), k -> new ArrayList <>()).addAll (matches );
308302 }
309303 }
310304
311- folderContents .values ().forEach (vulnList ->
312- vulnList .removeIf (vuln -> hideFilters .stream ().anyMatch (
313- hideFilter -> com .fortify .cli .aviator .fpr .filter .engine .VulnerabilityEvaluator .evaluate (parsedQueries .get (hideFilter ), vuln )
314- ))
315- );
305+ // 2. Process "hide" filters
306+ for (Filter hideFilter : hideFilters ) {
307+ for (List <Vulnerability > folderList : folderContents .values ()) {
308+ if (folderList .isEmpty ()) continue ;
316309
310+ List <Vulnerability > toHide = VulnerabilityFilterer .filter (folderList , hideFilter .getQuery ());
311+ if (!toHide .isEmpty ()) {
312+ folderList .removeAll (toHide );
313+ }
314+ }
315+ }
316+
317+ // 3. Selection Logic (Flatten all or select specific folders)
317318 if (!filterSelection .isFilteringByFolder ()) {
318- List <Vulnerability > result = folderContents .values ().stream ().flatMap (List ::stream ).collect (Collectors .toList ());
319+ List <Vulnerability > result = folderContents .values ().stream ()
320+ .flatMap (List ::stream )
321+ .distinct ()
322+ .collect (Collectors .toList ());
319323 logger .info ("FilterSet '{}' applied. {} of {} total vulnerabilities remain." , fs .getTitle (), result .size (), allVulnerabilities .size ());
320324 return result ;
321325 } else {
322- // This logic correctly finds folder IDs based on user-provided names.
323326 Set <String > targetFolderIds = fs .getFolderDefinitions ().stream ()
324- .filter (fd -> targetFolderNames .stream ().anyMatch (name -> name .equalsIgnoreCase (fd .getName ())))
325- .map (FolderDefinition ::getId )
326- .collect (Collectors .toSet ());
327+ .filter (fd -> targetFolderNames .stream ().anyMatch (name -> name .equalsIgnoreCase (fd .getName ())))
328+ .map (FolderDefinition ::getId )
329+ .collect (Collectors .toSet ());
327330
328331 if (targetFolderIds .isEmpty ()) {
329332 String available = fs .getFolderDefinitions ().stream ().map (FolderDefinition ::getName ).collect (Collectors .joining ("', '" , "'" , "'" ));
330333 throw new AviatorSimpleException ("Folder(s) not found in FilterSet '" +fs .getTitle ()+"'. Available folders: " +available );
331334 }
332335
333336 List <Vulnerability > result = folderContents .entrySet ().stream ()
334- .filter (entry -> targetFolderIds .contains (entry .getKey ()))
335- .flatMap (entry -> entry .getValue ().stream ())
336- .collect (Collectors .toList ());
337+ .filter (entry -> targetFolderIds .contains (entry .getKey ()))
338+ .flatMap (entry -> entry .getValue ().stream ())
339+ .distinct ()
340+ .collect (Collectors .toList ());
337341
338342 logger .info ("Filtered by folder(s) '{}'. {} vulnerabilities remain." , targetFolderNames , result .size ());
339343 return result ;
340344 }
341345 }
342-
343- private void updateSkippedIssue (UserPrompt userPrompt , String reasonTemplate , int issuesInCategory , int totalIssues ) {
344- if (userPrompt == null || userPrompt .getIssueData () == null ) {
345- LOG .error ("Cannot update skipped issue status for null userPrompt or issue data." );
346- return ;
347- }
348- String instanceId = userPrompt .getIssueData ().getInstanceID ();
349- AuditIssue auditIssue = auditIssueMap .get (instanceId );
350-
351- String comment = reasonTemplate
352- .replace ("{issues_new_in_category}" , String .valueOf (issuesInCategory ))
353- .replace ("{MAX_PER_CATEGORY}" , String .valueOf (MAX_PER_CATEGORY ))
354- .replace ("{issues_new_total}" , String .valueOf (totalIssues ))
355- .replace ("{MAX_TOTAL}" , String .valueOf (MAX_TOTAL ));
356-
357- if (auditIssue != null ) {
358- LOG .debug ("Updating existing audit entry for skipped issue: {}" , instanceId );
359- try {
360- auditProcessor .addCommentToIssueXml (instanceId , comment , Constants .USER_NAME );
361- } catch (Exception e ) {
362- LOG .error ("Failed to add comment to XML for skipped issue {}: {}" , instanceId , e .getMessage ());
363- }
364-
365- auditProcessor .updateIssueTag (auditIssue , Constants .AVIATOR_STATUS_TAG_ID , Constants .PROCESSED_BY_AVIATOR );
366-
367- Map <String , String > tags = auditIssue .getTags ();
368- String currentAnalysisStatus = tags .get (Constants .ANALYSIS_TAG_ID );
369- if (currentAnalysisStatus == null || currentAnalysisStatus .isEmpty () || "Not Set" .equalsIgnoreCase (currentAnalysisStatus )) {
370- auditProcessor .updateIssueTag (auditIssue , Constants .ANALYSIS_TAG_ID , Constants .PENDING_REVIEW );
371- LOG .debug ("Set Analysis tag to Pending Review for skipped issue: {}" , instanceId );
372- } else {
373- LOG .debug ("Skipped issue {} already has Analysis status '{}', not changing." , instanceId , currentAnalysisStatus );
374- }
375-
376- } else {
377- try {
378- auditProcessor .addSkippedIssueElement (instanceId , comment );
379- } catch (Exception e ) {
380- LOG .error ("Failed to add skipped issue element to audit.xml for {}: {}" , instanceId , e .getMessage ());
381- }
382- }
383- }
384- }
346+ }
0 commit comments