3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.scanner.externalissue;
22 import java.nio.file.Path;
23 import javax.annotation.Nullable;
24 import org.apache.commons.lang.StringUtils;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27 import org.sonar.api.scanner.ScannerSide;
28 import org.sonar.core.documentation.DocumentationLinkGenerator;
31 public class ExternalIssueReportValidator {
32 private static final Logger LOGGER = LoggerFactory.getLogger(ExternalIssueReportValidator.class);
33 private static final String RULE_ID = "ruleId";
34 private static final String SEVERITY = "severity";
35 private static final String TYPE = "type";
36 private static final String DOCUMENTATION_SUFFIX = "/analyzing-source-code/importing-external-issues/generic-issue-import-format/";
37 private final DocumentationLinkGenerator documentationLinkGenerator;
39 ExternalIssueReportValidator(DocumentationLinkGenerator documentationLinkGenerator) {
40 this.documentationLinkGenerator = documentationLinkGenerator;
43 public void validate(ExternalIssueReport report, Path reportPath) {
44 if (report.rules != null) {
45 checkNoField(report.issues, "issues", reportPath);
46 validateRules(report.rules, reportPath);
47 } else if (report.issues != null) {
48 String documentationLink = documentationLinkGenerator.getDocumentationLink(DOCUMENTATION_SUFFIX);
49 LOGGER.warn("External issues were imported with a deprecated format which will be removed soon. " +
50 "Please switch to the newest format to fully benefit from Clean Code: {}", documentationLink);
51 validateIssues(report.issues, reportPath);
53 throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field 'rules'", reportPath));
57 private static void validateIssues(ExternalIssueReport.Issue[] issues, Path reportPath) {
58 for (ExternalIssueReport.Issue issue : issues) {
59 mandatoryField(issue.ruleId, RULE_ID, reportPath);
60 mandatoryField(issue.severity, SEVERITY, reportPath);
61 mandatoryField(issue.type, TYPE, reportPath);
62 mandatoryField(issue.engineId, "engineId", reportPath);
63 validateAlwaysRequiredIssueFields(issue, reportPath);
67 private static void validateRules(ExternalIssueReport.Rule[] rules, Path reportPath) {
68 for (ExternalIssueReport.Rule rule : rules) {
69 mandatoryField(rule.ruleId, RULE_ID, reportPath);
70 mandatoryField(rule.name, "name", reportPath);
71 mandatoryField(rule.engineId, "engineId", reportPath);
72 mandatoryField(rule.cleanCodeAttribute, "cleanCodeAttribute", reportPath);
73 mandatoryArray(rule.impacts, "impacts", reportPath);
74 validateIssuesAsPartOfARule(rule.issues, reportPath);
78 private static void validateIssuesAsPartOfARule(ExternalIssueReport.Issue[] issues, Path reportPath) {
79 for (ExternalIssueReport.Issue issue : issues) {
80 validateAlwaysRequiredIssueFields(issue, reportPath);
81 checkNoField(issue.severity, SEVERITY, reportPath);
82 checkNoField(issue.type, TYPE, reportPath);
83 checkNoField(issue.ruleId, RULE_ID, reportPath);
87 private static void checkNoField(@Nullable Object value, String fieldName, Path reportPath) {
89 throw new IllegalStateException(String.format("Deprecated '%s' field found in the following report: '%s'.", fieldName, reportPath));
93 private static void validateAlwaysRequiredIssueFields(ExternalIssueReport.Issue issue, Path reportPath) {
94 mandatoryField(issue.primaryLocation, "primaryLocation", reportPath);
95 mandatoryFieldPrimaryLocation(issue.primaryLocation.filePath, "filePath", reportPath);
96 mandatoryFieldPrimaryLocation(issue.primaryLocation.message, "message", reportPath);
98 if (issue.primaryLocation.textRange != null) {
99 mandatoryFieldPrimaryLocation(issue.primaryLocation.textRange.startLine, "startLine of the text range", reportPath);
102 if (issue.secondaryLocations != null) {
103 for (ExternalIssueReport.Location l : issue.secondaryLocations) {
104 mandatoryFieldSecondaryLocation(l.filePath, "filePath", reportPath);
105 mandatoryFieldSecondaryLocation(l.textRange, "textRange", reportPath);
106 mandatoryFieldSecondaryLocation(l.textRange.startLine, "startLine of the text range", reportPath);
111 private static void mandatoryFieldPrimaryLocation(@Nullable Object value, String fieldName, Path reportPath) {
113 throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field '%s' in the primary location of" +
114 " the issue.", reportPath, fieldName));
118 private static void mandatoryFieldSecondaryLocation(@Nullable Object value, String fieldName, Path reportPath) {
120 throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field '%s' in a secondary location of" +
121 " the issue.", reportPath, fieldName));
125 private static void mandatoryField(@Nullable Object value, String fieldName, Path reportPath) {
126 if (value == null || (value instanceof String && ((String) value).isEmpty())) {
127 throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field '%s'.", reportPath, fieldName));
131 private static void mandatoryArray(@Nullable Object[] value, String fieldName, Path reportPath) {
132 mandatoryField(value, fieldName, reportPath);
133 if (value.length == 0) {
134 throw new IllegalStateException(String.format("Failed to parse report '%s': mandatory array '%s' not populated.", reportPath,
139 private static void mandatoryField(@Nullable String value, String fieldName, Path reportPath) {
140 if (StringUtils.isBlank(value)) {
141 throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field '%s'.", reportPath, fieldName));