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 */ 020 package org.sonar.plugins.core.timemachine; 021 022 import java.text.DateFormat; 023 import java.text.SimpleDateFormat; 024 import java.util.Arrays; 025 import java.util.Collection; 026 import java.util.Date; 027 import java.util.List; 028 import java.util.Set; 029 030 import org.apache.commons.lang.StringUtils; 031 import org.sonar.api.batch.Decorator; 032 import org.sonar.api.batch.DecoratorBarriers; 033 import org.sonar.api.batch.DecoratorContext; 034 import org.sonar.api.batch.DependedUpon; 035 import org.sonar.api.batch.DependsUpon; 036 import org.sonar.api.measures.CoreMetrics; 037 import org.sonar.api.measures.Measure; 038 import org.sonar.api.measures.MeasureUtils; 039 import org.sonar.api.measures.MeasuresFilters; 040 import org.sonar.api.measures.Metric; 041 import org.sonar.api.measures.RuleMeasure; 042 import org.sonar.api.notifications.Notification; 043 import org.sonar.api.notifications.NotificationManager; 044 import org.sonar.api.resources.Project; 045 import org.sonar.api.resources.Resource; 046 import org.sonar.api.resources.ResourceUtils; 047 import org.sonar.api.resources.Scopes; 048 import org.sonar.api.rules.Rule; 049 import org.sonar.api.rules.RulePriority; 050 import org.sonar.api.rules.Violation; 051 import org.sonar.batch.components.PastSnapshot; 052 import org.sonar.batch.components.TimeMachineConfiguration; 053 054 import com.google.common.collect.ArrayListMultimap; 055 import com.google.common.collect.ListMultimap; 056 import com.google.common.collect.Sets; 057 058 @DependsUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING) 059 public 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 && !ResourceUtils.isUnitTestClass(resource) 100 && context.getMeasure(CoreMetrics.NEW_VIOLATIONS) == null; 101 } 102 103 private void computeNewViolations(DecoratorContext context) { 104 Measure measure = new Measure(CoreMetrics.NEW_VIOLATIONS); 105 for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { 106 int variationIndex = pastSnapshot.getIndex(); 107 Collection<Measure> children = context.getChildrenMeasures(CoreMetrics.NEW_VIOLATIONS); 108 int count = countViolations(context.getViolations(), pastSnapshot.getTargetDate()); 109 double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count; 110 measure.setVariation(variationIndex, sum); 111 } 112 context.saveMeasure(measure); 113 } 114 115 private void computeNewViolationsPerSeverity(DecoratorContext context) { 116 ListMultimap<RulePriority, Violation> violationsPerSeverities = ArrayListMultimap.create(); 117 for (Violation violation : context.getViolations()) { 118 violationsPerSeverities.put(violation.getSeverity(), violation); 119 } 120 121 for (RulePriority severity : RulePriority.values()) { 122 Metric metric = severityToMetric(severity); 123 Measure measure = new Measure(metric); 124 for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { 125 int variationIndex = pastSnapshot.getIndex(); 126 int count = countViolations(violationsPerSeverities.get(severity), pastSnapshot.getTargetDate()); 127 Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric)); 128 double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count; 129 measure.setVariation(variationIndex, sum); 130 } 131 context.saveMeasure(measure); 132 } 133 } 134 135 private void computeNewViolationsPerRule(DecoratorContext context) { 136 for (RulePriority severity : RulePriority.values()) { 137 Metric metric = severityToMetric(severity); 138 ListMultimap<Rule, Measure> childMeasuresPerRule = ArrayListMultimap.create(); 139 ListMultimap<Rule, Violation> violationsPerRule = ArrayListMultimap.create(); 140 Set<Rule> rules = Sets.newHashSet(); 141 142 Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric)); 143 for (Measure child : children) { 144 RuleMeasure childRuleMeasure = (RuleMeasure) child; 145 Rule rule = childRuleMeasure.getRule(); 146 if (rule != null) { 147 childMeasuresPerRule.put(rule, childRuleMeasure); 148 rules.add(rule); 149 } 150 } 151 152 for (Violation violation : context.getViolations()) { 153 if (violation.getSeverity().equals(severity)) { 154 rules.add(violation.getRule()); 155 violationsPerRule.put(violation.getRule(), violation); 156 } 157 } 158 159 for (Rule rule : rules) { 160 RuleMeasure measure = RuleMeasure.createForRule(metric, rule, null); 161 measure.setSeverity(severity); 162 for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { 163 int variationIndex = pastSnapshot.getIndex(); 164 int count = countViolations(violationsPerRule.get(rule), pastSnapshot.getTargetDate()); 165 double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRule.get(rule)) + count; 166 measure.setVariation(variationIndex, sum); 167 } 168 context.saveMeasure(measure); 169 } 170 } 171 } 172 173 int countViolations(Collection<Violation> violations, Date targetDate) { 174 if (violations == null) { 175 return 0; 176 } 177 int count = 0; 178 for (Violation violation : violations) { 179 if (isAfter(violation, targetDate)) { 180 count++; 181 } 182 } 183 return count; 184 } 185 186 private boolean isAfter(Violation violation, Date date) { 187 if (date == null) { 188 return true; 189 } 190 return violation.getCreatedAt() != null && violation.getCreatedAt().after(date); 191 } 192 193 private Metric severityToMetric(RulePriority severity) { 194 Metric metric; 195 if (severity.equals(RulePriority.BLOCKER)) { 196 metric = CoreMetrics.NEW_BLOCKER_VIOLATIONS; 197 } else if (severity.equals(RulePriority.CRITICAL)) { 198 metric = CoreMetrics.NEW_CRITICAL_VIOLATIONS; 199 } else if (severity.equals(RulePriority.MAJOR)) { 200 metric = CoreMetrics.NEW_MAJOR_VIOLATIONS; 201 } else if (severity.equals(RulePriority.MINOR)) { 202 metric = CoreMetrics.NEW_MINOR_VIOLATIONS; 203 } else if (severity.equals(RulePriority.INFO)) { 204 metric = CoreMetrics.NEW_INFO_VIOLATIONS; 205 } else { 206 throw new IllegalArgumentException("Unsupported severity: " + severity); 207 } 208 return metric; 209 } 210 211 protected void notifyNewViolations(Project project, DecoratorContext context) { 212 List<PastSnapshot> projectPastSnapshots = timeMachineConfiguration.getProjectPastSnapshots(); 213 if (projectPastSnapshots.size() >= 1) { 214 // we always check new violations against period1 215 PastSnapshot pastSnapshot = projectPastSnapshots.get(0); 216 Double newViolationsCount = context.getMeasure(CoreMetrics.NEW_VIOLATIONS).getVariation1(); 217 // Do not send notification if this is the first analysis or if there's no violation 218 if (pastSnapshot.getTargetDate() != null && newViolationsCount != null && newViolationsCount > 0) { 219 // Maybe we should check if this is the first analysis or not? 220 DateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd"); 221 Notification notification = new Notification("new-violations") 222 .setFieldValue("count", String.valueOf(newViolationsCount.intValue())) 223 .setFieldValue("projectName", project.getLongName()) 224 .setFieldValue("projectKey", project.getKey()) 225 .setFieldValue("projectId", String.valueOf(project.getId())) 226 .setFieldValue("fromDate", dateformat.format(pastSnapshot.getTargetDate())); 227 notificationManager.scheduleForSending(notification); 228 } 229 } 230 } 231 232 @Override 233 public String toString() { 234 return getClass().getSimpleName(); 235 } 236 }