package org.sasanlabs.benchmark.service; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.sasanlabs.benchmark.model.ExpectedIssue; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; /** * Loads SAST ground truth from a CSV file with header columns {@code CWE}, {@code Vulnerability * Type}, {@code File}, {@code Line}, {@code Number of Sources}. The default path points at {@code * scanner/sast/expectedIssues.csv} on the classpath; a filesystem path (relative to the working * directory, or absolute) is still accepted, and any path may be overridden via the {@code * benchmark.sast.ground-truth.path} property. * *
Rows that fail to parse (missing columns, non-integer line / sources) are logged and skipped — * one bad row should not abort an entire benchmark run. * *
The CSV is parsed once and cached, so repeated reads (the benchmark flow, and the {@code
* /scanner/sast} endpoint) do not hit disk or re-parse on every call. The cache is warmed at
* startup, so a file that cannot be read aborts the context rather than failing the first request,
* the same way {@code EmbeddedLDAPConfig} treats its own startup work.
*/
@Component
public class CsvExpectedIssuesProvider implements IExpectedIssuesProvider {
private static final Logger LOGGER = LogManager.getLogger(CsvExpectedIssuesProvider.class);
private static final String CLASSPATH_PREFIX = "classpath:";
private static final String COL_CWE = "CWE";
private static final String COL_TYPE = "Vulnerability Type";
private static final String COL_FILE = "File";
private static final String COL_LINE = "Line";
private static final String COL_SOURCES = "Number of Sources";
private static final CSVFormat FORMAT =
CSVFormat.DEFAULT
.builder()
.setHeader()
.setSkipHeaderRecord(true)
.setIgnoreEmptyLines(true)
.setTrim(true)
.build();
private final String csvPath;
private volatile List