001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.batch.qualitygate;
021
022 import com.google.common.collect.ImmutableMap;
023 import com.google.common.collect.Lists;
024 import com.google.common.collect.Sets;
025 import org.apache.commons.lang.StringUtils;
026 import org.sonar.api.batch.Decorator;
027 import org.sonar.api.batch.DecoratorBarriers;
028 import org.sonar.api.batch.DecoratorContext;
029 import org.sonar.api.batch.DependedUpon;
030 import org.sonar.api.batch.DependsUpon;
031 import org.sonar.api.database.model.Snapshot;
032 import org.sonar.api.i18n.I18n;
033 import org.sonar.api.measures.CoreMetrics;
034 import org.sonar.api.measures.Measure;
035 import org.sonar.api.measures.Metric;
036 import org.sonar.api.resources.Project;
037 import org.sonar.api.resources.Resource;
038 import org.sonar.api.resources.ResourceUtils;
039 import org.sonar.api.utils.Duration;
040 import org.sonar.api.utils.Durations;
041 import org.sonar.core.qualitygate.db.QualityGateConditionDto;
042 import org.sonar.core.timemachine.Periods;
043
044 import java.util.Collection;
045 import java.util.List;
046 import java.util.Locale;
047 import java.util.Map;
048 import java.util.Set;
049
050 public class QualityGateVerifier implements Decorator {
051
052 private static final String VARIATION_METRIC_PREFIX = "new_";
053 private static final String VARIATION = "variation";
054 private static final Map<String, String> OPERATOR_LABELS = ImmutableMap.of(
055 QualityGateConditionDto.OPERATOR_EQUALS, "=",
056 QualityGateConditionDto.OPERATOR_NOT_EQUALS, "!=",
057 QualityGateConditionDto.OPERATOR_GREATER_THAN, ">",
058 QualityGateConditionDto.OPERATOR_LESS_THAN, "<");
059
060 private QualityGate qualityGate;
061
062 private Snapshot snapshot;
063 private Periods periods;
064 private I18n i18n;
065 private Durations durations;
066
067 public QualityGateVerifier(QualityGate qualityGate, Snapshot snapshot, Periods periods, I18n i18n, Durations durations) {
068 this.qualityGate = qualityGate;
069 this.snapshot = snapshot;
070 this.periods = periods;
071 this.i18n = i18n;
072 this.durations = durations;
073 }
074
075 @DependedUpon
076 public Metric generatesQualityGateStatus() {
077 return CoreMetrics.ALERT_STATUS;
078 }
079
080 @DependsUpon
081 public String dependsOnVariations() {
082 return DecoratorBarriers.END_OF_TIME_MACHINE;
083 }
084
085 @DependsUpon
086 public Collection<Metric> dependsUponMetrics() {
087 Set<Metric> metrics = Sets.newHashSet();
088 for (ResolvedCondition condition : qualityGate.conditions()) {
089 metrics.add(condition.metric());
090 }
091 return metrics;
092 }
093
094 @Override
095 public boolean shouldExecuteOnProject(Project project) {
096 return qualityGate.isEnabled();
097 }
098
099 @Override
100 public void decorate(Resource resource, DecoratorContext context) {
101 if (ResourceUtils.isRootProject(resource)) {
102 checkProjectConditions(resource, context);
103 }
104 }
105
106 private void checkProjectConditions(Resource resource, DecoratorContext context) {
107 Metric.Level globalLevel = Metric.Level.OK;
108 QualityGateDetails details = new QualityGateDetails();
109 List<String> labels = Lists.newArrayList();
110
111 for (ResolvedCondition condition : qualityGate.conditions()) {
112 Measure measure = context.getMeasure(condition.metric());
113 if (measure != null) {
114 Metric.Level level = ConditionUtils.getLevel(condition, measure);
115
116 measure.setAlertStatus(level);
117 String text = getText(condition, level);
118 if (!StringUtils.isBlank(text)) {
119 measure.setAlertText(text);
120 labels.add(text);
121 }
122
123 context.saveMeasure(measure);
124
125 if (Metric.Level.WARN == level && globalLevel != Metric.Level.ERROR) {
126 globalLevel = Metric.Level.WARN;
127
128 } else if (Metric.Level.ERROR == level) {
129 globalLevel = Metric.Level.ERROR;
130 }
131
132 details.addCondition(condition, level, ConditionUtils.getValue(condition, measure));
133 }
134 }
135
136 Measure globalMeasure = new Measure(CoreMetrics.ALERT_STATUS, globalLevel);
137 globalMeasure.setAlertStatus(globalLevel);
138 globalMeasure.setAlertText(StringUtils.join(labels, ", "));
139 context.saveMeasure(globalMeasure);
140
141 details.setLevel(globalLevel);
142 Measure detailsMeasure = new Measure(CoreMetrics.QUALITY_GATE_DETAILS, details.toJson());
143 context.saveMeasure(detailsMeasure);
144
145 }
146
147 private String getText(ResolvedCondition condition, Metric.Level level) {
148 if (level == Metric.Level.OK) {
149 return null;
150 }
151 return getAlertLabel(condition, level);
152 }
153
154 private String getAlertLabel(ResolvedCondition condition, Metric.Level level) {
155 Integer alertPeriod = condition.period();
156 String metric = i18n.message(Locale.ENGLISH, "metric." + condition.metricKey() + ".name", condition.metric().getName());
157
158 StringBuilder stringBuilder = new StringBuilder();
159 stringBuilder.append(metric);
160
161 if (alertPeriod != null && !condition.metricKey().startsWith(VARIATION_METRIC_PREFIX)) {
162 String variation = i18n.message(Locale.ENGLISH, VARIATION, VARIATION).toLowerCase();
163 stringBuilder.append(" ").append(variation);
164 }
165
166 stringBuilder
167 .append(" ").append(operatorLabel(condition.operator())).append(" ")
168 .append(alertValue(condition, level));
169
170 if (alertPeriod != null) {
171 stringBuilder.append(" ").append(periods.label(snapshot, alertPeriod));
172 }
173
174 return stringBuilder.toString();
175 }
176
177 private String alertValue(ResolvedCondition condition, Metric.Level level) {
178 String value = level.equals(Metric.Level.ERROR) ? condition.errorThreshold() : condition.warningThreshold();
179 if (condition.metric().getType().equals(Metric.ValueType.WORK_DUR)) {
180 return formatDuration(value);
181 } else {
182 return value;
183 }
184 }
185
186 private String formatDuration(String value) {
187 return durations.format(Locale.ENGLISH, Duration.create(Long.parseLong(value)), Durations.DurationFormat.SHORT);
188 }
189
190 private String operatorLabel(String operator) {
191 return OPERATOR_LABELS.get(operator);
192 }
193
194 @Override
195 public String toString() {
196 return getClass().getSimpleName();
197 }
198 }