/* * 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.qualitymodel; import com.google.common.collect.ImmutableMap; import java.util.Map; import org.sonar.api.ce.measure.Issue; import org.sonar.api.measures.CoreMetrics; import org.sonar.core.issue.DefaultIssue; import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.PathAwareVisitorAdapter; import org.sonar.server.computation.task.projectanalysis.formula.counter.RatingValue; import org.sonar.server.computation.task.projectanalysis.issue.ComponentIssuesRepository; import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository; 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.period.Period; import org.sonar.server.computation.task.projectanalysis.period.PeriodHolder; import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.api.rule.Severity.CRITICAL; import static org.sonar.api.rule.Severity.INFO; import static org.sonar.api.rule.Severity.MAJOR; import static org.sonar.api.rule.Severity.MINOR; import static org.sonar.api.rules.RuleType.BUG; import static org.sonar.api.rules.RuleType.VULNERABILITY; import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; import static org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit.LEAVES; import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E; /** * Compute following measures : * {@link CoreMetrics#NEW_RELIABILITY_RATING_KEY} * {@link CoreMetrics#NEW_SECURITY_RATING_KEY} */ public class NewReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVisitorAdapter<NewReliabilityAndSecurityRatingMeasuresVisitor.Counter> { private static final Map<String, Rating> RATING_BY_SEVERITY = ImmutableMap.of( BLOCKER, E, CRITICAL, D, MAJOR, C, MINOR, B, INFO, A); private final MeasureRepository measureRepository; private final ComponentIssuesRepository componentIssuesRepository; private final PeriodHolder periodHolder; private final Map<String, Metric> metricsByKey; public NewReliabilityAndSecurityRatingMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, ComponentIssuesRepository componentIssuesRepository, PeriodHolder periodHolder) { super(LEAVES, POST_ORDER, CounterFactory.INSTANCE); this.measureRepository = measureRepository; this.componentIssuesRepository = componentIssuesRepository; this.periodHolder = periodHolder; // Output metrics this.metricsByKey = ImmutableMap.of( NEW_RELIABILITY_RATING_KEY, metricRepository.getByKey(NEW_RELIABILITY_RATING_KEY), NEW_SECURITY_RATING_KEY, metricRepository.getByKey(NEW_SECURITY_RATING_KEY)); } @Override public void visitProject(Component project, Path<Counter> path) { computeAndSaveMeasures(project, path); } @Override public void visitDirectory(Component directory, Path<Counter> path) { computeAndSaveMeasures(directory, path); } @Override public void visitModule(Component module, Path<Counter> path) { computeAndSaveMeasures(module, path); } @Override public void visitFile(Component file, Path<Counter> path) { computeAndSaveMeasures(file, path); } private void computeAndSaveMeasures(Component component, Path<Counter> path) { if (!periodHolder.hasPeriod()) { return; } initRatingsToA(path); processIssues(component, path); path.current().newRatingValueByMetric.entrySet() .stream() .filter(entry -> entry.getValue().isSet()) .forEach( entry -> measureRepository.add( component, metricsByKey.get(entry.getKey()), newMeasureBuilder().setVariation(entry.getValue().getValue().getIndex()).createNoValue())); addToParent(path); } private static void initRatingsToA(Path<Counter> path) { path.current().newRatingValueByMetric.values().forEach(entry -> entry.increment(A)); } private void processIssues(Component component, Path<Counter> path) { componentIssuesRepository.getIssues(component) .stream() .filter(issue -> issue.resolution() == null) .filter(issue -> issue.type().equals(BUG) || issue.type().equals(VULNERABILITY)) .forEach(issue -> path.current().processIssue(issue, periodHolder.getPeriod())); } private static void addToParent(Path<Counter> path) { if (!path.isRoot()) { path.parent().add(path.current()); } } static final class Counter { private Map<String, RatingValue> newRatingValueByMetric = ImmutableMap.of( NEW_RELIABILITY_RATING_KEY, new RatingValue(), NEW_SECURITY_RATING_KEY, new RatingValue()); private Counter() { // prevents instantiation } void add(Counter otherCounter) { newRatingValueByMetric.entrySet().forEach(e -> e.getValue().increment(otherCounter.newRatingValueByMetric.get(e.getKey()))); } void processIssue(Issue issue, Period period) { if (isOnPeriod((DefaultIssue) issue, period)) { Rating rating = RATING_BY_SEVERITY.get(issue.severity()); if (issue.type().equals(BUG)) { newRatingValueByMetric.get(NEW_RELIABILITY_RATING_KEY).increment(rating); } else if (issue.type().equals(VULNERABILITY)) { newRatingValueByMetric.get(NEW_SECURITY_RATING_KEY).increment(rating); } } } private static boolean isOnPeriod(DefaultIssue issue, Period period) { // Add one second to not take into account issues created during current analysis return issue.creationDate().getTime() >= period.getSnapshotDate() + 1000L; } } private static final class CounterFactory extends SimpleStackElementFactory<NewReliabilityAndSecurityRatingMeasuresVisitor.Counter> { public static final CounterFactory INSTANCE = new CounterFactory(); private CounterFactory() { // prevents instantiation } @Override public Counter createForAny(Component component) { return new Counter(); } } }