/* * SonarQube * Copyright (C) 2009-2017 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. */ package org.sonar.scanner.scan.report; import com.google.common.collect.Maps; import freemarker.template.Template; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.Properties; import org.sonar.api.Property; import org.sonar.api.PropertyType; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.config.Settings; @Properties({ @Property(key = HtmlReport.HTML_REPORT_ENABLED_KEY, defaultValue = "false", name = "Enable HTML report", description = "Set this to true to generate an HTML report", type = PropertyType.BOOLEAN), @Property(key = HtmlReport.HTML_REPORT_LOCATION_KEY, defaultValue = HtmlReport.HTML_REPORT_LOCATION_DEFAULT, name = "HTML Report location", description = "Location of the generated report. Can be absolute or relative to working directory", project = false, global = false, type = PropertyType.STRING), @Property(key = HtmlReport.HTML_REPORT_NAME_KEY, defaultValue = HtmlReport.HTML_REPORT_NAME_DEFAULT, name = "HTML Report name", description = "Name of the generated report. Will be suffixed by .html or -light.html", project = false, global = false, type = PropertyType.STRING), @Property(key = HtmlReport.HTML_REPORT_LIGHTMODE_ONLY, defaultValue = "false", name = "Html report in light mode only", description = "Set this to true to only generate the new issues report (light report)", project = true, type = PropertyType.BOOLEAN)}) public class HtmlReport implements Reporter { private static final Logger LOG = LoggerFactory.getLogger(HtmlReport.class); public static final String HTML_REPORT_ENABLED_KEY = "sonar.issuesReport.html.enable"; public static final String HTML_REPORT_LOCATION_KEY = "sonar.issuesReport.html.location"; public static final String HTML_REPORT_LOCATION_DEFAULT = "issues-report"; public static final String HTML_REPORT_NAME_KEY = "sonar.issuesReport.html.name"; public static final String HTML_REPORT_NAME_DEFAULT = "issues-report"; public static final String HTML_REPORT_LIGHTMODE_ONLY = "sonar.issuesReport.lightModeOnly"; private final Settings settings; private final FileSystem fs; private final IssuesReportBuilder builder; private final SourceProvider sourceProvider; private final RuleNameProvider ruleNameProvider; public HtmlReport(Settings settings, FileSystem fs, IssuesReportBuilder builder, SourceProvider sourceProvider, RuleNameProvider ruleNameProvider) { this.settings = settings; this.fs = fs; this.builder = builder; this.sourceProvider = sourceProvider; this.ruleNameProvider = ruleNameProvider; } @Override public void execute() { if (settings.getBoolean(HTML_REPORT_ENABLED_KEY)) { LOG.warn("HTML report is deprecated. Use SonarLint CLI to have local reports of issues"); IssuesReport report = builder.buildReport(); print(report); } } public void print(IssuesReport report) { File reportFileDir = getReportFileDir(); String reportName = settings.getString(HTML_REPORT_NAME_KEY); if (!isLightModeOnly()) { File reportFile = new File(reportFileDir, reportName + ".html"); LOG.debug("Generating HTML Report to: " + reportFile.getAbsolutePath()); writeToFile(report, reportFile, true); LOG.info("HTML Issues Report generated: " + reportFile.getAbsolutePath()); } File lightReportFile = new File(reportFileDir, reportName + "-light.html"); LOG.debug("Generating Light HTML Report to: " + lightReportFile.getAbsolutePath()); writeToFile(report, lightReportFile, false); LOG.info("Light HTML Issues Report generated: " + lightReportFile.getAbsolutePath()); try { copyDependencies(reportFileDir); } catch (Exception e) { throw new IllegalStateException("Fail to copy HTML report resources to: " + reportFileDir, e); } } private File getReportFileDir() { String reportFileDirStr = settings.getString(HTML_REPORT_LOCATION_KEY); File reportFileDir = new File(reportFileDirStr); if (!reportFileDir.isAbsolute()) { reportFileDir = new File(fs.workDir(), reportFileDirStr); } if (StringUtils.endsWith(reportFileDirStr, ".html")) { LOG.warn("{} should indicate a directory. Using parent folder.", HTML_REPORT_LOCATION_KEY); reportFileDir = reportFileDir.getParentFile(); } try { FileUtils.forceMkdir(reportFileDir); } catch (IOException e) { throw new IllegalStateException("Fail to create the directory " + reportFileDirStr, e); } return reportFileDir; } public void writeToFile(IssuesReport report, File toFile, boolean complete) { try { freemarker.log.Logger.selectLoggerLibrary(freemarker.log.Logger.LIBRARY_NONE); freemarker.template.Configuration cfg = new freemarker.template.Configuration(); cfg.setClassForTemplateLoading(HtmlReport.class, ""); Map<String, Object> root = Maps.newHashMap(); root.put("report", report); root.put("ruleNameProvider", ruleNameProvider); root.put("sourceProvider", sourceProvider); root.put("complete", complete); Template template = cfg.getTemplate("issuesreport.ftl"); try (FileOutputStream fos = new FileOutputStream(toFile); Writer writer = new OutputStreamWriter(fos, fs.encoding())) { template.process(root, writer); writer.flush(); } } catch (Exception e) { throw new IllegalStateException("Fail to generate HTML Issues Report to: " + toFile, e); } } private void copyDependencies(File toDir) throws IOException { File target = new File(toDir, "issuesreport_files"); FileUtils.forceMkdir(target); // I don't know how to extract a directory from classpath, that's why an exhaustive list of files // is provided here : copyDependency(target, "sonar.eot"); copyDependency(target, "sonar.svg"); copyDependency(target, "sonar.ttf"); copyDependency(target, "sonar.woff"); copyDependency(target, "favicon.ico"); copyDependency(target, "PRJ.png"); copyDependency(target, "DIR.png"); copyDependency(target, "FIL.png"); copyDependency(target, "jquery.min.js"); copyDependency(target, "sep12.png"); copyDependency(target, "sonar.css"); copyDependency(target, "sonarqube-24x100.png"); } private void copyDependency(File target, String filename) { try (InputStream input = getClass().getResourceAsStream("/org/sonar/scanner/scan/report/issuesreport_files/" + filename); OutputStream output = new FileOutputStream(new File(target, filename))) { IOUtils.copy(input, output); } catch (IOException e) { throw new IllegalStateException("Fail to copy file " + filename + " to " + target, e); } } public boolean isLightModeOnly() { return settings.getBoolean(HTML_REPORT_LIGHTMODE_ONLY); } }