Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions src/main/java/gov/nasa/pds/validate/ValidateLauncher.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2019, California Institute of Technology ("Caltech").
// Copyright © 2026, California Institute of Technology ("Caltech").
// U.S. Government sponsorship acknowledged.
//
// All rights reserved.
Expand Down Expand Up @@ -125,6 +125,7 @@
import gov.nasa.pds.validate.report.JSONReport;
import gov.nasa.pds.validate.report.Report;
import gov.nasa.pds.validate.report.XmlReport;
import gov.nasa.pds.validate.report.StreamingJsonReport;
import gov.nasa.pds.validate.util.ToolInfo;
import gov.nasa.pds.validate.util.Utility;

Expand Down Expand Up @@ -1041,15 +1042,18 @@ public void setCatalogs(List<String> catalogs) {
/**
* Set the output style for the report.
*
* @param style 'sum' for a summary report, 'min' for a minimal report, and 'full' for a full
* report
* @throws Exception
* @param style 'full' for a full report, 'json' for a buffered JSON report,
* 'xml' for an XML report, or 'streaming-json' for a line-delimited
* JSON report (one object per product).
* @throws Exception If an invalid style is provided.
*/
public void setReportStyle(String style) throws Exception {
if (!style.equalsIgnoreCase("full") && !style.equalsIgnoreCase("json")
&& !style.equalsIgnoreCase("xml")) {
if (!style.equalsIgnoreCase("full") &&
!style.equalsIgnoreCase("json") &&
!style.equalsIgnoreCase("xml") &&
!style.equalsIgnoreCase("streaming-json")) {
throw new Exception("Invalid value entered for 's' flag. Value can only "
+ "be either 'full', 'json' or 'xml'");
+ "be 'full', 'json', 'xml', or 'streaming-json'");
}
this.reportStyle = style;
}
Expand Down Expand Up @@ -1339,13 +1343,16 @@ public void displayVersion() throws IOException {
* @throws IOException If an error occurred while setting up the report.
*/
public void setupReport(String cliArgs[]) throws IOException {
if (this.reportStyle.equals("full")) {
if (this.reportStyle.equalsIgnoreCase("full")) {
this.report = new FullReport();
} else if (this.reportStyle.equals("json")) {
} else if (this.reportStyle.equalsIgnoreCase("json")) {
this.report = new JSONReport();
} else if (this.reportStyle.equals("xml")) {
} else if (this.reportStyle.equalsIgnoreCase("xml")) {
this.report = new XmlReport();
} else if (this.reportStyle.equalsIgnoreCase("streaming-json")) {
this.report = new StreamingJsonReport();
}

report.setLevel(severity);
if (reportFile != null) {
report.setWriter(new PrintWriter(reportFile));
Expand Down
136 changes: 136 additions & 0 deletions src/main/java/gov/nasa/pds/validate/report/StreamingJsonReport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package gov.nasa.pds.validate.report;

import java.util.logging.Logger;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;

import gov.nasa.pds.tools.validate.ValidationProblem;
import gov.nasa.pds.validate.status.Status;

public class StreamingJsonReport extends Report {
private static final Logger LOG = Logger.getLogger(StreamingJsonReport.class.getName());
private static final Gson gson = new Gson();
private JsonObject currentProduct;
private JsonArray currentMessages;

@Override
protected void begin(Block block) {
if (block == Block.LABEL) {
this.currentProduct = new JsonObject();
this.currentMessages = new JsonArray();
}
}

@Override
protected void append(Status status, String lidvid, String target) {
this.currentProduct.addProperty("label", target);
this.currentProduct.addProperty("lidvid", lidvid);
this.currentProduct.addProperty("status", status.getName());
}

@Override
protected void append(ValidationProblem problem) {
JsonObject msg = new JsonObject();
msg.addProperty("severity", problem.getProblem().getSeverity().getName());
msg.addProperty("type", problem.getProblem().getType().getKey());
msg.addProperty("message", problem.getMessage());

if (problem.getLineNumber() != -1) {
msg.addProperty("line", problem.getLineNumber());
if (problem.getColumnNumber() != -1) {
msg.addProperty("column", problem.getColumnNumber());
}
}
this.currentMessages.add(msg);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both append methods asusme begin(Block.LABEL) has already initialized currentProduct and currentMessages. If the report lifecycle ever emits problems outside a label block, this could throw a NullPointerException. May be worth guarding or making the lifecycle assumption explicit?


@Override
protected void end(Block block) {
if (block == Block.LABEL) {
int fatals = 0;
int errors = 0;
int warnings = 0;
int infos = 0;

for (JsonElement element : this.currentMessages) {
String severity = element.getAsJsonObject().get("severity").getAsString();
if ("FATAL".equalsIgnoreCase(severity)) fatals++;
else if ("ERROR".equalsIgnoreCase(severity)) errors++;
else if ("WARNING".equalsIgnoreCase(severity)) warnings++;
else if ("INFO".equalsIgnoreCase(severity)) infos++;
}

this.currentProduct.addProperty("fatal", fatals);
this.currentProduct.addProperty("error", errors);
this.currentProduct.addProperty("warning", warnings);
this.currentProduct.addProperty("info", infos);

this.currentProduct.add("messages", this.currentMessages);

if (getWriter() != null) {
getWriter().println(gson.toJson(this.currentProduct));
getWriter().flush();
} else {
LOG.info(gson.toJson(this.currentProduct));

Check warning on line 76 in src/main/java/gov/nasa/pds/validate/report/StreamingJsonReport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Invoke method(s) only conditionally.

See more on https://sonarcloud.io/project/issues?id=NASA-PDS_validate&issues=AZ8k-qHooYG4JGBnxa-g&open=AZ8k-qHooYG4JGBnxa-g&pullRequest=1636

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if no writer is configured, LOG.info can add timestamps, log levels, class names, or other formatting cruft, which would make each line no longer NDJSON, right?

}

this.currentProduct = null;
this.currentMessages = null;
}
}

// --- Implement abstract overrides with explicit comments for SonarQube ---

@Override
protected void append(String title) {
// Intentionally empty: Title blocks are excluded from line-delimited JSON log output
}

@Override
protected void appendConfig(String label, String message, String value) {
// Intentionally empty: Global tool setup configurations are excluded from streaming records
}

@Override
protected void appendParam(String label, String message, String value) {
// Intentionally empty: Run parameter notifications are excluded from streaming records
}

@Override
protected void summarizeAddMessage(String msg, long count) {
// Intentionally empty: Aggregate counts are dropped in favor of line-item logs
}

@Override
protected void summarizeDepWarn(String msg) {
// Intentionally empty: Deprecation warnings are handled in global stdout rather than streaming targets
}

@Override
protected void summarizeRefs(int failed, int passed, int skipped, int total) {
// Intentionally empty: Context references are skipped to isolate file validation metrics
}

@Override
protected void summarizeTotals(int errors, int total, int warnings) {
// Intentionally empty: Replaced by target summary record type handled in summarizeProds
}

@Override
protected void summarizeProds(int failed, int passed, int skipped, int total) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method receives int skipped as a parameter but doesn't use it. Should it go into the summary?

summary.addProperty("skipped", skipped);

JsonObject summary = new JsonObject();
summary.addProperty("recordType", "summary");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I'm understanding: here, we add a recordType of summary to summaries but we don't do something similar to products, like

"recordType": "product"

or something?

summary.addProperty("passed", passed);
summary.addProperty("failed", failed);
summary.addProperty("total", total);

if (getWriter() != null) {
getWriter().println(gson.toJson(summary));
getWriter().flush();
} else {
LOG.info(gson.toJson(summary));

Check warning on line 133 in src/main/java/gov/nasa/pds/validate/report/StreamingJsonReport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Invoke method(s) only conditionally.

See more on https://sonarcloud.io/project/issues?id=NASA-PDS_validate&issues=AZ8k-qHooYG4JGBnxa-h&open=AZ8k-qHooYG4JGBnxa-h&pullRequest=1636
}
}
}
Loading