/*
* Sonar, open source software quality management tool.
* Copyright (C) 2009 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar 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.
*
* Sonar 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 Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.plugins.secrules;
import com.google.common.collect.Maps;
import org.apache.commons.configuration.Configuration;
import org.sonar.api.batch.Decorator;
import org.sonar.api.batch.DecoratorContext;
import org.sonar.api.batch.DependedUpon;
import org.sonar.api.batch.DependsUpon;
import org.sonar.api.measures.*;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rules.*;
import org.sonar.api.utils.KeyValue;
import org.sonar.api.utils.KeyValueFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public final class SecurityRulesDecorator implements Decorator {
private List<Rule> rules;
private RulesProfile rulesProfile;
private Map<RulePriority, Integer> weights;
public SecurityRulesDecorator(RuleFinder ruleFinder, RulesProfile rulesProfile, Configuration configuration) {
this.rulesProfile = rulesProfile;
weights = getPriorityWeights(configuration);
this.rules = new RulesParser(configuration, ruleFinder).getRulesList();
}
@DependsUpon
public List<Metric> dependUpon() {
return Arrays.asList(CoreMetrics.NCLOC);
}
@DependedUpon
public List<Metric> generatesMetrics() {
return Arrays.asList(SecurityRulesMetrics.SECURITY_VIOLATIONS,
SecurityRulesMetrics.SECURITY_RCI, SecurityRulesMetrics.WEIGHTED_SECURITY_VIOLATIONS);
}
/**
* {@inheritDoc}
*/
public boolean shouldExecuteOnProject(Project project) {
return true;
}
public void decorate(Resource resource, DecoratorContext context) {
if (shouldDecorate(resource)) {
Map<RulePriority, Integer> distribution = computeViolationsForRules(context);
double ncloc = MeasureUtils.getValue(context.getMeasure(CoreMetrics.NCLOC), 0.0);
int usedRules = getUsedRules();
// First calculate the violations at the resource level
double nbViolations = countViolations(distribution);
double weightedViolations = computeWeightedViolations(distribution);
CountDistributionBuilder countDistribution = computeCountDistribution(distribution);
// Consolidate violations from children
for (DecoratorContext child : context.getChildren()) {
nbViolations += MeasureUtils.getValue(child.getMeasure(SecurityRulesMetrics.SECURITY_VIOLATIONS), 0.0);
weightedViolations += MeasureUtils.getValue(child.getMeasure(SecurityRulesMetrics.WEIGHTED_SECURITY_VIOLATIONS), 0.0);
countDistribution.add(child.getMeasure(SecurityRulesMetrics.SECURITY_VIOLATIONS_DISTRIBUTION));
}
// Save calculated measures
Measure violations = new Measure(SecurityRulesMetrics.SECURITY_VIOLATIONS, nbViolations);
if (Scopes.isHigherThan(resource, Scopes.FILE)) {
// do not set description on files, else it avoids the "best value mechanism". Measures with value 0 would
// still be saved on files.
violations.setDescription(usedRules + "/" + rules.size());
}
context.saveMeasure(violations);
context.saveMeasure(SecurityRulesMetrics.WEIGHTED_SECURITY_VIOLATIONS, weightedViolations);
context.saveMeasure(countDistribution.build());
if (ncloc > 0) {
context.saveMeasure(SecurityRulesMetrics.SECURITY_RCI, 100 - weightedViolations / ncloc * 100);
}
}
}
private boolean shouldDecorate(Resource resource) {
return Scopes.isHigherThanOrEquals(resource, Scopes.FILE) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier());
}
private Map<RulePriority, Integer> computeViolationsForRules(DecoratorContext context) {
Map<RulePriority, Integer> distribution = Maps.newHashMap();
List<Violation> violations = context.getViolations();
for (Rule rule : rules) {
ActiveRule activeRule = rulesProfile.getActiveRule(rule);
if (activeRule != null) {
countViolationsForRule(distribution, activeRule, violations);
}
}
return distribution;
}
protected void countViolationsForRule(Map<RulePriority, Integer> distribution, ActiveRule activeRule, List<Violation> violations) {
for (Violation violation : violations) {
if (violation.getRule().equals(activeRule.getRule())) {
countViolationForRule(distribution, activeRule.getSeverity());
}
}
}
protected void countViolationForRule(Map<RulePriority, Integer> distribution, RulePriority severity) {
int current = 0;
if (distribution.get(severity) != null) {
current += distribution.get(severity);
}
distribution.put(severity, current + 1);
}
protected int countViolations(Map<RulePriority, Integer> distribution) {
int count = 0;
for (Integer value : distribution.values()) {
count += value;
}
return count;
}
protected int computeWeightedViolations(Map<RulePriority, Integer> distribution) {
int count = 0;
for (Map.Entry<RulePriority, Integer> entry : distribution.entrySet()) {
count += entry.getValue() * weights.get(entry.getKey());
}
return count;
}
protected CountDistributionBuilder computeCountDistribution(Map<RulePriority, Integer> distribution) {
CountDistributionBuilder countDistribution = new CountDistributionBuilder(SecurityRulesMetrics.SECURITY_VIOLATIONS_DISTRIBUTION);
for (Map.Entry<RulePriority, Integer> entry : distribution.entrySet()) {
countDistribution.add(entry.getKey(), entry.getValue());
}
return countDistribution;
}
protected int getUsedRules() {
int usedRules = 0;
for (Rule rule : rules) {
ActiveRule activeRule = rulesProfile.getActiveRule(rule);
if (activeRule != null) {
usedRules++;
}
}
return usedRules;
}
private Map<RulePriority, Integer> getPriorityWeights(Configuration configuration) {
// The key must be hard-coded since the WeightedViolationsDecorator is not accessible through the API
String levelWeight = configuration.getString("sonar.core.rule.weight", "INFO=0;MINOR=1;MAJOR=3;CRITICAL=5;BLOCKER=10");
Map<RulePriority, Integer> weights = KeyValueFormat.parse(levelWeight, new KeyValueFormat.Transformer<RulePriority, Integer>() {
public KeyValue<RulePriority, Integer> transform(String key, String value) {
try {
return new KeyValue<RulePriority, Integer>(RulePriority.valueOf(key.toUpperCase()), Integer.parseInt(value));
} catch (Exception e) {
return null;
}
}
});
for (RulePriority priority : RulePriority.values()) {
if (!weights.containsKey(priority)) {
weights.put(priority, 1);
}
}
return weights;
}
}