org.sonar.scanner.report.ReportPublisher.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.scanner.report.ReportPublisher.java

Source

/*
 * 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.report;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
import okhttp3.HttpUrl;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.ScannerSide;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Settings;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.TempFolder;
import org.sonar.api.utils.ZipUtils;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.scanner.analysis.DefaultAnalysisMode;
import org.sonar.scanner.bootstrap.ScannerWsClient;
import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonar.scanner.scan.ImmutableProjectReactor;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.WsCe;
import org.sonarqube.ws.client.HttpException;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsResponse;

import static org.sonar.core.util.FileUtils.deleteQuietly;

@ScannerSide
public class ReportPublisher implements Startable {

    private static final Logger LOG = Loggers.get(ReportPublisher.class);

    public static final String KEEP_REPORT_PROP_KEY = "sonar.batch.keepReport";
    public static final String VERBOSE_KEY = "sonar.verbose";
    public static final String METADATA_DUMP_FILENAME = "report-task.txt";

    private final Settings settings;
    private final ScannerWsClient wsClient;
    private final AnalysisContextReportPublisher contextPublisher;
    private final ImmutableProjectReactor projectReactor;
    private final DefaultAnalysisMode analysisMode;
    private final TempFolder temp;
    private final ReportPublisherStep[] publishers;
    private final Server server;

    private File reportDir;
    private ScannerReportWriter writer;

    public ReportPublisher(Settings settings, ScannerWsClient wsClient, Server server,
            AnalysisContextReportPublisher contextPublisher, ImmutableProjectReactor projectReactor,
            DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) {
        this.settings = settings;
        this.wsClient = wsClient;
        this.server = server;
        this.contextPublisher = contextPublisher;
        this.projectReactor = projectReactor;
        this.analysisMode = analysisMode;
        this.temp = temp;
        this.publishers = publishers;
    }

    @Override
    public void start() {
        reportDir = new File(projectReactor.getRoot().getWorkDir(), "batch-report");
        writer = new ScannerReportWriter(reportDir);
        contextPublisher.init(writer);

        if (!analysisMode.isIssues() && !analysisMode.isMediumTest()) {
            String publicUrl = server.getPublicRootUrl();
            if (HttpUrl.parse(publicUrl) == null) {
                throw MessageException.of("Failed to parse public URL set in SonarQube server: " + publicUrl);
            }
        }
    }

    @Override
    public void stop() {
        if (!shouldKeepReport()) {
            deleteQuietly(reportDir);
        }
    }

    public File getReportDir() {
        return reportDir;
    }

    public ScannerReportWriter getWriter() {
        return writer;
    }

    public void execute() {
        // If this is a issues mode analysis then we should not upload reports
        String taskId = null;
        if (!analysisMode.isIssues()) {
            File report = generateReportFile();
            if (shouldKeepReport()) {
                LOG.info("Analysis report generated in " + reportDir);
            }
            if (!analysisMode.isMediumTest()) {
                taskId = upload(report);
            }
        }
        logSuccess(taskId);
    }

    private boolean shouldKeepReport() {
        return settings.getBoolean(KEEP_REPORT_PROP_KEY) || settings.getBoolean(VERBOSE_KEY);
    }

    private File generateReportFile() {
        try {
            long startTime = System.currentTimeMillis();
            for (ReportPublisherStep publisher : publishers) {
                publisher.publish(writer);
            }
            long stopTime = System.currentTimeMillis();
            LOG.info("Analysis report generated in {}ms, dir size={}", stopTime - startTime,
                    FileUtils.byteCountToDisplaySize(FileUtils.sizeOfDirectory(reportDir)));

            startTime = System.currentTimeMillis();
            File reportZip = temp.newFile("batch-report", ".zip");
            ZipUtils.zipDir(reportDir, reportZip);
            stopTime = System.currentTimeMillis();
            LOG.info("Analysis reports compressed in {}ms, zip size={}", stopTime - startTime,
                    FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(reportZip)));
            return reportZip;
        } catch (IOException e) {
            throw new IllegalStateException("Unable to prepare analysis report", e);
        }
    }

    /**
     * Uploads the report file to server and returns the generated task id
     */
    @VisibleForTesting
    String upload(File report) {
        LOG.debug("Upload report");
        long startTime = System.currentTimeMillis();
        ProjectDefinition projectDefinition = projectReactor.getRoot();
        PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, report);
        PostRequest post = new PostRequest("api/ce/submit").setMediaType(MediaTypes.PROTOBUF)
                .setParam("organization", settings.getString(CoreProperties.PROJECT_ORGANIZATION_PROPERTY))
                .setParam("projectKey", projectDefinition.getKey())
                .setParam("projectName", projectDefinition.getOriginalName())
                .setParam("projectBranch", projectDefinition.getBranch()).setPart("report", filePart);

        WsResponse response;
        try {
            response = wsClient.call(post).failIfNotSuccessful();
        } catch (HttpException e) {
            throw MessageException.of(String.format("Failed to upload report - %d: %s", e.code(),
                    ScannerWsClient.tryParseAsJsonError(e.content())));
        }

        try (InputStream protobuf = response.contentStream()) {
            return WsCe.SubmitResponse.parser().parseFrom(protobuf).getTaskId();
        } catch (Exception e) {
            throw Throwables.propagate(e);
        } finally {
            long stopTime = System.currentTimeMillis();
            LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms");
        }
    }

    @VisibleForTesting
    void logSuccess(@Nullable String taskId) {
        if (taskId == null) {
            LOG.info("ANALYSIS SUCCESSFUL");
        } else {
            String publicUrl = server.getPublicRootUrl();
            HttpUrl httpUrl = HttpUrl.parse(publicUrl);

            Map<String, String> metadata = new LinkedHashMap<>();
            String effectiveKey = projectReactor.getRoot().getKeyWithBranch();
            if (settings.hasKey(CoreProperties.PROJECT_ORGANIZATION_PROPERTY)) {
                metadata.put("organization", settings.getString(CoreProperties.PROJECT_ORGANIZATION_PROPERTY));
            }
            metadata.put("projectKey", effectiveKey);
            metadata.put("serverUrl", publicUrl);
            metadata.put("serverVersion", server.getVersion());

            URL dashboardUrl = httpUrl.newBuilder().addPathSegment("dashboard").addPathSegment("index")
                    .addPathSegment(effectiveKey).build().url();
            metadata.put("dashboardUrl", dashboardUrl.toExternalForm());

            URL taskUrl = HttpUrl.parse(publicUrl).newBuilder().addPathSegment("api").addPathSegment("ce")
                    .addPathSegment("task").addQueryParameter("id", taskId).build().url();
            metadata.put("ceTaskId", taskId);
            metadata.put("ceTaskUrl", taskUrl.toExternalForm());

            LOG.info("ANALYSIS SUCCESSFUL, you can browse {}", dashboardUrl);
            LOG.info(
                    "Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report");
            LOG.info("More about the report processing at {}", taskUrl);

            dumpMetadata(metadata);
        }
    }

    private void dumpMetadata(Map<String, String> metadata) {
        Path file = projectReactor.getRoot().getWorkDir().toPath().resolve(METADATA_DUMP_FILENAME);
        try (Writer output = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
            for (Map.Entry<String, String> entry : metadata.entrySet()) {
                output.write(entry.getKey());
                output.write("=");
                output.write(entry.getValue());
                output.write("\n");
            }

            LOG.debug("Report metadata written to {}", file);
        } catch (IOException e) {
            throw new IllegalStateException("Unable to dump " + file, e);
        }
    }
}