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}