org.sonar.server.computation.task.projectanalysis.step.QualityGateMeasuresStep.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.server.computation.task.projectanalysis.step.QualityGateMeasuresStep.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.server.computation.task.projectanalysis.step;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.server.computation.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
import org.sonar.server.computation.task.projectanalysis.measure.Measure;
import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
import org.sonar.server.computation.task.projectanalysis.measure.QualityGateStatus;
import org.sonar.server.computation.task.projectanalysis.measure.qualitygatedetails.EvaluatedCondition;
import org.sonar.server.computation.task.projectanalysis.measure.qualitygatedetails.QualityGateDetailsData;
import org.sonar.server.computation.task.projectanalysis.metric.Metric;
import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
import org.sonar.server.computation.task.projectanalysis.qualitygate.Condition;
import org.sonar.server.computation.task.projectanalysis.qualitygate.ConditionEvaluator;
import org.sonar.server.computation.task.projectanalysis.qualitygate.ConditionStatus;
import org.sonar.server.computation.task.projectanalysis.qualitygate.EvaluationResult;
import org.sonar.server.computation.task.projectanalysis.qualitygate.EvaluationResultTextConverter;
import org.sonar.server.computation.task.projectanalysis.qualitygate.MutableQualityGateStatusHolder;
import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate;
import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateHolder;
import org.sonar.server.computation.task.step.ComputationStep;

import static com.google.common.collect.FluentIterable.from;
import static java.lang.String.format;
import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
import static org.sonar.server.computation.task.projectanalysis.qualitygate.ConditionStatus.NO_VALUE_STATUS;
import static org.sonar.server.computation.task.projectanalysis.qualitygate.ConditionStatus.create;

/**
 * This step:
 * <ul>
 * <li>updates the QualityGateStatus of all the project's measures for the metrics of the conditions of the current
 * QualityGate (retrieved from {@link QualityGateHolder})</li>
 * <li>computes the measures on the project for metrics {@link CoreMetrics#QUALITY_GATE_DETAILS_KEY} and
 * {@link CoreMetrics#ALERT_STATUS_KEY}</li>
 * </ul>
 *
 * It must be executed after the computation of differential measures {@link ComputeMeasureVariationsStep}
 */
public class QualityGateMeasuresStep implements ComputationStep {
    // Condition on period should come first
    private static final Ordering<Condition> PERIOD_ORDERING = Ordering.natural().reverse()
            .onResultOf(Condition::hasPeriod);

    private final TreeRootHolder treeRootHolder;
    private final QualityGateHolder qualityGateHolder;
    private final MutableQualityGateStatusHolder qualityGateStatusHolder;
    private final MeasureRepository measureRepository;
    private final MetricRepository metricRepository;
    private final EvaluationResultTextConverter evaluationResultTextConverter;

    public QualityGateMeasuresStep(TreeRootHolder treeRootHolder, QualityGateHolder qualityGateHolder,
            MutableQualityGateStatusHolder qualityGateStatusHolder, MeasureRepository measureRepository,
            MetricRepository metricRepository, EvaluationResultTextConverter evaluationResultTextConverter) {
        this.treeRootHolder = treeRootHolder;
        this.qualityGateHolder = qualityGateHolder;
        this.qualityGateStatusHolder = qualityGateStatusHolder;
        this.evaluationResultTextConverter = evaluationResultTextConverter;
        this.measureRepository = measureRepository;
        this.metricRepository = metricRepository;
    }

    @Override
    public void execute() {
        new DepthTraversalTypeAwareCrawler(new TypeAwareVisitorAdapter(CrawlerDepthLimit.PROJECT, PRE_ORDER) {
            @Override
            public void visitProject(Component project) {
                executeForProject(project);
            }
        }).visit(treeRootHolder.getRoot());
    }

    private void executeForProject(Component project) {
        Optional<QualityGate> qualityGate = qualityGateHolder.getQualityGate();
        if (qualityGate.isPresent()) {
            QualityGateDetailsDataBuilder builder = new QualityGateDetailsDataBuilder();
            updateMeasures(project, qualityGate.get().getConditions(), builder);

            addProjectMeasure(project, builder);

            updateQualityGateStatusHolder(qualityGate.get(), builder);
        }
    }

    private void updateQualityGateStatusHolder(QualityGate qualityGate, QualityGateDetailsDataBuilder builder) {
        qualityGateStatusHolder.setStatus(convert(builder.getGlobalLevel()),
                createStatusPerCondition(qualityGate.getConditions(), builder.getEvaluatedConditions()));
    }

    private static ConditionStatus.EvaluationStatus toEvaluationStatus(Measure.Level globalLevel) {
        switch (globalLevel) {
        case OK:
            return ConditionStatus.EvaluationStatus.OK;
        case WARN:
            return ConditionStatus.EvaluationStatus.WARN;
        case ERROR:
            return ConditionStatus.EvaluationStatus.ERROR;
        default:
            throw new IllegalArgumentException(
                    format("Unsupported value '%s' of Measure.Level can not be converted to EvaluationStatus",
                            globalLevel));
        }
    }

    private static org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus convert(
            Measure.Level globalLevel) {
        switch (globalLevel) {
        case OK:
            return org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus.OK;
        case WARN:
            return org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus.WARN;
        case ERROR:
            return org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus.ERROR;
        default:
            throw new IllegalArgumentException(
                    format("Unsupported value '%s' of Measure.Level can not be converted to QualityGateStatus",
                            globalLevel));
        }
    }

    private static Map<Condition, ConditionStatus> createStatusPerCondition(Iterable<Condition> conditions,
            Iterable<EvaluatedCondition> evaluatedConditions) {
        Map<Condition, EvaluatedCondition> evaluatedConditionPerCondition = from(evaluatedConditions)
                .uniqueIndex(EvaluatedConditionToCondition.INSTANCE);

        ImmutableMap.Builder<Condition, ConditionStatus> builder = ImmutableMap.builder();
        for (Condition condition : conditions) {
            EvaluatedCondition evaluatedCondition = evaluatedConditionPerCondition.get(condition);

            if (evaluatedCondition == null) {
                builder.put(condition, NO_VALUE_STATUS);
            } else {
                builder.put(condition, create(toEvaluationStatus(evaluatedCondition.getLevel()),
                        evaluatedCondition.getActualValue()));
            }
        }
        return builder.build();
    }

    private void updateMeasures(Component project, Set<Condition> conditions,
            QualityGateDetailsDataBuilder builder) {
        Multimap<Metric, Condition> conditionsPerMetric = conditions.stream()
                .collect(MoreCollectors.index(Condition::getMetric, java.util.function.Function.identity()));
        for (Map.Entry<Metric, Collection<Condition>> entry : conditionsPerMetric.asMap().entrySet()) {
            Metric metric = entry.getKey();
            Optional<Measure> measure = measureRepository.getRawMeasure(project, metric);
            if (!measure.isPresent()) {
                continue;
            }

            MetricEvaluationResult metricEvaluationResult = evaluateQualityGate(measure.get(), entry.getValue());
            String text = evaluationResultTextConverter.asText(metricEvaluationResult.condition,
                    metricEvaluationResult.evaluationResult);
            builder.addLabel(text);

            Measure updatedMeasure = Measure.updatedMeasureBuilder(measure.get())
                    .setQualityGateStatus(
                            new QualityGateStatus(metricEvaluationResult.evaluationResult.getLevel(), text))
                    .create();
            measureRepository.update(project, metric, updatedMeasure);

            builder.addEvaluatedCondition(metricEvaluationResult);
        }
    }

    private static MetricEvaluationResult evaluateQualityGate(Measure measure, Collection<Condition> conditions) {
        ConditionEvaluator conditionEvaluator = new ConditionEvaluator();
        MetricEvaluationResult metricEvaluationResult = null;
        for (Condition newCondition : PERIOD_ORDERING.immutableSortedCopy(conditions)) {
            EvaluationResult newEvaluationResult = conditionEvaluator.evaluate(newCondition, measure);
            if (metricEvaluationResult == null || newEvaluationResult.getLevel()
                    .ordinal() > metricEvaluationResult.evaluationResult.getLevel().ordinal()) {
                metricEvaluationResult = new MetricEvaluationResult(newEvaluationResult, newCondition);
            }
        }
        return metricEvaluationResult;
    }

    private void addProjectMeasure(Component project, QualityGateDetailsDataBuilder builder) {
        Measure globalMeasure = Measure.newMeasureBuilder().setQualityGateStatus(
                new QualityGateStatus(builder.getGlobalLevel(), StringUtils.join(builder.getLabels(), ", ")))
                .create(builder.getGlobalLevel());
        Metric metric = metricRepository.getByKey(CoreMetrics.ALERT_STATUS_KEY);
        measureRepository.add(project, metric, globalMeasure);

        String detailMeasureValue = new QualityGateDetailsData(builder.getGlobalLevel(),
                builder.getEvaluatedConditions()).toJson();
        Measure detailsMeasure = Measure.newMeasureBuilder().create(detailMeasureValue);
        Metric qgDetailsMetric = metricRepository.getByKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY);
        measureRepository.add(project, qgDetailsMetric, detailsMeasure);
    }

    @Override
    public String getDescription() {
        return "Compute Quality Gate measures";
    }

    private static final class QualityGateDetailsDataBuilder {
        private Measure.Level globalLevel = Measure.Level.OK;
        private List<String> labels = new ArrayList<>();
        private List<EvaluatedCondition> evaluatedConditions = new ArrayList<>();

        public Measure.Level getGlobalLevel() {
            return globalLevel;
        }

        public void addLabel(@Nullable String label) {
            if (StringUtils.isNotBlank(label)) {
                labels.add(label);
            }
        }

        public List<String> getLabels() {
            return labels;
        }

        public void addEvaluatedCondition(MetricEvaluationResult metricEvaluationResult) {
            Measure.Level level = metricEvaluationResult.evaluationResult.getLevel();
            if (Measure.Level.WARN == level && this.globalLevel != Measure.Level.ERROR) {
                globalLevel = Measure.Level.WARN;

            } else if (Measure.Level.ERROR == level) {
                globalLevel = Measure.Level.ERROR;
            }
            evaluatedConditions.add(new EvaluatedCondition(metricEvaluationResult.condition, level,
                    metricEvaluationResult.evaluationResult.getValue()));
        }

        public List<EvaluatedCondition> getEvaluatedConditions() {
            return evaluatedConditions;
        }
    }

    private enum EvaluatedConditionToCondition implements Function<EvaluatedCondition, Condition> {
        INSTANCE;

        @Override
        @Nonnull
        public Condition apply(@Nonnull EvaluatedCondition input) {
            return input.getCondition();
        }
    }

    private static class MetricEvaluationResult {
        private final EvaluationResult evaluationResult;
        private final Condition condition;

        private MetricEvaluationResult(EvaluationResult evaluationResult, Condition condition) {
            this.evaluationResult = evaluationResult;
            this.condition = condition;
        }
    }

}