aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-core
diff options
context:
space:
mode:
authorAntoine Vinot <antoine.vinot@sonarsource.com>2022-11-08 12:11:52 +0100
committersonartech <sonartech@sonarsource.com>2022-11-15 20:02:59 +0000
commit7b6049377bbeb887442a038efbc119656ebb3a12 (patch)
treedad87ff49b0f771fc82154e0180c4804ce2936ba /sonar-core
parente71079a32e8938780c040d4c08aedd5591352b7e (diff)
downloadsonarqube-7b6049377bbeb887442a038efbc119656ebb3a12.tar.gz
sonarqube-7b6049377bbeb887442a038efbc119656ebb3a12.zip
SONAR-17560 Move existing sarif object to sonar-core
Diffstat (limited to 'sonar-core')
-rw-r--r--sonar-core/build.gradle1
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/ArtifactLocation.java34
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/CodeFlow.java26
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Driver.java50
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Location.java26
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/LocationWrapper.java25
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/PartialFingerprints.java21
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/PhysicalLocation.java32
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/PropertiesBag.java34
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Region.java77
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Result.java97
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Rule.java76
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Run.java54
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Sarif210.java47
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/SarifSerializer.java49
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/ThreadFlow.java26
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/Tool.java21
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/WrappedText.java43
-rw-r--r--sonar-core/src/main/java/org/sonar/core/sarif/package-info.java23
-rw-r--r--sonar-core/src/test/java/org/sonar/core/sarif/RuleTest.java52
-rw-r--r--sonar-core/src/test/java/org/sonar/core/sarif/Sarif210SerializationDeserializationTest.java34
-rw-r--r--sonar-core/src/test/java/org/sonar/core/sarif/SarifSerializerTest.java43
-rw-r--r--sonar-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker1
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/sarif/valid-sarif210.json107
24 files changed, 999 insertions, 0 deletions
diff --git a/sonar-core/build.gradle b/sonar-core/build.gradle
index ceacea76e64..63608062593 100644
--- a/sonar-core/build.gradle
+++ b/sonar-core/build.gradle
@@ -24,6 +24,7 @@ dependencies {
compile project(':sonar-plugin-api-impl')
compileOnly 'com.google.code.findbugs:jsr305'
+ compileOnly 'com.google.code.gson:gson'
testCompile 'com.tngtech.java:junit-dataprovider'
testCompile 'junit:junit'
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/ArtifactLocation.java b/sonar-core/src/main/java/org/sonar/core/sarif/ArtifactLocation.java
new file mode 100644
index 00000000000..b9839979c74
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/ArtifactLocation.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+
+public class ArtifactLocation {
+ private static final String URI_BASE_ID = "%SRCROOT";
+
+ @SerializedName("uri")
+ private final String uri;
+ @SerializedName("uriBaseId")
+ private final String uriBaseId;
+
+ private ArtifactLocation(String uriBaseId, String uri) {
+ this.uriBaseId = uriBaseId;
+ this.uri = uri;
+ }
+
+ public static ArtifactLocation of(String uri) {
+ return new ArtifactLocation(URI_BASE_ID, uri);
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public String getUriBaseId() {
+ return uriBaseId;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/CodeFlow.java b/sonar-core/src/main/java/org/sonar/core/sarif/CodeFlow.java
new file mode 100644
index 00000000000..0ee64717140
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/CodeFlow.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+public class CodeFlow {
+ @SerializedName("threadFlows")
+ private final List<ThreadFlow> threadFlows;
+
+ private CodeFlow(List<ThreadFlow> threadFlows) {
+ this.threadFlows = threadFlows;
+ }
+
+ public static CodeFlow of(List<ThreadFlow> threadFlows) {
+ return new CodeFlow(threadFlows);
+ }
+
+ public List<ThreadFlow> getThreadFlows() {
+ return threadFlows;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Driver.java b/sonar-core/src/main/java/org/sonar/core/sarif/Driver.java
new file mode 100644
index 00000000000..cbbbe3b32a3
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Driver.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.Set;
+
+public class Driver {
+ private static final String TOOL_NAME = "SonarQube";
+ private static final String ORGANIZATION_NAME = "SonarSource";
+
+ @SerializedName("name")
+ private final String name;
+ @SerializedName("organization")
+ private final String organization;
+ @SerializedName("semanticVersion")
+ private final String semanticVersion;
+ @SerializedName("rules")
+ private final Set<Rule> rules;
+
+ public Driver(String semanticVersion, Set<Rule> rules) {
+ this(TOOL_NAME, ORGANIZATION_NAME, semanticVersion, rules);
+ }
+
+ private Driver(String name, String organization, String semanticVersion, Set<Rule> rules) {
+ this.name = name;
+ this.organization = organization;
+ this.semanticVersion = semanticVersion;
+ this.rules = Set.copyOf(rules);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOrganization() {
+ return organization;
+ }
+
+ public String getSemanticVersion() {
+ return semanticVersion;
+ }
+
+ public Set<Rule> getRules() {
+ return rules;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Location.java b/sonar-core/src/main/java/org/sonar/core/sarif/Location.java
new file mode 100644
index 00000000000..c2a334c9bd9
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Location.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Location {
+ @SerializedName("physicalLocation")
+ private final PhysicalLocation physicalLocation;
+
+ private Location(PhysicalLocation physicalLocation) {
+ this.physicalLocation = physicalLocation;
+ }
+
+ public static Location of(PhysicalLocation physicalLocation) {
+ return new Location(physicalLocation);
+ }
+
+ public PhysicalLocation getPhysicalLocation() {
+ return physicalLocation;
+ }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/LocationWrapper.java b/sonar-core/src/main/java/org/sonar/core/sarif/LocationWrapper.java
new file mode 100644
index 00000000000..2c551204713
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/LocationWrapper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+
+public class LocationWrapper {
+ @SerializedName("location")
+ private final Location location;
+
+ private LocationWrapper(Location location) {
+ this.location = location;
+ }
+
+ public static LocationWrapper of(Location location) {
+ return new LocationWrapper(location);
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/PartialFingerprints.java b/sonar-core/src/main/java/org/sonar/core/sarif/PartialFingerprints.java
new file mode 100644
index 00000000000..67cbbdef1d6
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/PartialFingerprints.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+
+public class PartialFingerprints {
+ @SerializedName("primaryLocationLineHash")
+ private final String primaryLocationLineHash;
+
+ public PartialFingerprints(String primaryLocationLineHash) {
+ this.primaryLocationLineHash = primaryLocationLineHash;
+ }
+
+ public String getPrimaryLocationLineHash() {
+ return primaryLocationLineHash;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/PhysicalLocation.java b/sonar-core/src/main/java/org/sonar/core/sarif/PhysicalLocation.java
new file mode 100644
index 00000000000..c4c1e833944
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/PhysicalLocation.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+
+public class PhysicalLocation {
+ @SerializedName("artifactLocation")
+ private final ArtifactLocation artifactLocation;
+ @SerializedName("region")
+ private final Region region;
+
+ private PhysicalLocation(ArtifactLocation artifactLocation, Region region) {
+ this.artifactLocation = artifactLocation;
+ this.region = region;
+ }
+
+ public static PhysicalLocation of(ArtifactLocation artifactLocation, Region region) {
+ return new PhysicalLocation(artifactLocation, region);
+ }
+
+ public ArtifactLocation getArtifactLocation() {
+ return artifactLocation;
+ }
+
+ public Region getRegion() {
+ return region;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/PropertiesBag.java b/sonar-core/src/main/java/org/sonar/core/sarif/PropertiesBag.java
new file mode 100644
index 00000000000..1f44afc39ac
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/PropertiesBag.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.Set;
+
+public class PropertiesBag {
+ @SerializedName("tags")
+ private final Set<String> tags;
+ @SerializedName("security-severity")
+ private final String securitySeverity;
+
+ private PropertiesBag(String securitySeverity, Set<String> tags) {
+ this.tags = Set.copyOf(tags);
+ this.securitySeverity = securitySeverity;
+ }
+
+ public static PropertiesBag of(String securitySeverity, Set<String> tags) {
+ return new PropertiesBag(securitySeverity, tags);
+ }
+
+ public Set<String> getTags() {
+ return tags;
+ }
+
+ public String getSecuritySeverity() {
+ return securitySeverity;
+ }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Region.java b/sonar-core/src/main/java/org/sonar/core/sarif/Region.java
new file mode 100644
index 00000000000..8fd89fa4c41
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Region.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Region {
+ @SerializedName("startLine")
+ private final int startLine;
+ @SerializedName("endLine")
+ private final int endLine;
+ @SerializedName("startColumn")
+ private final int startColumn;
+ @SerializedName("endColumn")
+ private final int endColumn;
+
+ private Region(int startLine, int endLine, int startColumn, int endColumn) {
+ this.startLine = startLine;
+ this.endLine = endLine;
+ this.startColumn = startColumn;
+ this.endColumn = endColumn;
+ }
+
+ public static RegionBuilder builder() {
+ return new RegionBuilder();
+ }
+
+ public int getStartLine() {
+ return startLine;
+ }
+
+ public int getEndLine() {
+ return endLine;
+ }
+
+ public int getStartColumn() {
+ return startColumn;
+ }
+
+ public int getEndColumn() {
+ return endColumn;
+ }
+
+ public static final class RegionBuilder {
+ private int startLine;
+ private int endLine;
+ private int startColumn;
+ private int endColumn;
+
+ public RegionBuilder startLine(int startLine) {
+ this.startLine = startLine;
+ return this;
+ }
+
+ public RegionBuilder endLine(int endLine) {
+ this.endLine = endLine;
+ return this;
+ }
+
+ public RegionBuilder startColumn(int startColumn) {
+ this.startColumn = startColumn;
+ return this;
+ }
+
+ public RegionBuilder endColumn(int endColumn) {
+ this.endColumn = endColumn;
+ return this;
+ }
+
+ public Region build() {
+ return new Region(startLine, endLine, startColumn, endColumn);
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Result.java b/sonar-core/src/main/java/org/sonar/core/sarif/Result.java
new file mode 100644
index 00000000000..3815da4a480
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Result.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+import java.util.Set;
+import org.sonar.api.rule.RuleKey;
+
+public class Result {
+ @SerializedName("ruleId")
+ private final String ruleId;
+ @SerializedName("message")
+ private final WrappedText message;
+ @SerializedName("locations")
+ private final Set<Location> locations;
+ @SerializedName("partialFingerprints")
+ private final PartialFingerprints partialFingerprints;
+ @SerializedName("codeFlows")
+ private final List<CodeFlow> codeFlows;
+
+
+ private Result(RuleKey ruleKey, String message, Location location, String primaryLocationLineHash, List<CodeFlow> codeFlows) {
+ this.ruleId = ruleKey.toString();
+ this.message = WrappedText.of(message);
+ this.locations = Set.of(location);
+ this.partialFingerprints = new PartialFingerprints(primaryLocationLineHash);
+ this.codeFlows = List.copyOf(codeFlows);
+ }
+
+ public String getRuleId() {
+ return ruleId;
+ }
+
+ public WrappedText getMessage() {
+ return message;
+ }
+
+ public Set<Location> getLocations() {
+ return locations;
+ }
+
+ public PartialFingerprints getPartialFingerprints() {
+ return partialFingerprints;
+ }
+
+ public List<CodeFlow> getCodeFlows() {
+ return codeFlows;
+ }
+
+ public static ResultBuilder builder() {
+ return new ResultBuilder();
+ }
+
+ public static final class ResultBuilder {
+ private RuleKey ruleKey;
+ private String message;
+ private Location location;
+ private String hash;
+ private List<CodeFlow> codeFlows;
+
+ private ResultBuilder() {
+ }
+
+ public ResultBuilder ruleKey(RuleKey ruleKey) {
+ this.ruleKey = ruleKey;
+ return this;
+ }
+
+ public ResultBuilder message(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public ResultBuilder locations(Location location) {
+ this.location = location;
+ return this;
+ }
+
+ public ResultBuilder hash(String hash) {
+ this.hash = hash;
+ return this;
+ }
+
+ public ResultBuilder codeFlows(List<CodeFlow> codeFlows) {
+ this.codeFlows = codeFlows;
+ return this;
+ }
+
+ public Result build() {
+ return new Result(ruleKey, message, location, hash, codeFlows);
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Rule.java b/sonar-core/src/main/java/org/sonar/core/sarif/Rule.java
new file mode 100644
index 00000000000..ce234118159
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Rule.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.Objects;
+import org.sonar.api.rule.RuleKey;
+
+public class Rule {
+ @SerializedName("id")
+ private final String id;
+ @SerializedName("name")
+ private final String name;
+ @SerializedName("shortDescription")
+ private final WrappedText shortDescription;
+ @SerializedName("fullDescription")
+ private final WrappedText fullDescription;
+ @SerializedName("help")
+ private final WrappedText help;
+ @SerializedName("properties")
+ private final PropertiesBag properties;
+
+ public Rule(RuleKey ruleKey, String ruleName, String ruleDescription, PropertiesBag properties) {
+ id = ruleKey.toString();
+ name = ruleKey.toString();
+ shortDescription = WrappedText.of(ruleName);
+ fullDescription = WrappedText.of(ruleName);
+ help = WrappedText.of(ruleDescription);
+ this.properties = properties;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public WrappedText getShortDescription() {
+ return shortDescription;
+ }
+
+ public WrappedText getFullDescription() {
+ return fullDescription;
+ }
+
+ public WrappedText getHelp() {
+ return help;
+ }
+
+ public PropertiesBag getProperties() {
+ return properties;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Rule rule = (Rule) o;
+ return Objects.equals(id, rule.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Run.java b/sonar-core/src/main/java/org/sonar/core/sarif/Run.java
new file mode 100644
index 00000000000..ae8cc0a78e4
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Run.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gson.annotations.SerializedName;
+import java.util.Set;
+
+public class Run {
+
+ @VisibleForTesting
+ public static final String LANGUAGE_EN_US = "en-us";
+ @VisibleForTesting
+ public static final String COLUMN_KIND = "utf16CodeUnits";
+
+ @SerializedName("tool")
+ private final Tool tool;
+ @SerializedName("results")
+ private final Set<Result> results;
+ @SerializedName("language")
+ private final String language;
+ @SerializedName("columnKind")
+ private final String columnKind;
+
+ public Run(Tool tool, Set<Result> results) {
+ this(tool, results, LANGUAGE_EN_US, COLUMN_KIND);
+ }
+
+ private Run(Tool tool, Set<Result> results, String language, String columnKind) {
+ this.tool = tool;
+ this.results = Set.copyOf(results);
+ this.language = language;
+ this.columnKind = columnKind;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public String getColumnKind() {
+ return columnKind;
+ }
+
+ public Tool getTool() {
+ return tool;
+ }
+
+ public Set<Result> getResults() {
+ return results;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Sarif210.java b/sonar-core/src/main/java/org/sonar/core/sarif/Sarif210.java
new file mode 100644
index 00000000000..d19c386461f
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Sarif210.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gson.annotations.SerializedName;
+import java.util.Set;
+
+public class Sarif210 {
+
+ @VisibleForTesting
+ public static final String SARIF_SCHEMA_URL = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
+ @VisibleForTesting
+ public static final String SARIF_VERSION = "2.1.0";
+
+ @SerializedName("version")
+ private final String version;
+ @SerializedName("$schema")
+ private final String schema;
+ @SerializedName("runs")
+ private final Set<Run> runs;
+
+ public Sarif210(Run run) {
+ this(SARIF_SCHEMA_URL, SARIF_VERSION, run);
+ }
+
+ private Sarif210(String schema, String version, Run run) {
+ this.schema = schema;
+ this.version = version;
+ this.runs = Set.of(run);
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getSchema() {
+ return schema;
+ }
+
+ public Set<Run> getRuns() {
+ return runs;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/SarifSerializer.java b/sonar-core/src/main/java/org/sonar/core/sarif/SarifSerializer.java
new file mode 100644
index 00000000000..62c2452d896
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/SarifSerializer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gson.Gson;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Base64;
+import java.util.zip.GZIPOutputStream;
+import javax.inject.Inject;
+import org.sonar.api.ce.ComputeEngineSide;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+@ComputeEngineSide
+public class SarifSerializer {
+ private final Gson gson;
+
+ @Inject
+ public SarifSerializer() {
+ this(new Gson());
+ }
+
+ @VisibleForTesting
+ SarifSerializer(Gson gson) {
+ this.gson = gson;
+ }
+
+ public String serializeAndEncode(Sarif210 sarif210) {
+ String serializedSarif = gson.toJson(sarif210);
+ return compressToGzipAndEncodeBase64(serializedSarif);
+ }
+
+ private static String compressToGzipAndEncodeBase64(String input) {
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ GZIPOutputStream gzipStream = new GZIPOutputStream(outputStream)) {
+ gzipStream.write(input.getBytes(UTF_8));
+ gzipStream.finish();
+ return Base64.getEncoder().encodeToString(outputStream.toByteArray());
+ } catch (IOException e) {
+ throw new UncheckedIOException(String.format("Failed to compress and encode the input: %s", input), e);
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/ThreadFlow.java b/sonar-core/src/main/java/org/sonar/core/sarif/ThreadFlow.java
new file mode 100644
index 00000000000..45d75c0bdc0
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/ThreadFlow.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+public class ThreadFlow {
+ @SerializedName("locations")
+ private final List<LocationWrapper> locations;
+
+ private ThreadFlow(List<LocationWrapper> locations) {
+ this.locations = locations;
+ }
+
+ public static ThreadFlow of(List<LocationWrapper> locations) {
+ return new ThreadFlow(locations);
+ }
+
+ public List<LocationWrapper> getLocations() {
+ return locations;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/Tool.java b/sonar-core/src/main/java/org/sonar/core/sarif/Tool.java
new file mode 100644
index 00000000000..3990f8447f8
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/Tool.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Tool {
+ @SerializedName("driver")
+ private final Driver driver;
+
+ public Tool(Driver driver) {
+ this.driver = driver;
+ }
+
+ public Driver getDriver() {
+ return driver;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/WrappedText.java b/sonar-core/src/main/java/org/sonar/core/sarif/WrappedText.java
new file mode 100644
index 00000000000..ddf259342f6
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/WrappedText.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.Objects;
+
+public class WrappedText {
+ @SerializedName("text")
+ private final String text;
+
+ private WrappedText(String textToWrap) {
+ this.text = textToWrap;
+ }
+
+ public static WrappedText of(String textToWrap) {
+ return new WrappedText(textToWrap);
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ WrappedText that = (WrappedText) o;
+ return Objects.equals(text, that.text);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(text);
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/sarif/package-info.java b/sonar-core/src/main/java/org/sonar/core/sarif/package-info.java
new file mode 100644
index 00000000000..b5c75389dc6
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/sarif/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.core.sarif;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-core/src/test/java/org/sonar/core/sarif/RuleTest.java b/sonar-core/src/test/java/org/sonar/core/sarif/RuleTest.java
new file mode 100644
index 00000000000..e579f8c3571
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/sarif/RuleTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import java.util.Set;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.sarif.PropertiesBag;
+import org.sonar.core.sarif.Rule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class RuleTest {
+
+ @Test
+ public void equals_matchOnlyOnId() {
+ Rule rule1 = createRule("rep1", "rule1");
+ Rule rule1Bis = createRule("rep1", "rule1");
+ Rule rule2 = withRuleId(rule1, "rep1", "rule2");
+
+ assertThat(rule1).isEqualTo(rule1Bis).isNotEqualTo(rule2);
+ }
+
+ @Test
+ public void equals_notMatchWithNull(){
+ Rule rule1 = createRule("rep1", "rule2");
+
+ assertThat(rule1).isNotEqualTo(null);
+ }
+
+ @Test
+ public void equals_matchWithSameObject(){
+ Rule rule1 = createRule("rep5", "rule2");
+
+ assertThat(rule1).isEqualTo(rule1);
+ }
+
+ private static Rule withRuleId(Rule rule, String repoName, String ruleName) {
+ return new Rule(RuleKey.of(repoName, ruleName), rule.getName(), rule.getFullDescription().getText(), rule.getProperties());
+ }
+
+ private static Rule createRule(String repoName, String ruleName) {
+ return new Rule(RuleKey.of(repoName, ruleName), RandomStringUtils.randomAlphanumeric(5), RandomStringUtils.randomAlphanumeric(5),
+ PropertiesBag.of(RandomStringUtils.randomAlphanumeric(3), Set.of(RandomStringUtils.randomAlphanumeric(4))));
+ }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/sarif/Sarif210SerializationDeserializationTest.java b/sonar-core/src/test/java/org/sonar/core/sarif/Sarif210SerializationDeserializationTest.java
new file mode 100644
index 00000000000..ef40fe8dcd5
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/sarif/Sarif210SerializationDeserializationTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.sonar.core.sarif.Sarif210;
+import org.sonar.test.JsonAssert;
+
+import static java.util.Objects.requireNonNull;
+
+public class Sarif210SerializationDeserializationTest {
+
+ private static final String VALID_SARIF_210_FILE_JSON = "valid-sarif210.json";
+
+ @Test
+ public void verify_json_serialization_of_sarif210() throws IOException {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+ String expectedJson = IOUtils.toString(requireNonNull(getClass().getResource(VALID_SARIF_210_FILE_JSON)), StandardCharsets.UTF_8);
+ Sarif210 deserializedJson = gson.fromJson(expectedJson, Sarif210.class);
+ String reserializedJson = gson.toJson(deserializedJson);
+
+ JsonAssert.assertJson(reserializedJson).isSimilarTo(expectedJson);
+ }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/sarif/SarifSerializerTest.java b/sonar-core/src/test/java/org/sonar/core/sarif/SarifSerializerTest.java
new file mode 100644
index 00000000000..e239a5e859f
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/sarif/SarifSerializerTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017-2022 SonarSource SA
+ * All rights reserved
+ * mailto:info AT sonarsource DOT com
+ */
+package org.sonar.core.sarif;
+
+import com.google.gson.Gson;
+import org.sonar.core.sarif.Sarif210;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SarifSerializerTest {
+
+ private static final String SARIF_JSON = "{\"message\" : \"A sarif in json format as String.\"}";
+ private static final String SARIF_JSON_ENCODED = "H4sIAAAAAAAAAKtWyk0tLk5MT1VSsFJQclQoTizKTFPIzFPIKs7PU0jLL8pNLFFILFYILinKzEvXU6oFACgK7/YxAAAA";
+
+ @Mock
+ private Gson gson;
+
+ @InjectMocks
+ private SarifSerializer serializer;
+
+ @Test
+ public void serializeAndEncode_should_compressInGZipAndEncodeBase64() {
+ when(gson.toJson(any(Sarif210.class))).thenReturn(SARIF_JSON);
+ Sarif210 sarif210 = mock(Sarif210.class);
+
+ String encoded = serializer.serializeAndEncode(sarif210);
+
+ assertThat(encoded).isEqualTo(SARIF_JSON_ENCODED);
+ }
+
+}
diff --git a/sonar-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/sonar-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 00000000000..1f0955d450f
--- /dev/null
+++ b/sonar-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/sonar-core/src/test/resources/org/sonar/core/sarif/valid-sarif210.json b/sonar-core/src/test/resources/org/sonar/core/sarif/valid-sarif210.json
new file mode 100644
index 00000000000..7e0c2ef9340
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/sarif/valid-sarif210.json
@@ -0,0 +1,107 @@
+{
+ "version": "2.1.0",
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+ "runs": [
+ {
+ "tool": {
+ "driver": {
+ "name": "SonarQube",
+ "organization": "SonarSource",
+ "semanticVersion": "9.6",
+ "rules": [
+ {
+ "id": "java:S5132",
+ "name": "java:S5132",
+ "shortDescription": {
+ "text": "Make this final static field too."
+ },
+ "fullDescription": {
+ "text": "Make this final static field too."
+ },
+ "help": {
+ "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam hendrerit nisi sed sollicitudin pellentesque. Nunc posuere purus rhoncus pulvinar aliquam. Ut aliquet tristique nisl vitae volutpat. Nulla aliquet porttitor venenatis. Donec a dui et dui fringilla consectetur id nec massa. Aliquam erat volutpat. Sed ut dui ut lacus dictum fermentum vel tincidunt neque. Sed sed lacinia lectus. Duis sit amet sodales felis. Duis nunc eros, mattis at dui ac, convallis semper risus. In adipiscing ultrices tellus, in suscipit massa vehicula eu."
+ },
+ "properties": {
+ "tags": [
+ "tag1",
+ "tag2"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ "results": [
+ {
+ "ruleId": "java:S5132",
+ "message": {
+ "text": "this is the message"
+ },
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "www.google.com",
+ "uriBaseId": "%SRCROOT"
+ },
+ "region": {
+ "startLine": 11,
+ "endLine": 222,
+ "startColumn": 54,
+ "endColumn": 4
+ }
+ }
+ }
+ ],
+ "partialFingerprints": {
+ "primaryLocationLineHash": "thisISTHEHAS"
+ },
+ "codeFlows": [
+ {
+ "threadFlows": [
+ {
+ "locations": [
+ {
+ "location": {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "www.google.com",
+ "uriBaseId": "%SRCROOT"
+ },
+ "region": {
+ "startLine": 11,
+ "endLine": 222,
+ "startColumn": 54,
+ "endColumn": 4
+ }
+ }
+ }
+ },
+ {
+ "location": {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "www.google.com",
+ "uriBaseId": "%SRCROOT"
+ },
+ "region": {
+ "startLine": 22,
+ "endLine": 4323,
+ "startColumn": 545,
+ "endColumn": 4324
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "language": "en-us",
+ "columnKind": "utf16CodeUnits"
+ }
+ ]
+}