package pl.touk.sputnik.processor.sonar; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.Iterator; import java.util.Map; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import pl.touk.sputnik.review.ReviewResult; import pl.touk.sputnik.review.Severity; import pl.touk.sputnik.review.Violation; import com.google.common.base.Strings; import com.google.common.collect.Maps; /** * Parses a json file produced by a run of Sonar. */ @Slf4j @AllArgsConstructor class SonarResultParser { private final File resultFile; /** * A component in the result file is identified by a key. * It may reference another component by its key (called moduleKey). */ @AllArgsConstructor private static class Component { public String path; public String moduleKey; } /** * Parses the file and returns all the issues as a ReviewResult. */ public ReviewResult parseResults() throws IOException { ReviewResult result = new ReviewResult(); ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(new FileReader(resultFile)); JsonNode issues = rootNode.path("issues"); Iterator<JsonNode> issuesIterator = issues.iterator(); Map<String, Component> components = getComponents(rootNode.path("components")); while (issuesIterator.hasNext()) { JsonNode issue = issuesIterator.next(); boolean isNew = issue.path("isNew").asBoolean(); if (!isNew) { log.debug("Skipping already indexed issue: {}", issue.toString()); continue; } JsonNode lineNode = issue.path("line"); if (lineNode.isMissingNode()) { log.debug("Skipping an issue with no line information: {}", issue.toString()); continue; } int line = lineNode.asInt(); String message = issue.path("message").asText(); String file = getIssueFilePath(issue.path("component").asText(), components); String severity = issue.path("severity").asText(); String rule = issue.path("rule").asText(); result.add(new Violation(file, line, String.format("%s (Rule: %s)", message, rule), getSeverity(severity))); } return result; } /** * Converts a Sonar severity to a Sputnik severity. * @param severityName severity to convert. */ static Severity getSeverity(String severityName) { switch (severityName) { case "BLOCKER": case "CRITICAL": case "MAJOR": return Severity.ERROR; case "MINOR": return Severity.WARNING; case "INFO": return Severity.INFO; default: log.warn("Unknown severity: " + severityName); } return Severity.WARNING; } /** * Extracts all the components from the json data. */ private Map<String, Component> getComponents(JsonNode componentsNode) { Iterator<JsonNode> it = componentsNode.iterator(); Map<String, Component> components = Maps.newHashMap(); while(it.hasNext()){ JsonNode componentNode = it.next(); JsonNode pathNode = componentNode.path("path"); JsonNode moduleNode = componentNode.path("moduleKey"); components.put(componentNode.path("key").asText(), new Component(pathNode.asText(), moduleNode.isMissingNode()?null:moduleNode.asText())); } return components; } /** * Returns the path of the file linked to an issue created by Sonar. * The path is relative to the folder where Sonar has been run. * * @param issueComponent "component" field in an issue. * @param components information about all components. */ private String getIssueFilePath(String issueComponent, Map<String, Component> components) { Component comp = components.get(issueComponent); String file = comp.path; if (!Strings.isNullOrEmpty(comp.moduleKey)) { String theKey = comp.moduleKey; while (!theKey.isEmpty()) { Component theChildComp = components.get(theKey); int p = theKey.lastIndexOf(":"); if (p > 0) { theKey = theKey.substring(0, p); } else { theKey = ""; } if (theChildComp != null && !Strings.isNullOrEmpty(theChildComp.path)) { file = theChildComp.path + '/' + file; } } } return file; } }