Created
October 5, 2018 10:34
-
-
Save szpq0i/0029e0ccccf7af57555eb7400fb8c477 to your computer and use it in GitHub Desktop.
UpsourcePlugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2000-2018 JetBrains s.r.o. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.jetbrains.upsource.sonar; | |
import org.sonar.api.batch.BatchSide; | |
import org.sonar.api.batch.InstantiationStrategy; | |
import org.sonar.api.batch.fs.InputFile; | |
import org.sonar.api.batch.postjob.PostJob; | |
import org.sonar.api.batch.postjob.PostJobContext; | |
import org.sonar.api.batch.postjob.PostJobDescriptor; | |
import org.sonar.api.batch.postjob.issue.PostJobIssue; | |
import org.sonar.api.batch.rule.Severity; | |
import org.sonar.api.config.Settings; | |
import org.sonar.api.utils.log.Logger; | |
import org.sonar.api.utils.log.Loggers; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.ls.DOMImplementationLS; | |
import org.w3c.dom.ls.LSSerializer; | |
import javax.xml.parsers.DocumentBuilderFactory; | |
import java.io.OutputStream; | |
import java.net.HttpURLConnection; | |
import java.net.URI; | |
import java.nio.file.Path; | |
import java.nio.file.Paths; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import java.io.FileWriter; | |
import java.io.IOException; | |
@BatchSide | |
@InstantiationStrategy(InstantiationStrategy.PER_TASK) | |
public class PushIssuesJob implements PostJob { | |
private static final Logger LOGGER = Loggers.get(PushIssuesJob.class); | |
private final Settings settings; | |
public PushIssuesJob(Settings settings) { | |
this.settings = settings; | |
} | |
@Override | |
public void describe(PostJobDescriptor descriptor) { | |
descriptor.name("Push issues to Upsource"); | |
} | |
@Override | |
public void execute(PostJobContext context) { | |
final String upsourceUrl = settings.getString(UpsourcePlugin.UPSOURCE_SERVER); | |
if (upsourceUrl == null || upsourceUrl.isEmpty()) { | |
LOGGER.error("Cannot post issues to Upsource: key " + UpsourcePlugin.UPSOURCE_SERVER + " is not defined"); | |
return; | |
} | |
final String upsourceToken = settings.getString(UpsourcePlugin.UPSOURCE_TOKEN); | |
final String upsourceProject = settings.getString(UpsourcePlugin.UPSOURCE_PROJECT); | |
if (upsourceProject == null || upsourceProject.isEmpty()) { | |
LOGGER.error("Cannot post issues to Upsource: key " + UpsourcePlugin.UPSOURCE_PROJECT + " is not defined"); | |
return; | |
} | |
final String upsourceRevision = settings.getString(UpsourcePlugin.UPSOURCE_REVISION); | |
if (upsourceRevision == null || upsourceRevision.isEmpty()) { | |
LOGGER.error("Cannot post issues to Upsource: key " + UpsourcePlugin.UPSOURCE_REVISION + " is not defined"); | |
return; | |
} | |
final String upsourceProjectPath = settings.getString(UpsourcePlugin.UPSOURCE_PROJECT_PATH); | |
final String sonarProjectKey = settings.getString("sonar.projectKey"); | |
final String sonarSources = settings.getString("sonar.sources"); | |
// issues are not accessible when the mode "issues" is not enabled | |
// with the scanner property "sonar.analysis.mode=issues" | |
try { | |
final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); | |
final Element root = (Element) document.appendChild(document.createElement("issues")); | |
root.setAttribute("version", "1"); | |
// all open issues | |
final AtomicInteger issuesCount = new AtomicInteger(0); | |
final Iterable<PostJobIssue> issues; | |
try { | |
issues = context.issues(); | |
} catch (Throwable ex) { | |
if (context.analysisMode().isIssues()) | |
throw ex; | |
LOGGER.info("Cannot load issues: " + ex.getMessage()); | |
return; | |
} | |
for (PostJobIssue issue : issues) { | |
if (issue.inputComponent() != null && issue.inputComponent().isFile()) { | |
final Integer issueLine = issue.line(); | |
final String message = issue.message(); | |
final Severity severity = issue.severity(); | |
final String filePath = fileKeyToFilePath((InputFile) issue.inputComponent(), upsourceProjectPath); | |
if (filePath == null) { | |
LOGGER.warn("Cannot parse input file '" + issue.inputComponent() + "'"); | |
continue; | |
} | |
final Element issueElement = (Element) root.appendChild(document.createElement("issue")); | |
appendChild(document, issueElement, "file", filePath); | |
appendChild(document, issueElement, "line", issueLine == null ? "0" : Integer.toString(issueLine)); | |
appendChild(document, issueElement, "message", removeAbsolutePaths(message)); | |
appendChild(document, issueElement, "severity", severity.name()); | |
issuesCount.incrementAndGet(); | |
} | |
} | |
if (issuesCount.get() == 0) { | |
LOGGER.warn("No issues found to post to Upsource"); | |
return; | |
} | |
final String result = documentToString(document); | |
final String upsourceInspectionsPath = settings.getString(UpsourcePlugin.UPSOURCE_INSPECTIONS_PATH); | |
if (upsourceInspectionsPath != null) { | |
String fileName = String.format("%s/results-%s.xml", upsourceInspectionsPath, upsourceRevision); | |
try (FileWriter writer = new FileWriter(fileName)) { | |
writer.write(result); | |
LOGGER.info("Saving inspections to: " + fileName); | |
} catch (IOException e) { | |
LOGGER.error("An error occurred while saving inspection results", e); | |
} | |
} | |
final URI uri = URI.create(suffix(upsourceUrl, "/") + "~inspections/" + upsourceProject + "/" + upsourceRevision + "/sonar"); | |
try { | |
LOGGER.info("Posting " + issuesCount.get() + " issues to " + uri.toString()); | |
HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection(); | |
conn.setDoOutput(true); | |
conn.setRequestMethod("POST"); | |
if (upsourceToken != null && !upsourceToken.isEmpty()) { | |
conn.setRequestProperty("Authorization", "Basic " + upsourceToken); | |
} | |
conn.setRequestProperty("Content-Type", "application/xml; charset=UTF-8"); | |
try { | |
conn.connect(); | |
try (OutputStream os = conn.getOutputStream()) { | |
os.write(result.getBytes()); | |
} | |
final int responseCode = conn.getResponseCode(); | |
LOGGER.info("Posted " + issuesCount.get() + " issues to " + uri.toString() + ". Response code: " + responseCode); | |
} finally { | |
conn.disconnect(); | |
} | |
} catch (Throwable e) { | |
throw new Error("Posting " + issuesCount.get() + " issues to " + uri.toString(), e); | |
} | |
} catch (Throwable e) { | |
LOGGER.error("Posting issues to Upsource", e); | |
} | |
} | |
private static String removeAbsolutePaths(String message) { | |
if (message == null) | |
return ""; | |
String currentDirectory = Paths.get(System.getProperty("user.dir")).toAbsolutePath().toString(); | |
if (message.contains(currentDirectory)) { | |
StringBuilder builder = new StringBuilder(message); | |
int index; | |
while ((index = builder.indexOf(currentDirectory)) >= 0) | |
builder.replace(index, index + currentDirectory.length(), ""); | |
return builder.toString(); | |
} | |
return message; | |
} | |
private static String suffix(String str, String suffix) { | |
return str.endsWith(suffix) ? str : (str + suffix); | |
} | |
private static String prefix(String prefix, String str) { | |
return str.startsWith(prefix) ? str : (prefix + str); | |
} | |
private static String toVcsPath(final String projectPath, final String filePath) { | |
if (projectPath == null || projectPath.isEmpty()) { | |
return prefix("/", filePath); | |
} | |
return prefix("/", suffix(projectPath, "/")) + prefix("/", filePath).substring(1); | |
} | |
private static String fileKeyToFilePath(final InputFile file, final String projectPath) { | |
Path currentDirectory = Paths.get(System.getProperty("user.dir")); | |
Path relativeFilePath = currentDirectory.relativize(file.path()); | |
return toVcsPath(projectPath, relativeFilePath.toString().replace('\\', '/')); | |
} | |
private static void appendChild(final Document document, final Element root, final String name, final String value) { | |
final Node node = root.appendChild(document.createElement(name)); | |
node.appendChild(document.createTextNode(value)); | |
} | |
private static String documentToString(final Document document) { | |
final DOMImplementationLS domImplementation = (DOMImplementationLS) document.getImplementation(); | |
final LSSerializer lsSerializer = domImplementation.createLSSerializer(); | |
lsSerializer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); | |
lsSerializer.getDomConfig().setParameter("xml-declaration", Boolean.FALSE); | |
lsSerializer.setNewLine("\n"); | |
return lsSerializer.writeToString(document); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2000-2018 JetBrains s.r.o. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.jetbrains.upsource.sonar; | |
import org.sonar.api.*; | |
@Properties({ | |
@Property( | |
key = UpsourcePlugin.UPSOURCE_SERVER, | |
defaultValue = "", | |
name = "Upsource server URL", | |
description = "", | |
global = true), | |
@Property( | |
key = UpsourcePlugin.UPSOURCE_TOKEN, | |
name = "Upsource project integration token", | |
description = "", | |
global = false, | |
type = PropertyType.PASSWORD), | |
@Property( | |
key = UpsourcePlugin.UPSOURCE_PROJECT, | |
name = "Upsource project ID", | |
description = "", | |
project = true, | |
global = false), | |
@Property( | |
key = UpsourcePlugin.UPSOURCE_PROJECT_PATH, | |
name = "Path to the project in version control (if not in the root)", | |
description = "", | |
project = true, | |
global = false), | |
@Property( | |
key = UpsourcePlugin.UPSOURCE_REVISION, | |
name = "VCS revision ID", | |
description = "", | |
project = true, | |
module = false, | |
global = false), | |
@Property( | |
key = UpsourcePlugin.UPSOURCE_INSPECTIONS_PATH, | |
name = "Path to directory with inspections file (intended to be used for debug purposes)", | |
description = "", | |
project = true, | |
global = false)}) | |
public class UpsourcePlugin implements Plugin { | |
public static final String UPSOURCE_SERVER = "sonar.upsource.url"; | |
public static final String UPSOURCE_PROJECT = "sonar.upsource.project"; | |
public static final String UPSOURCE_PROJECT_PATH = "sonar.upsource.projectpath"; | |
public static final String UPSOURCE_REVISION = "sonar.upsource.revision"; | |
public static final String UPSOURCE_TOKEN = "sonar.upsource.token"; | |
public static final String UPSOURCE_INSPECTIONS_PATH = "sonar.upsource.inspectionspath"; | |
@Override | |
public void define(Context context) { | |
context.addExtension(PushIssuesJob.class); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment