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

Java tutorial

Introduction

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

Source

/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact 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.batch.report;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.io.Files;
import com.squareup.okhttp.HttpUrl;
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.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Settings;
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.batch.analysis.DefaultAnalysisMode;
import org.sonar.batch.bootstrap.BatchWsClient;
import org.sonar.batch.scan.ImmutableProjectReactor;
import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.WsCe;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsResponse;

import static org.apache.commons.lang.StringUtils.trimToEmpty;
import static org.sonar.core.util.FileUtils.deleteQuietly;

@BatchSide
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 BatchWsClient wsClient;
    private final AnalysisContextReportPublisher contextPublisher;
    private final ImmutableProjectReactor projectReactor;
    private final DefaultAnalysisMode analysisMode;
    private final TempFolder temp;
    private final ReportPublisherStep[] publishers;

    private File reportDir;
    private ScannerReportWriter writer;

    public ReportPublisher(Settings settings, BatchWsClient wsClient,
            AnalysisContextReportPublisher contextPublisher, ImmutableProjectReactor projectReactor,
            DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) {
        this.settings = settings;
        this.wsClient = wsClient;
        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 = publicUrl();
            if (HttpUrl.parse(publicUrl) == null) {
                throw MessageException.of("Failed to parse public URL set in SonarQube server: " + publicUrl);
            }
        }
    }

    @Override
    public void stop() {
        if (!settings.getBoolean(KEEP_REPORT_PROP_KEY) && !settings.getBoolean(VERBOSE_KEY)) {
            deleteQuietly(reportDir);
        } else {
            LOG.info("Analysis report generated in " + 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 (!analysisMode.isMediumTest()) {
                taskId = upload(report);
            }
        }
        logSuccess(taskId);
    }

    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("projectKey", projectDefinition.getKey())
                .setParam("projectName", projectDefinition.getName())
                .setParam("projectBranch", projectDefinition.getBranch()).setPart("report", filePart);
        WsResponse response = wsClient.call(post).failIfNotSuccessful();
        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 = publicUrl();
            HttpUrl httpUrl = HttpUrl.parse(publicUrl);

            Map<String, String> metadata = new LinkedHashMap<>();
            String effectiveKey = projectReactor.getRoot().getKeyWithBranch();
            metadata.put("projectKey", effectiveKey);
            metadata.put("serverUrl", publicUrl);

            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) {
        File file = new File(projectReactor.getRoot().getWorkDir(), METADATA_DUMP_FILENAME);
        try (Writer output = Files.newWriter(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);
        }
    }

    /**
     * The public URL is optionally configured on server. If not, then the regular URL is returned.
     * See https://jira.sonarsource.com/browse/SONAR-4239
     */
    private String publicUrl() {
        String baseUrl = trimToEmpty(settings.getString(CoreProperties.SERVER_BASE_URL));
        if (baseUrl.equals(settings.getDefaultValue(CoreProperties.SERVER_BASE_URL))) {
            // crap workaround for https://jira.sonarsource.com/browse/SONAR-7109
            // If server base URL was not configured in Sonar server then is is better to take URL configured on batch side
            baseUrl = wsClient.baseUrl();
        }
        return baseUrl.replaceAll("(/)+$", "");
    }
}