001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar 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 * Sonar 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
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.plugins.core.timemachine;
021
022import java.text.DateFormat;
023import java.text.SimpleDateFormat;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Date;
027import java.util.List;
028import java.util.Set;
029
030import org.apache.commons.lang.StringUtils;
031import org.sonar.api.batch.Decorator;
032import org.sonar.api.batch.DecoratorBarriers;
033import org.sonar.api.batch.DecoratorContext;
034import org.sonar.api.batch.DependedUpon;
035import org.sonar.api.batch.DependsUpon;
036import org.sonar.api.measures.CoreMetrics;
037import org.sonar.api.measures.Measure;
038import org.sonar.api.measures.MeasureUtils;
039import org.sonar.api.measures.MeasuresFilters;
040import org.sonar.api.measures.Metric;
041import org.sonar.api.measures.RuleMeasure;
042import org.sonar.api.notifications.Notification;
043import org.sonar.api.notifications.NotificationManager;
044import org.sonar.api.resources.Project;
045import org.sonar.api.resources.Resource;
046import org.sonar.api.resources.ResourceUtils;
047import org.sonar.api.resources.Scopes;
048import org.sonar.api.rules.Rule;
049import org.sonar.api.rules.RulePriority;
050import org.sonar.api.rules.Violation;
051import org.sonar.batch.components.PastSnapshot;
052import org.sonar.batch.components.TimeMachineConfiguration;
053
054import com.google.common.collect.ArrayListMultimap;
055import com.google.common.collect.ListMultimap;
056import com.google.common.collect.Sets;
057
058@DependsUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
059public class NewViolationsDecorator implements Decorator {
060
061  private TimeMachineConfiguration timeMachineConfiguration;
062  private NotificationManager notificationManager;
063
064  public NewViolationsDecorator(TimeMachineConfiguration timeMachineConfiguration, NotificationManager notificationManager) {
065    this.timeMachineConfiguration = timeMachineConfiguration;
066    this.notificationManager = notificationManager;
067  }
068
069  public boolean shouldExecuteOnProject(Project project) {
070    return project.isLatestAnalysis();
071  }
072
073  @DependedUpon
074  public List<Metric> generatesMetric() {
075    return Arrays.asList(
076        CoreMetrics.NEW_VIOLATIONS,
077        CoreMetrics.NEW_BLOCKER_VIOLATIONS,
078        CoreMetrics.NEW_CRITICAL_VIOLATIONS,
079        CoreMetrics.NEW_MAJOR_VIOLATIONS,
080        CoreMetrics.NEW_MINOR_VIOLATIONS,
081        CoreMetrics.NEW_INFO_VIOLATIONS);
082  }
083
084  @SuppressWarnings("rawtypes")
085  public void decorate(Resource resource, DecoratorContext context) {
086    if (shouldDecorateResource(resource, context)) {
087      computeNewViolations(context);
088      computeNewViolationsPerSeverity(context);
089      computeNewViolationsPerRule(context);
090    }
091    if (ResourceUtils.isRootProject(resource)) {
092      notifyNewViolations((Project) resource, context);
093    }
094  }
095
096  private boolean shouldDecorateResource(Resource<?> resource, DecoratorContext context) {
097    return (StringUtils.equals(Scopes.PROJECT, resource.getScope()) || StringUtils.equals(Scopes.DIRECTORY, resource.getScope()) || StringUtils
098        .equals(Scopes.FILE, resource.getScope()))
099      && (context.getMeasure(CoreMetrics.NEW_VIOLATIONS) == null);
100  }
101
102  private void computeNewViolations(DecoratorContext context) {
103    Measure measure = new Measure(CoreMetrics.NEW_VIOLATIONS);
104    for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
105      int variationIndex = pastSnapshot.getIndex();
106      Collection<Measure> children = context.getChildrenMeasures(CoreMetrics.NEW_VIOLATIONS);
107      int count = countViolations(context.getViolations(), pastSnapshot.getTargetDate());
108      double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count;
109      measure.setVariation(variationIndex, sum);
110    }
111    context.saveMeasure(measure);
112  }
113
114  private void computeNewViolationsPerSeverity(DecoratorContext context) {
115    ListMultimap<RulePriority, Violation> violationsPerSeverities = ArrayListMultimap.create();
116    for (Violation violation : context.getViolations()) {
117      violationsPerSeverities.put(violation.getSeverity(), violation);
118    }
119
120    for (RulePriority severity : RulePriority.values()) {
121      Metric metric = severityToMetric(severity);
122      Measure measure = new Measure(metric);
123      for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
124        int variationIndex = pastSnapshot.getIndex();
125        int count = countViolations(violationsPerSeverities.get(severity), pastSnapshot.getTargetDate());
126        Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric));
127        double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count;
128        measure.setVariation(variationIndex, sum);
129      }
130      context.saveMeasure(measure);
131    }
132  }
133
134  private void computeNewViolationsPerRule(DecoratorContext context) {
135    for (RulePriority severity : RulePriority.values()) {
136      Metric metric = severityToMetric(severity);
137      ListMultimap<Rule, Measure> childMeasuresPerRule = ArrayListMultimap.create();
138      ListMultimap<Rule, Violation> violationsPerRule = ArrayListMultimap.create();
139      Set<Rule> rules = Sets.newHashSet();
140
141      Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
142      for (Measure child : children) {
143        RuleMeasure childRuleMeasure = (RuleMeasure) child;
144        Rule rule = childRuleMeasure.getRule();
145        if (rule != null) {
146          childMeasuresPerRule.put(rule, childRuleMeasure);
147          rules.add(rule);
148        }
149      }
150
151      for (Violation violation : context.getViolations()) {
152        if (violation.getSeverity().equals(severity)) {
153          rules.add(violation.getRule());
154          violationsPerRule.put(violation.getRule(), violation);
155        }
156      }
157
158      for (Rule rule : rules) {
159        RuleMeasure measure = RuleMeasure.createForRule(metric, rule, null);
160        measure.setSeverity(severity);
161        for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) {
162          int variationIndex = pastSnapshot.getIndex();
163          int count = countViolations(violationsPerRule.get(rule), pastSnapshot.getTargetDate());
164          double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRule.get(rule)) + count;
165          measure.setVariation(variationIndex, sum);
166        }
167        context.saveMeasure(measure);
168      }
169    }
170  }
171
172  int countViolations(Collection<Violation> violations, Date targetDate) {
173    if (violations == null) {
174      return 0;
175    }
176    int count = 0;
177    for (Violation violation : violations) {
178      if (isAfter(violation, targetDate)) {
179        count++;
180      }
181    }
182    return count;
183  }
184
185  private boolean isAfter(Violation violation, Date date) {
186    if (date == null) {
187      return true;
188    }
189    return violation.getCreatedAt() != null && violation.getCreatedAt().after(date);
190  }
191
192  private Metric severityToMetric(RulePriority severity) {
193    Metric metric;
194    if (severity.equals(RulePriority.BLOCKER)) {
195      metric = CoreMetrics.NEW_BLOCKER_VIOLATIONS;
196    } else if (severity.equals(RulePriority.CRITICAL)) {
197      metric = CoreMetrics.NEW_CRITICAL_VIOLATIONS;
198    } else if (severity.equals(RulePriority.MAJOR)) {
199      metric = CoreMetrics.NEW_MAJOR_VIOLATIONS;
200    } else if (severity.equals(RulePriority.MINOR)) {
201      metric = CoreMetrics.NEW_MINOR_VIOLATIONS;
202    } else if (severity.equals(RulePriority.INFO)) {
203      metric = CoreMetrics.NEW_INFO_VIOLATIONS;
204    } else {
205      throw new IllegalArgumentException("Unsupported severity: " + severity);
206    }
207    return metric;
208  }
209
210  protected void notifyNewViolations(Project project, DecoratorContext context) {
211    List<PastSnapshot> projectPastSnapshots = timeMachineConfiguration.getProjectPastSnapshots();
212    if (projectPastSnapshots.size() >= 1) {
213      // we always check new violations against period1
214      PastSnapshot pastSnapshot = projectPastSnapshots.get(0);
215      Double newViolationsCount = context.getMeasure(CoreMetrics.NEW_VIOLATIONS).getVariation1();
216      // Do not send notification if this is the first analysis or if there's no violation
217      if (pastSnapshot.getTargetDate() != null && newViolationsCount != null && newViolationsCount > 0) {
218        // Maybe we should check if this is the first analysis or not?
219        DateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd");
220        Notification notification = new Notification("new-violations")
221            .setFieldValue("count", String.valueOf(newViolationsCount.intValue()))
222            .setFieldValue("projectName", project.getLongName())
223            .setFieldValue("projectKey", project.getKey())
224            .setFieldValue("projectId", String.valueOf(project.getId()))
225            .setFieldValue("fromDate", dateformat.format(pastSnapshot.getTargetDate()));
226        notificationManager.scheduleForSending(notification);
227      }
228    }
229  }
230
231  @Override
232  public String toString() {
233    return getClass().getSimpleName();
234  }
235}