]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3437, SONAR-5189 Store measures in a persistit cache
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 23 Apr 2014 12:41:32 +0000 (14:41 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Wed, 30 Apr 2014 15:27:21 +0000 (17:27 +0200)
38 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/CountUnresolvedIssuesDecorator.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyDecorator.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/VariationDecorator.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/CountUnresolvedIssuesDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/VariationDecoratorTest.java
pom.xml
sonar-batch/pom.xml
sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java
sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java
sonar-batch/src/main/java/org/sonar/batch/index/Bucket.java
sonar-batch/src/main/java/org/sonar/batch/index/Cache.java
sonar-batch/src/main/java/org/sonar/batch/index/Caches.java
sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java
sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
sonar-batch/src/main/java/org/sonar/batch/index/PersistenceManager.java
sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java
sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/measure/package-info.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java
sonar-batch/src/test/java/org/sonar/batch/index/BucketTest.java
sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java
sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/SonarIndex.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/MeasuresFilters.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/RuleMeasure.java
sonar-plugin-api/src/main/java/org/sonar/api/rules/Rule.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Characteristic.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Requirement.java
sonar-plugin-api/src/test/java/org/sonar/api/measures/MeasuresFiltersTest.java
sonar-plugin-api/src/test/java/org/sonar/api/test/IsRuleMeasure.java

index 05ad24edf1c076ec75d756eb403449f644d1465c..00c145353dd869a31f16fe62486d1624edc461d0 100644 (file)
 package org.sonar.plugins.core.issue;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.*;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Sets;
 import org.apache.commons.lang.time.DateUtils;
-import org.sonar.api.batch.*;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
 import org.sonar.api.component.ResourcePerspectives;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.issue.Issue;
-import org.sonar.api.measures.*;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.RuleMeasure;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RulePriority;
@@ -38,7 +54,12 @@ import org.sonar.batch.components.TimeMachineConfiguration;
 
 import javax.annotation.Nullable;
 
-import java.util.*;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Computes metrics related to number of issues.
@@ -80,7 +101,7 @@ public class CountUnresolvedIssuesDecorator implements Decorator {
       CoreMetrics.OPEN_ISSUES,
       CoreMetrics.REOPENED_ISSUES,
       CoreMetrics.CONFIRMED_ISSUES
-    );
+      );
   }
 
   public void decorate(Resource resource, DecoratorContext context) {
@@ -90,7 +111,7 @@ public class CountUnresolvedIssuesDecorator implements Decorator {
       boolean shouldSaveNewMetrics = shouldSaveNewMetrics(context);
 
       Multiset<RulePriority> severityBag = HashMultiset.create();
-      Map<RulePriority, Multiset<Rule>> rulesPerSeverity = Maps.newHashMap();
+      Map<RulePriority, Multiset<RuleKey>> rulesPerSeverity = Maps.newHashMap();
       ListMultimap<RulePriority, Issue> issuesPerSeverity = ArrayListMultimap.create();
       int countOpen = 0;
       int countReopened = 0;
@@ -98,8 +119,8 @@ public class CountUnresolvedIssuesDecorator implements Decorator {
 
       for (Issue issue : issues) {
         severityBag.add(RulePriority.valueOf(issue.severity()));
-        Multiset<Rule> rulesBag = initRules(rulesPerSeverity, RulePriority.valueOf(issue.severity()));
-        rulesBag.add(rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule()));
+        Multiset<RuleKey> rulesBag = initRules(rulesPerSeverity, RulePriority.valueOf(issue.severity()));
+        rulesBag.add(issue.ruleKey());
         issuesPerSeverity.put(RulePriority.valueOf(issue.severity()), issue);
 
         if (Issue.STATUS_OPEN.equals(issue.status())) {
@@ -159,22 +180,22 @@ public class CountUnresolvedIssuesDecorator implements Decorator {
     }
   }
 
-  private void saveIssuesPerRules(DecoratorContext context, RulePriority severity, Map<RulePriority, Multiset<Rule>> rulesPerSeverity) {
+  private void saveIssuesPerRules(DecoratorContext context, RulePriority severity, Map<RulePriority, Multiset<RuleKey>> rulesPerSeverity) {
     Metric metric = SeverityUtils.severityToIssueMetric(severity);
 
     Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
     for (Measure child : children) {
       RuleMeasure childRuleMeasure = (RuleMeasure) child;
-      Rule rule = childRuleMeasure.getRule();
-      if (rule != null && MeasureUtils.hasValue(childRuleMeasure)) {
-        Multiset<Rule> rulesBag = initRules(rulesPerSeverity, severity);
-        rulesBag.add(rule, childRuleMeasure.getIntValue());
+      RuleKey ruleKey = childRuleMeasure.ruleKey();
+      if (ruleKey != null && MeasureUtils.hasValue(childRuleMeasure)) {
+        Multiset<RuleKey> rulesBag = initRules(rulesPerSeverity, severity);
+        rulesBag.add(ruleKey, childRuleMeasure.getIntValue());
       }
     }
 
-    Multiset<Rule> rulesBag = rulesPerSeverity.get(severity);
+    Multiset<RuleKey> rulesBag = rulesPerSeverity.get(severity);
     if (rulesBag != null) {
-      for (Multiset.Entry<Rule> entry : rulesBag.entrySet()) {
+      for (Multiset.Entry<RuleKey> entry : rulesBag.entrySet()) {
         RuleMeasure measure = RuleMeasure.createForRule(metric, entry.getElement(), (double) entry.getCount());
         measure.setSeverity(severity);
         context.saveMeasure(measure);
@@ -185,34 +206,34 @@ public class CountUnresolvedIssuesDecorator implements Decorator {
   private void saveNewIssuesPerRule(DecoratorContext context, RulePriority severity, Collection<Issue> issues, boolean shouldSaveNewMetrics) {
     if (shouldSaveNewMetrics) {
       Metric metric = SeverityUtils.severityToNewMetricIssue(severity);
-      ListMultimap<Rule, Measure> childMeasuresPerRule = ArrayListMultimap.create();
-      ListMultimap<Rule, Issue> issuesPerRule = ArrayListMultimap.create();
-      Set<Rule> rules = Sets.newHashSet();
+      ListMultimap<RuleKey, Measure> childMeasuresPerRuleKeys = ArrayListMultimap.create();
+      ListMultimap<RuleKey, Issue> issuesPerRuleKeys = ArrayListMultimap.create();
+      Set<RuleKey> ruleKeys = Sets.newHashSet();
 
       Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
       for (Measure child : children) {
         RuleMeasure childRuleMeasure = (RuleMeasure) child;
-        Rule rule = childRuleMeasure.getRule();
-        if (rule != null) {
-          childMeasuresPerRule.put(rule, childRuleMeasure);
-          rules.add(rule);
+        RuleKey ruleKey = childRuleMeasure.ruleKey();
+        if (ruleKey != null) {
+          childMeasuresPerRuleKeys.put(ruleKey, childRuleMeasure);
+          ruleKeys.add(ruleKey);
         }
       }
 
       for (Issue issue : issues) {
         if (RulePriority.valueOf(issue.severity()).equals(severity)) {
-          Rule rule = rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule());
-          rules.add(rule);
-          issuesPerRule.put(rule, issue);
+          ruleKeys.add(issue.ruleKey());
+          issuesPerRuleKeys.put(issue.ruleKey(), issue);
         }
       }
 
-      for (Rule rule : rules) {
+      for (RuleKey ruleKey : ruleKeys) {
+        Rule rule = rulefinder.findByKey(ruleKey);
         RuleMeasure measure = RuleMeasure.createForRule(metric, rule, null);
         measure.setSeverity(severity);
         for (Period period : timeMachineConfiguration.periods()) {
           int variationIndex = period.getIndex();
-          double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRule.get(rule)) + countIssues(issuesPerRule.get(rule), period);
+          double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRuleKeys.get(rule.ruleKey())) + countIssues(issuesPerRuleKeys.get(rule.ruleKey()), period);
           measure.setVariation(variationIndex, sum);
         }
         context.saveMeasure(measure);
@@ -242,8 +263,8 @@ public class CountUnresolvedIssuesDecorator implements Decorator {
     return sum;
   }
 
-  private Multiset<Rule> initRules(Map<RulePriority, Multiset<Rule>> rulesPerSeverity, RulePriority severity) {
-    Multiset<Rule> rulesBag = rulesPerSeverity.get(severity);
+  private Multiset<RuleKey> initRules(Map<RulePriority, Multiset<RuleKey>> rulesPerSeverity, RulePriority severity) {
+    Multiset<RuleKey> rulesBag = rulesPerSeverity.get(severity);
     if (rulesBag == null) {
       rulesBag = HashMultiset.create();
       rulesPerSeverity.put(severity, rulesBag);
index c824537cecea47a21d5c8128ca73ef77794774f4..1e20c12f99431be39311d9418c6480a2f5b7707c 100644 (file)
@@ -24,7 +24,13 @@ import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.time.DateUtils;
-import org.sonar.api.batch.*;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.TimeMachine;
+import org.sonar.api.batch.TimeMachineQuery;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.MetricFinder;
@@ -32,6 +38,7 @@ import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.Scopes;
 import org.sonar.batch.components.PeriodsDefinition;
+import org.sonar.batch.index.DefaultIndex;
 import org.sonar.core.DryRunIncompatible;
 
 import java.util.List;
@@ -46,9 +53,11 @@ public class TendencyDecorator implements Decorator {
   private TimeMachineQuery query;
   private TendencyAnalyser analyser;
   private List<Metric> metrics;
+  private final DefaultIndex index;
 
-  public TendencyDecorator(TimeMachine timeMachine, MetricFinder metricFinder) {
+  public TendencyDecorator(TimeMachine timeMachine, MetricFinder metricFinder, DefaultIndex index) {
     this.timeMachine = timeMachine;
+    this.index = index;
     this.analyser = new TendencyAnalyser();
     this.metrics = Lists.newLinkedList();
     for (Metric metric : metricFinder.findAll()) {
@@ -58,10 +67,11 @@ public class TendencyDecorator implements Decorator {
     }
   }
 
-  TendencyDecorator(TimeMachine timeMachine, TimeMachineQuery query, TendencyAnalyser analyser) {
+  TendencyDecorator(TimeMachine timeMachine, TimeMachineQuery query, TendencyAnalyser analyser, DefaultIndex index) {
     this.timeMachine = timeMachine;
     this.query = query;
     this.analyser = analyser;
+    this.index = index;
   }
 
   @DependsUpon
@@ -108,7 +118,7 @@ public class TendencyDecorator implements Decorator {
           values.add(measure.getValue());
 
           measure.setTendency(analyser.analyseLevel(valuesPerMetric.get(metric)));
-          context.saveMeasure(measure);
+          index.updateMeasure(resource, measure);
         }
       }
     }
index efebe9d7a62401a1d558ce5ee44cb653f5e2749d..be6d1a8f35edb31fd5040f8251559d5272442731 100644 (file)
@@ -21,16 +21,26 @@ package org.sonar.plugins.core.timemachine;
 
 import com.google.common.collect.Maps;
 import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.*;
-import org.sonar.api.measures.*;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.measures.RuleMeasure;
 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.RuleFinder;
 import org.sonar.api.technicaldebt.batch.Characteristic;
 import org.sonar.batch.components.PastMeasuresLoader;
 import org.sonar.batch.components.PastSnapshot;
 import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.DefaultIndex;
 
 import java.util.Collection;
 import java.util.List;
@@ -42,16 +52,20 @@ public class VariationDecorator implements Decorator {
   private List<PastSnapshot> projectPastSnapshots;
   private MetricFinder metricFinder;
   private PastMeasuresLoader pastMeasuresLoader;
+  private RuleFinder ruleFinder;
+  private final DefaultIndex sonarIndex;
 
-
-  public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, TimeMachineConfiguration timeMachineConfiguration) {
-    this(pastMeasuresLoader, metricFinder, timeMachineConfiguration.getProjectPastSnapshots());
+  public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, TimeMachineConfiguration timeMachineConfiguration, RuleFinder ruleFinder,
+    DefaultIndex index) {
+    this(pastMeasuresLoader, metricFinder, timeMachineConfiguration.getProjectPastSnapshots(), ruleFinder, index);
   }
 
-  VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots) {
+  VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots, RuleFinder ruleFinder, DefaultIndex index) {
     this.pastMeasuresLoader = pastMeasuresLoader;
     this.projectPastSnapshots = projectPastSnapshots;
     this.metricFinder = metricFinder;
+    this.ruleFinder = ruleFinder;
+    this.sonarIndex = index;
   }
 
   public boolean shouldExecuteOnProject(Project project) {
@@ -82,10 +96,10 @@ public class VariationDecorator implements Decorator {
 
   private void computeVariation(Resource resource, DecoratorContext context, PastSnapshot pastSnapshot) {
     List<Object[]> pastMeasures = pastMeasuresLoader.getPastMeasures(resource, pastSnapshot);
-    compareWithPastMeasures(context, pastSnapshot.getIndex(), pastMeasures);
+    compareWithPastMeasures(resource, context, pastSnapshot.getIndex(), pastMeasures);
   }
 
-  void compareWithPastMeasures(DecoratorContext context, int index, List<Object[]> pastMeasures) {
+  void compareWithPastMeasures(Resource resource, DecoratorContext context, int index, List<Object[]> pastMeasures) {
     Map<MeasureKey, Object[]> pastMeasuresByKey = Maps.newHashMap();
     for (Object[] pastMeasure : pastMeasures) {
       pastMeasuresByKey.put(new MeasureKey(pastMeasure), pastMeasure);
@@ -98,11 +112,11 @@ public class VariationDecorator implements Decorator {
       Characteristic characteristic = measure.getCharacteristic();
       Integer characteristicId = characteristic != null ? characteristic.id() : null;
       Integer personId = measure.getPersonId();
-      Integer ruleId = measure instanceof RuleMeasure ? ((RuleMeasure) measure).getRule().getId() : null;
+      Integer ruleId = measure instanceof RuleMeasure ? ruleFinder.findByKey(((RuleMeasure) measure).ruleKey()).getId() : null;
 
       Object[] pastMeasure = pastMeasuresByKey.get(new MeasureKey(metricId, characteristicId, personId, ruleId));
       if (updateVariation(measure, pastMeasure, index)) {
-        context.saveMeasure(measure);
+        sonarIndex.updateMeasure(resource, measure);
       }
     }
   }
index 41241336bd0089178aa4965d935514b869f1a884..600f455ca21dd4f504a5aed457aa727105132aa0 100644 (file)
@@ -31,7 +31,11 @@ import org.sonar.api.component.ResourcePerspectives;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.measures.*;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.RuleMeasure;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.Scopes;
@@ -55,7 +59,12 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyDouble;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 public class CountUnresolvedIssuesDecoratorTest {
 
@@ -83,9 +92,9 @@ public class CountUnresolvedIssuesDecoratorTest {
     ruleB1 = Rule.create().setRepositoryKey("ruleB1").setKey("ruleB1").setName("nameB1");
 
     ruleFinder = mock(RuleFinder.class);
-    when(ruleFinder.findByKey(ruleA1.getRepositoryKey(), ruleA1.getKey())).thenReturn(ruleA1);
-    when(ruleFinder.findByKey(ruleA2.getRepositoryKey(), ruleA2.getKey())).thenReturn(ruleA2);
-    when(ruleFinder.findByKey(ruleB1.getRepositoryKey(), ruleB1.getKey())).thenReturn(ruleB1);
+    when(ruleFinder.findByKey(ruleA1.ruleKey())).thenReturn(ruleA1);
+    when(ruleFinder.findByKey(ruleA2.ruleKey())).thenReturn(ruleA2);
+    when(ruleFinder.findByKey(ruleB1.ruleKey())).thenReturn(ruleB1);
 
     rightNow = new Date();
     tenDaysAgo = DateUtils.addDays(rightNow, -10);
@@ -324,7 +333,7 @@ public class CountUnresolvedIssuesDecoratorTest {
       }
       RuleMeasure m = (RuleMeasure) o;
       return ObjectUtils.equals(metric, m.getMetric()) &&
-        ObjectUtils.equals(rule, m.getRule()) &&
+        ObjectUtils.equals(rule.ruleKey(), m.ruleKey()) &&
         ObjectUtils.equals(var1, m.getVariation1()) &&
         ObjectUtils.equals(var2, m.getVariation2());
     }
index 8713c66537b226a8555c62cd984100d5cf60f61e..a60dfbb5e9e2f76621ddc3cd3e69b0ddd5acdb26 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MetricFinder;
 import org.sonar.api.resources.Directory;
 import org.sonar.api.resources.Project;
+import org.sonar.batch.index.DefaultIndex;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -53,7 +54,7 @@ public class TendencyDecoratorTest {
     MetricFinder metricFinder = mock(MetricFinder.class);
     when(metricFinder.findAll()).thenReturn(Arrays.asList(CoreMetrics.LINES, CoreMetrics.COVERAGE, CoreMetrics.COVERAGE_LINE_HITS_DATA, CoreMetrics.PROFILE));
 
-    TendencyDecorator decorator = new TendencyDecorator(null, metricFinder);
+    TendencyDecorator decorator = new TendencyDecorator(null, metricFinder, mock(DefaultIndex.class));
 
     TimeMachineQuery query = decorator.initQuery(project);
     assertThat(query.getMetrics().size(), is(2));
@@ -80,7 +81,7 @@ public class TendencyDecoratorTest {
     when(context.getMeasure(CoreMetrics.LINES)).thenReturn(new Measure(CoreMetrics.LINES, 1400.0));
     when(context.getMeasure(CoreMetrics.COVERAGE)).thenReturn(new Measure(CoreMetrics.LINES, 90.0));
 
-    TendencyDecorator decorator = new TendencyDecorator(timeMachine, query, analyser);
+    TendencyDecorator decorator = new TendencyDecorator(timeMachine, query, analyser, mock(DefaultIndex.class));
     decorator.decorate(new Directory("org/foo"), context);
 
     verify(analyser).analyseLevel(Arrays.asList(1200.0, 1300.0, 1150.0, 1400.0));
@@ -99,7 +100,7 @@ public class TendencyDecoratorTest {
       ));
 
     DecoratorContext context = mock(DecoratorContext.class);
-    TendencyDecorator decorator = new TendencyDecorator(timeMachine, query, analyser);
+    TendencyDecorator decorator = new TendencyDecorator(timeMachine, query, analyser, mock(DefaultIndex.class));
     decorator.decorate(new Directory("org/foo"), context);
 
     verify(analyser, never()).analyseLevel(anyList());
index fe48596751c53e028bf31c61e8aabdd93d343883..971d6d9f99fa43c5d4d0532602bbe6533ced8704 100644 (file)
@@ -32,15 +32,18 @@ import org.sonar.api.resources.File;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
 import org.sonar.batch.components.PastMeasuresLoader;
 import org.sonar.batch.components.PastSnapshot;
 import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.DefaultIndex;
 import org.sonar.jpa.test.AbstractDbUnitTestCase;
 
 import java.util.Arrays;
 import java.util.Date;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -60,7 +63,8 @@ public class VariationDecoratorTest extends AbstractDbUnitTestCase {
   @Test
   public void shouldComputeVariations() {
     TimeMachineConfiguration timeMachineConfiguration = mock(TimeMachineConfiguration.class);
-    VariationDecorator decorator = new VariationDecorator(mock(PastMeasuresLoader.class), mock(MetricFinder.class), timeMachineConfiguration);
+    VariationDecorator decorator = new VariationDecorator(mock(PastMeasuresLoader.class), mock(MetricFinder.class), timeMachineConfiguration, mock(RuleFinder.class),
+      mock(DefaultIndex.class));
 
     assertThat(decorator.shouldComputeVariation(new Project("foo"))).isTrue();
     assertThat(decorator.shouldComputeVariation(new File("foo/bar.c"))).isFalse();
@@ -89,11 +93,13 @@ public class VariationDecoratorTest extends AbstractDbUnitTestCase {
     Measure currentCoverage = newMeasure(COVERAGE, 80.0);
     when(context.getMeasures(Matchers.<MeasuresFilter>anyObject())).thenReturn(Arrays.asList(currentNcloc, currentCoverage));
 
-    VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class), Arrays.asList(pastSnapshot1, pastSnapshot3));
+    DefaultIndex index = mock(DefaultIndex.class);
+    VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class), Arrays.asList(pastSnapshot1, pastSnapshot3), mock(RuleFinder.class),
+      index);
     decorator.decorate(dir, context);
 
     // context updated for each variation : 2 times for ncloc and 1 time for coverage
-    verify(context, times(3)).saveMeasure(Matchers.<Measure>anyObject());
+    verify(index, times(3)).updateMeasure(eq(dir), Matchers.<Measure>anyObject());
 
     assertThat(currentNcloc.getVariation1()).isEqualTo(20.0);
     assertThat(currentNcloc.getVariation2()).isNull();
@@ -106,11 +112,16 @@ public class VariationDecoratorTest extends AbstractDbUnitTestCase {
 
   @Test
   public void shouldComputeVariationOfRuleMeasures() {
-    Rule rule1 = Rule.create();
+    RuleFinder ruleFinder = mock(RuleFinder.class);
+
+    Rule rule1 = Rule.create("repo", "rule1");
     rule1.setId(1);
-    Rule rule2 = Rule.create();
+    Rule rule2 = Rule.create("repo", "rule2");
     rule2.setId(2);
 
+    when(ruleFinder.findByKey(rule1.ruleKey())).thenReturn(rule1);
+    when(ruleFinder.findByKey(rule2.ruleKey())).thenReturn(rule2);
+
     Resource dir = new Directory("org/foo");
 
     PastMeasuresLoader pastMeasuresLoader = mock(PastMeasuresLoader.class);
@@ -129,11 +140,13 @@ public class VariationDecoratorTest extends AbstractDbUnitTestCase {
     Measure violationsRule2 = RuleMeasure.createForRule(VIOLATIONS, rule2, 70.0);
     when(context.getMeasures(Matchers.<MeasuresFilter>anyObject())).thenReturn(Arrays.asList(violations, violationsRule1, violationsRule2));
 
-    VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class), Arrays.asList(pastSnapshot1));
+    DefaultIndex index = mock(DefaultIndex.class);
+    VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class), Arrays.asList(pastSnapshot1), ruleFinder,
+      index);
     decorator.decorate(dir, context);
 
     // context updated for each variation
-    verify(context, times(3)).saveMeasure(Matchers.<Measure>anyObject());
+    verify(index, times(3)).updateMeasure(eq(dir), Matchers.<Measure>anyObject());
 
     assertThat(violations.getVariation1()).isEqualTo(20.0);
   }
diff --git a/pom.xml b/pom.xml
index b1053d36b2f79a9016587f3cc72de75bd7095b67..de91bc59576082169dbb27c6f2bdbcb5772c1a49 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <groupId>com.akiban</groupId>
         <artifactId>akiban-persistit</artifactId>
         <!-- do not use 3.3.0. It was build only for Java 7. Waiting for 3.3.1 to be deployed in central repo -->
-        <version>3.2.7</version>
+        <version>3.3.0</version>
         <exclusions>
           <exclusion>
             <groupId>commons-logging</groupId>
           </exclusion>
         </exclusions>
       </dependency>
+      <dependency>
+        <groupId>com.esotericsoftware.kryo</groupId>
+        <artifactId>kryo</artifactId>
+        <version>2.23.0</version>
+      </dependency>
       <dependency>
         <groupId>com.github.kevinsawicki</groupId>
         <artifactId>http-request</artifactId>
index 8e527478c01118590111de4d3a946b608d67544c..dae636cb3e737dae08002afd1028499d53b5195d 100644 (file)
       <groupId>com.akiban</groupId>
       <artifactId>akiban-persistit</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.esotericsoftware.kryo</groupId>
+      <artifactId>kryo</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.codehaus.sonar</groupId>
       <artifactId>sonar-core</artifactId>
index 466be91dc79ec33b4c6ca85091eebb9296fb7cbb..565417efad0b56885319a45708b963a04153899a 100644 (file)
@@ -38,7 +38,12 @@ import org.sonar.batch.index.DefaultIndex;
 import javax.annotation.Nullable;
 import javax.persistence.Query;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class DefaultTimeMachine implements TimeMachine {
 
@@ -155,7 +160,6 @@ public class DefaultTimeMachine implements TimeMachine {
   static Measure toMeasure(MeasureModel model, Metric metric, @Nullable Characteristic characteristic) {
     // NOTE: measures on rule are not supported
     Measure measure = new Measure(metric);
-    measure.setId(model.getId());
     measure.setDescription(model.getDescription());
     measure.setValue(model.getValue());
     measure.setData(model.getData(metric));
index 71e9cc36a1c27b3a4b82e20b0d6ae3671df16393..1747593630821d82fadb6df466b5cec132093dfc 100644 (file)
@@ -48,6 +48,7 @@ import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -110,7 +111,7 @@ public final class DebtDecorator implements Decorator {
     for (Measure measure : context.getChildrenMeasures(MeasuresFilters.rules(CoreMetrics.TECHNICAL_DEBT))) {
       Long debt = measure.getValue().longValue();
       RuleMeasure ruleMeasure = (RuleMeasure) measure;
-      total += computeDebt(debt, ruleMeasure.getRule().ruleKey(), ruleDebts, characteristicDebts);
+      total += computeDebt(debt, ruleMeasure.ruleKey(), ruleDebts, characteristicDebts);
     }
 
     context.saveMeasure(CoreMetrics.TECHNICAL_DEBT, total.doubleValue());
index af6a87ae8a662401f4c642a8d9f998c9e3a20949..ea4ed3b11a0219c58e420c8b98d39ccdfc8b7625 100644 (file)
  */
 package org.sonar.batch.index;
 
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.MeasuresFilter;
-import org.sonar.api.measures.MeasuresFilters;
 import org.sonar.api.resources.Resource;
-import org.sonar.api.utils.SonarException;
 
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
 public final class Bucket {
 
   private Resource resource;
-  private ListMultimap<String, Measure> measuresByMetric = ArrayListMultimap.create();
 
   private Bucket parent;
   private List<Bucket> children;
@@ -79,30 +70,7 @@ public final class Bucket {
     return parent;
   }
 
-  public void addMeasure(Measure measure) {
-    List<Measure> metricMeasures = measuresByMetric.get(measure.getMetric().getKey());
-
-    boolean add = true;
-    if (metricMeasures != null) {
-      int index = metricMeasures.indexOf(measure);
-      if (index > -1) {
-        if (metricMeasures.get(index) == measure) {
-          add = false;
-        } else if (measure.getMetric().equals(CoreMetrics.TESTS)) {
-          // Hack for SONAR-5212
-          measuresByMetric.remove(measure.getMetric().getKey(), metricMeasures.get(index));
-        } else {
-          throw new SonarException("Can not add twice the same measure on " + resource + ": " + measure);
-        }
-      }
-    }
-    if (add) {
-      measuresByMetric.put(measure.getMetric().getKey(), measure);
-    }
-  }
-
   public void clear() {
-    measuresByMetric = null;
     children = null;
     if (parent != null) {
       parent.removeChild(this);
@@ -110,16 +78,6 @@ public final class Bucket {
     }
   }
 
-  public <M> M getMeasures(final MeasuresFilter<M> filter) {
-    Collection<Measure> unfiltered;
-    if (filter instanceof MeasuresFilters.MetricFilter) {
-      unfiltered = measuresByMetric.get(((MeasuresFilters.MetricFilter) filter).filterOnMetricKey());
-    } else {
-      unfiltered = measuresByMetric.values();
-    }
-    return filter.filter(unfiltered);
-  }
-
   @Override
   public boolean equals(Object o) {
     if (this == o) {
index 46f6bbf0a84f698c909340f174ad269421b272b9..39815dcfcb355f5191fa1d91e5769f9c85a2e31a 100644 (file)
@@ -26,6 +26,7 @@ import com.persistit.exception.PersistitException;
 import org.apache.commons.lang.builder.ToStringBuilder;
 
 import javax.annotation.CheckForNull;
+
 import java.io.Serializable;
 import java.util.Iterator;
 import java.util.Set;
@@ -219,7 +220,6 @@ public class Cache<V extends Serializable> {
     }
   }
 
-
   /**
    * Clears the default as well as all group caches.
    */
@@ -293,6 +293,20 @@ public class Cache<V extends Serializable> {
     }
   }
 
+  /**
+   * Lazy-loading values for given keys
+   */
+  public Iterable<V> values(Object firstKey, Object secondKey) {
+    try {
+      exchange.clear();
+      exchange.append(firstKey).append(secondKey).append(Key.BEFORE);
+      Exchange iteratorExchange = new Exchange(exchange);
+      return new ValueIterable<V>(iteratorExchange, false);
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to get values from cache " + name, e);
+    }
+  }
+
   /**
    * Lazy-loading values for a given key
    */
@@ -352,7 +366,6 @@ public class Cache<V extends Serializable> {
     }
   }
 
-
   //
   // LAZY ITERATORS AND ITERABLES
   //
index f440db4da246494cc7c1a03bc562c01ddb01e5c0..55e2de064951c29c63ccc6f4d2126a8647ca1457 100644 (file)
  */
 package org.sonar.batch.index;
 
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Sets;
 import com.persistit.Exchange;
 import com.persistit.Persistit;
+import com.persistit.Value;
 import com.persistit.Volume;
+import com.persistit.encoding.CoderContext;
+import com.persistit.encoding.ValueCoder;
 import com.persistit.exception.PersistitException;
 import com.persistit.logging.Slf4jAdapter;
 import org.apache.commons.io.FileUtils;
+import org.objenesis.strategy.SerializingInstantiatorStrategy;
 import org.picocontainer.Startable;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.BatchComponent;
+import org.sonar.api.measures.Measure;
 import org.sonar.api.utils.TempFolder;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.Serializable;
 import java.util.Properties;
 import java.util.Set;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterInputStream;
 
 /**
  * Factory of caches
@@ -49,6 +61,7 @@ public class Caches implements BatchComponent, Startable {
   private Persistit persistit;
   private Volume volume;
   private final TempFolder tempFolder;
+  private Kryo kryo;
 
   public Caches(TempFolder tempFolder) {
     this.tempFolder = tempFolder;
@@ -60,6 +73,8 @@ public class Caches implements BatchComponent, Startable {
       tempDir = tempFolder.newDir("caches");
       persistit = new Persistit();
       persistit.setPersistitLogger(new Slf4jAdapter(LoggerFactory.getLogger("PERSISTIT")));
+      kryo = new Kryo();
+      kryo.setInstantiatorStrategy(new SerializingInstantiatorStrategy());
       Properties props = new Properties();
       props.setProperty("datapath", tempDir.getAbsolutePath());
       props.setProperty("logpath", "${datapath}/log");
@@ -70,6 +85,7 @@ public class Caches implements BatchComponent, Startable {
       props.setProperty("volume.1", "${datapath}/persistit,create,pageSize:8192,initialPages:10,extensionPages:100,maximumPages:25000");
       persistit.setProperties(props);
       persistit.initialize();
+      persistit.getCoderManager().registerValueCoder(Measure.class, new MeasureValueCoder());
       volume = persistit.createTemporaryVolume();
 
     } catch (Exception e) {
@@ -77,6 +93,28 @@ public class Caches implements BatchComponent, Startable {
     }
   }
 
+  /**
+   * Special value coder for measures because measure_data field may be too big for persitit. We use Kryo
+   * with deflate compression as workaround.
+   *
+   */
+  private class MeasureValueCoder implements ValueCoder {
+    public void put(Value value, Object object, CoderContext context) {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      Output output = new Output(new DeflaterOutputStream(baos));
+      kryo.writeObject(output, object);
+      output.close();
+      value.putByteArray(baos.toByteArray());
+    }
+
+    public Object get(Value value, Class clazz, CoderContext context) {
+      Input input = new Input(new InflaterInputStream(new ByteArrayInputStream(value.getByteArray())));
+      Object someObject = kryo.readObject(input, clazz);
+      input.close();
+      return someObject;
+    }
+  }
+
   public <V extends Serializable> Cache<V> createCache(String cacheName) {
     Preconditions.checkState(volume != null && volume.isOpened(), "Caches are not initialized");
     Preconditions.checkState(!cacheNames.contains(cacheName), "Cache is already created: " + cacheName);
index d5e42948fc5f51b937dcbf33229c2840ac65a425..dc0bac9be7e46a42a4126fc18b5a4d03d17e6b6b 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.api.batch.SonarIndex;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.design.Dependency;
+import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.MeasuresFilters;
@@ -52,12 +53,15 @@ import org.sonar.api.violations.ViolationQuery;
 import org.sonar.batch.ProjectTree;
 import org.sonar.batch.issue.DeprecatedViolations;
 import org.sonar.batch.issue.ModuleIssues;
+import org.sonar.batch.qualitygate.QualityGateVerifier;
+import org.sonar.batch.scan.measure.MeasureCache;
 import org.sonar.core.component.ComponentKeys;
 import org.sonar.core.component.ScanGraph;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -85,17 +89,19 @@ public class DefaultIndex extends SonarIndex {
   private ProjectTree projectTree;
   private final DeprecatedViolations deprecatedViolations;
   private ModuleIssues moduleIssues;
+  private final MeasureCache measureCache;
 
   private ResourceKeyMigration migration;
 
   public DefaultIndex(PersistenceManager persistence, ProjectTree projectTree, MetricFinder metricFinder,
-    ScanGraph graph, DeprecatedViolations deprecatedViolations, ResourceKeyMigration migration) {
+    ScanGraph graph, DeprecatedViolations deprecatedViolations, ResourceKeyMigration migration, MeasureCache measureCache) {
     this.persistence = persistence;
     this.projectTree = projectTree;
     this.metricFinder = metricFinder;
     this.graph = graph;
     this.deprecatedViolations = deprecatedViolations;
     this.migration = migration;
+    this.measureCache = measureCache;
   }
 
   public void start() {
@@ -174,26 +180,23 @@ public class DefaultIndex extends SonarIndex {
 
   @Override
   public Measure getMeasure(Resource resource, Metric metric) {
-    Bucket bucket = buckets.get(resource);
-    if (bucket != null) {
-      return bucket.getMeasures(MeasuresFilters.metric(metric));
-    }
-    return null;
+    return getMeasures(resource, MeasuresFilters.metric(metric));
   }
 
   @Override
   public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) {
-    Bucket bucket = buckets.get(resource);
-    if (bucket != null) {
-      // TODO the data measures which are not kept in memory are not reloaded yet. Use getMeasure().
-      return bucket.getMeasures(filter);
+    // Reload resource so that effective key is populated
+    Resource indexedResource = getResource(resource);
+    Iterable<Measure> unfiltered = measureCache.byResource(indexedResource);
+    Collection<Measure> all = new ArrayList<Measure>();
+    if (unfiltered != null) {
+      for (Measure measure : unfiltered) {
+        all.add(measure);
+      }
     }
-    return null;
+    return filter.filter(all);
   }
 
-  /**
-   * the measure is updated if it's already registered.
-   */
   @Override
   public Measure addMeasure(Resource resource, Measure measure) {
     Bucket bucket = getBucket(resource);
@@ -203,15 +206,27 @@ public class DefaultIndex extends SonarIndex {
         throw new SonarException("Unknown metric: " + measure.getMetricKey());
       }
       measure.setMetric(metric);
-      bucket.addMeasure(measure);
-
-      if (measure.getPersistenceMode().useDatabase()) {
-        persistence.saveMeasure(bucket.getResource(), measure);
+      if (measureCache.contains(resource, measure)
+        // Hack for SONAR-5212
+        && !measure.getMetric().equals(CoreMetrics.TESTS)) {
+        throw new SonarException("Can not add twice the same measure on " + resource + ": " + measure);
       }
+      measureCache.put(resource, measure);
     }
     return measure;
   }
 
+  /**
+   * Used by some core features like TendencyDecorator, {@link QualityGateVerifier}, VariationDecorator 
+   * that need to update some existing measures
+   */
+  public void updateMeasure(Resource resource, Measure measure) {
+    if (!measureCache.contains(resource, measure)) {
+      throw new SonarException("Can't update measure on " + resource + ": " + measure);
+    }
+    measureCache.put(resource, measure);
+  }
+
   //
   //
   //
index 9dd3460e0ff286c7e825fbed0c89ef82918b9d57..39b9d8d472a32e7d7a6c622a906f9507832e6e42 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.batch.index;
 import org.sonar.api.batch.Event;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.design.Dependency;
-import org.sonar.api.measures.Measure;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.ProjectLink;
 import org.sonar.api.resources.Resource;
@@ -34,17 +33,14 @@ public final class DefaultPersistenceManager implements PersistenceManager {
 
   private ResourcePersister resourcePersister;
   private SourcePersister sourcePersister;
-  private MeasurePersister measurePersister;
   private DependencyPersister dependencyPersister;
   private LinkPersister linkPersister;
   private EventPersister eventPersister;
 
   public DefaultPersistenceManager(ResourcePersister resourcePersister, SourcePersister sourcePersister,
-    MeasurePersister measurePersister, DependencyPersister dependencyPersister,
-    LinkPersister linkPersister, EventPersister eventPersister) {
+    DependencyPersister dependencyPersister, LinkPersister linkPersister, EventPersister eventPersister) {
     this.resourcePersister = resourcePersister;
     this.sourcePersister = sourcePersister;
-    this.measurePersister = measurePersister;
     this.dependencyPersister = dependencyPersister;
     this.linkPersister = linkPersister;
     this.eventPersister = eventPersister;
@@ -55,14 +51,6 @@ public final class DefaultPersistenceManager implements PersistenceManager {
     sourcePersister.clear();
   }
 
-  public void setDelayedMode(boolean b) {
-    measurePersister.setDelayedMode(b);
-  }
-
-  public void dump() {
-    measurePersister.dump();
-  }
-
   public void saveProject(Project project, Project parent) {
     resourcePersister.saveProject(project, parent);
   }
@@ -82,12 +70,6 @@ public final class DefaultPersistenceManager implements PersistenceManager {
     return sourcePersister.getSource(resource);
   }
 
-  public void saveMeasure(Resource resource, Measure measure) {
-    if (ResourceUtils.isPersistable(resource)) {
-      measurePersister.saveMeasure(resource, measure);
-    }
-  }
-
   public void saveDependency(Project project, Dependency dependency, Dependency parentDependency) {
     if (ResourceUtils.isPersistable(dependency.getFrom()) && ResourceUtils.isPersistable(dependency.getTo())) {
       dependencyPersister.saveDependency(project, dependency, parentDependency);
index e394f3199ee4de5e980a6397d7801ad3e57172ac..09dfd493df2ddbea783286d05d1f64cca418c66e 100644 (file)
 package org.sonar.batch.index;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.SetMultimap;
 import org.apache.ibatis.session.SqlSession;
-import org.slf4j.LoggerFactory;
 import org.sonar.api.database.model.MeasureMapper;
 import org.sonar.api.database.model.MeasureModel;
 import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.RuleMeasure;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.technicaldebt.batch.Characteristic;
 import org.sonar.api.utils.SonarException;
+import org.sonar.batch.index.Cache.Entry;
+import org.sonar.batch.scan.measure.MeasureCache;
 import org.sonar.core.persistence.MyBatis;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-public final class MeasurePersister {
+public final class MeasurePersister implements ScanPersister {
   private final MyBatis mybatis;
-  private final ResourcePersister resourcePersister;
   private final RuleFinder ruleFinder;
-  private final SetMultimap<Resource, Measure> unsavedMeasuresByResource = LinkedHashMultimap.create();
-  private boolean delayedMode = false;
+  private final MeasureCache measureCache;
+  private final SnapshotCache snapshotCache;
+  private final ResourceCache resourceCache;
 
-  public MeasurePersister(MyBatis mybatis, ResourcePersister resourcePersister, RuleFinder ruleFinder) {
+  public MeasurePersister(MyBatis mybatis, RuleFinder ruleFinder,
+    MeasureCache measureCache, SnapshotCache snapshotCache, ResourceCache resourceCache) {
     this.mybatis = mybatis;
-    this.resourcePersister = resourcePersister;
     this.ruleFinder = ruleFinder;
+    this.measureCache = measureCache;
+    this.snapshotCache = snapshotCache;
+    this.resourceCache = resourceCache;
   }
 
-  public void setDelayedMode(boolean delayedMode) {
-    this.delayedMode = delayedMode;
-  }
+  @Override
+  public void persist() {
+    SqlSession session = mybatis.openSession();
+    try {
+      MeasureMapper mapper = session.getMapper(MeasureMapper.class);
 
-  public void dump() {
-    LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresByResource.size());
+      for (Entry<Measure> entry : measureCache.entries()) {
+        String effectiveKey = entry.key()[0].toString();
+        Measure measure = entry.value();
+        Resource resource = resourceCache.get(effectiveKey);
 
-    insert(getMeasuresToSave());
-  }
-
-  public void saveMeasure(Resource resource, Measure measure) {
-    if (shouldSaveLater(measure)) {
-      if (measure.getMetric().equals(CoreMetrics.TESTS) && unsavedMeasuresByResource.get(resource).contains(measure)) {
-        // Hack for SONAR-5212
-        unsavedMeasuresByResource.remove(resource, measure);
+        if (shouldPersistMeasure(resource, measure)) {
+          Snapshot snapshot = snapshotCache.get(effectiveKey);
+          MeasureModel measureModel = model(measure).setSnapshotId(snapshot.getId());
+          try {
+            mapper.insert(measureModel);
+          } catch (Exception e) {
+            // SONAR-4066
+            throw new SonarException(String.format("Unable to save measure for metric [%s] on component [%s]", measure.getMetricKey(), resource.getKey()), e);
+          }
+        }
       }
-      unsavedMeasuresByResource.put(resource, measure);
-      return;
-    }
-    try {
-      insertOrUpdate(resource, measure);
-    } catch (Exception e) {
-      // SONAR-4066
-      throw new SonarException(String.format("Unable to save measure for metric [%s] on component [%s]", measure.getMetricKey(), resource.getKey()), e);
-    }
-  }
 
-  private MeasureModel insertOrUpdate(Resource resource, Measure measure) {
-    Snapshot snapshot = resourcePersister.getSnapshotOrFail(resource);
-    if (measure.getId() != null) {
-      return update(measure, snapshot);
-    }
-    if (shouldPersistMeasure(resource, measure)) {
-      MeasureModel insert = insert(measure, snapshot);
-      measure.setId(insert.getId());
-      return insert;
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
     }
-    return null;
-  }
-
-  private boolean shouldSaveLater(Measure measure) {
-    return delayedMode && measure.getPersistenceMode().useMemory();
   }
 
   @VisibleForTesting
@@ -115,24 +97,6 @@ public final class MeasurePersister {
       || isNotEmpty;
   }
 
-  private List<MeasureModelAndDetails> getMeasuresToSave() {
-    List<MeasureModelAndDetails> measures = Lists.newArrayList();
-
-    Map<Resource, Collection<Measure>> map = unsavedMeasuresByResource.asMap();
-    for (Map.Entry<Resource, Collection<Measure>> entry : map.entrySet()) {
-      Resource resource = entry.getKey();
-      Snapshot snapshot = resourcePersister.getSnapshot(entry.getKey());
-      for (Measure measure : entry.getValue()) {
-        if (shouldPersistMeasure(resource, measure)) {
-          measures.add(new MeasureModelAndDetails(model(measure).setSnapshotId(snapshot.getId()), resource.getKey(), measure.getMetricKey()));
-        }
-      }
-    }
-
-    unsavedMeasuresByResource.clear();
-    return measures;
-  }
-
   private MeasureModel model(Measure measure) {
     MeasureModel model = new MeasureModel();
     // we assume that the index has updated the metric
@@ -162,9 +126,9 @@ public final class MeasurePersister {
     if (measure instanceof RuleMeasure) {
       RuleMeasure ruleMeasure = (RuleMeasure) measure;
       model.setRulePriority(ruleMeasure.getSeverity());
-      Rule rule = ruleMeasure.getRule();
-      if (rule != null) {
-        Rule ruleWithId = ruleFinder.findByKey(rule.getRepositoryKey(), rule.getKey());
+      RuleKey ruleKey = ruleMeasure.ruleKey();
+      if (ruleKey != null) {
+        Rule ruleWithId = ruleFinder.findByKey(ruleKey);
         if (ruleWithId == null) {
           throw new SonarException("Can not save a measure with unknown rule " + ruleMeasure);
         }
@@ -173,86 +137,4 @@ public final class MeasurePersister {
     }
     return model;
   }
-
-  private void insert(Iterable<MeasureModelAndDetails> values) {
-    SqlSession session = mybatis.openSession(false);
-    try {
-      MeasureMapper mapper = session.getMapper(MeasureMapper.class);
-
-      for (MeasureModelAndDetails value : values) {
-        try {
-          mapper.insert(value.getMeasureModel());
-        } catch (Exception e) {
-          // SONAR-4066
-          throw new SonarException(String.format("Unable to save measure for metric [%s] on component [%s]", value.getMetricKey(), value.getResourceKey()), e);
-        }
-      }
-
-      session.commit();
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
-  }
-
-  private MeasureModel insert(Measure measure, Snapshot snapshot) {
-    MeasureModel value = model(measure);
-    value.setSnapshotId(snapshot.getId());
-
-    SqlSession session = mybatis.openSession(false);
-    try {
-      MeasureMapper mapper = session.getMapper(MeasureMapper.class);
-
-      mapper.insert(value);
-
-      session.commit();
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
-
-    return value;
-  }
-
-  private MeasureModel update(Measure measure, Snapshot snapshot) {
-    MeasureModel value = model(measure);
-    value.setId(measure.getId());
-    value.setSnapshotId(snapshot.getId());
-
-    SqlSession session = mybatis.openSession(false);
-    try {
-      MeasureMapper mapper = session.getMapper(MeasureMapper.class);
-
-      mapper.update(value);
-
-      session.commit();
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
-
-    return value;
-  }
-
-  // SONAR-4066
-  private static class MeasureModelAndDetails {
-    private final MeasureModel measureModel;
-    private final String resourceKey;
-    private final String metricKey;
-
-    public MeasureModelAndDetails(MeasureModel measureModel, String resourceKey, String metricKey) {
-      this.measureModel = measureModel;
-      this.resourceKey = resourceKey;
-      this.metricKey = metricKey;
-    }
-
-    public MeasureModel getMeasureModel() {
-      return measureModel;
-    }
-
-    public String getResourceKey() {
-      return resourceKey;
-    }
-
-    public String getMetricKey() {
-      return metricKey;
-    }
-  }
 }
index dec88e49d15314afe5ee1fb7ab656da67a693a46..d90f63d8b666dfbba8d18d6c9b029a4e98d9aa69 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.batch.index;
 import org.sonar.api.batch.Event;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.design.Dependency;
-import org.sonar.api.measures.Measure;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.ProjectLink;
 import org.sonar.api.resources.Resource;
@@ -32,10 +31,6 @@ import java.util.List;
 public interface PersistenceManager {
   void clear();
 
-  void setDelayedMode(boolean b);
-
-  void dump();
-
   void saveProject(Project project, Project parent);
 
   Snapshot saveResource(Project project, Resource resource, Resource parent);
@@ -44,8 +39,6 @@ public interface PersistenceManager {
 
   String getSource(Resource resource);
 
-  void saveMeasure(Resource resource, Measure measure);
-
   void saveDependency(Project project, Dependency dependency, Dependency parentDependency);
 
   void saveLink(Project project, ProjectLink link);
index 0ba16ea66d78cb223b9eb7f147b231d4645d3d3a..90bafa7dc3e009577dcc21701a284ae39cd34a02 100644 (file)
@@ -116,8 +116,6 @@ public final class PhaseExecutor {
 
     executeInitializersPhase();
 
-    persistenceManager.setDelayedMode(true);
-
     if (phases.isEnabled(Phases.Phase.SENSOR)) {
       // Index and lock the filesystem
       fs.index();
@@ -135,12 +133,6 @@ public final class PhaseExecutor {
       decoratorsExecutor.execute();
     }
 
-    String saveMeasures = "Save measures";
-    eventBus.fireEvent(new BatchStepEvent(saveMeasures, true));
-    persistenceManager.dump();
-    eventBus.fireEvent(new BatchStepEvent(saveMeasures, false));
-    persistenceManager.setDelayedMode(false);
-
     if (module.isRoot()) {
       jsonReport.execute();
 
@@ -162,6 +154,7 @@ public final class PhaseExecutor {
       LOGGER.debug("Execute {}", persister.getClass().getName());
       persister.persist();
     }
+
     eventBus.fireEvent(new BatchStepEvent(persistersStep, false));
   }
 
index ddf1553e42fcb0e3f790663c87e0104f519fcba5..ac18daadf95ba6708d6f6e829e34bb773d83b1e8 100644 (file)
@@ -23,7 +23,11 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.*;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.measures.CoreMetrics;
@@ -34,10 +38,15 @@ import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.ResourceUtils;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.Durations;
+import org.sonar.batch.index.DefaultIndex;
 import org.sonar.core.qualitygate.db.QualityGateConditionDto;
 import org.sonar.core.timemachine.Periods;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 
 public class QualityGateVerifier implements Decorator {
 
@@ -55,13 +64,15 @@ public class QualityGateVerifier implements Decorator {
   private Periods periods;
   private I18n i18n;
   private Durations durations;
+  private final DefaultIndex index;
 
-  public QualityGateVerifier(QualityGate qualityGate, Snapshot snapshot, Periods periods, I18n i18n, Durations durations) {
+  public QualityGateVerifier(QualityGate qualityGate, Snapshot snapshot, Periods periods, I18n i18n, Durations durations, DefaultIndex index) {
     this.qualityGate = qualityGate;
     this.snapshot = snapshot;
     this.periods = periods;
     this.i18n = i18n;
     this.durations = durations;
+    this.index = index;
   }
 
   @DependedUpon
@@ -77,7 +88,7 @@ public class QualityGateVerifier implements Decorator {
   @DependsUpon
   public Collection<Metric> dependsUponMetrics() {
     Set<Metric> metrics = Sets.newHashSet();
-    for (ResolvedCondition condition: qualityGate.conditions()) {
+    for (ResolvedCondition condition : qualityGate.conditions()) {
       metrics.add(condition.metric());
     }
     return metrics;
@@ -91,16 +102,16 @@ public class QualityGateVerifier implements Decorator {
   @Override
   public void decorate(Resource resource, DecoratorContext context) {
     if (ResourceUtils.isRootProject(resource)) {
-      checkProjectConditions(context);
+      checkProjectConditions(resource, context);
     }
   }
 
-  private void checkProjectConditions(DecoratorContext context) {
+  private void checkProjectConditions(Resource resource, DecoratorContext context) {
     Metric.Level globalLevel = Metric.Level.OK;
     QualityGateDetails details = new QualityGateDetails();
     List<String> labels = Lists.newArrayList();
 
-    for (ResolvedCondition condition: qualityGate.conditions()) {
+    for (ResolvedCondition condition : qualityGate.conditions()) {
       Measure measure = context.getMeasure(condition.metric());
       if (measure != null) {
         Metric.Level level = ConditionUtils.getLevel(condition, measure);
@@ -112,7 +123,7 @@ public class QualityGateVerifier implements Decorator {
           labels.add(text);
         }
 
-        context.saveMeasure(measure);
+        index.updateMeasure(resource, measure);
 
         if (Metric.Level.WARN == level && globalLevel != Metric.Level.ERROR) {
           globalLevel = Metric.Level.WARN;
index 59e68e6078cc1c086212b40beb9208375e5303a5..335884d7f8779e59058c5a888820ab5ca1f104d3 100644 (file)
@@ -68,6 +68,7 @@ import org.sonar.batch.rule.RulesProvider;
 import org.sonar.batch.scan.filesystem.InputFileCache;
 import org.sonar.batch.scan.maven.FakeMavenPluginExecutor;
 import org.sonar.batch.scan.maven.MavenPluginExecutor;
+import org.sonar.batch.scan.measure.MeasureCache;
 import org.sonar.batch.source.HighlightableBuilder;
 import org.sonar.batch.source.SymbolizableBuilder;
 import org.sonar.core.component.ScanGraph;
@@ -188,6 +189,9 @@ public class ProjectScanContainer extends ComponentContainer {
       // Differential periods
       PeriodsDefinition.class,
 
+      // Measures
+      MeasureCache.class,
+
       ProjectSettingsReady.class);
   }
 
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java
new file mode 100644 (file)
index 0000000..b29b6c8
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.scan.measure;
+
+import com.google.common.base.Preconditions;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.resources.Resource;
+import org.sonar.batch.index.Cache;
+import org.sonar.batch.index.Cache.Entry;
+import org.sonar.batch.index.Caches;
+
+/**
+ * Cache of all measures. This cache is shared amongst all project modules.
+ */
+public class MeasureCache implements BatchComponent {
+
+  private final Cache<Measure> cache;
+
+  public MeasureCache(Caches caches) {
+    cache = caches.createCache("measures");
+  }
+
+  public Iterable<Entry<Measure>> entries() {
+    return cache.entries();
+  }
+
+  public Iterable<Measure> byResource(Resource r) {
+    return cache.values(r.getEffectiveKey());
+  }
+
+  public MeasureCache put(Resource resource, Measure measure) {
+    Preconditions.checkNotNull(resource.getEffectiveKey());
+    Preconditions.checkNotNull(measure.getMetricKey());
+    cache.put(resource.getEffectiveKey(), computeMeasureKey(measure), measure);
+    return this;
+  }
+
+  public boolean contains(Resource resource, Measure measure) {
+    Preconditions.checkNotNull(resource.getEffectiveKey());
+    Preconditions.checkNotNull(measure.getMetricKey());
+    return cache.containsKey(resource.getEffectiveKey(), computeMeasureKey(measure));
+  }
+
+  private static String computeMeasureKey(Measure m) {
+    StringBuilder sb = new StringBuilder();
+    if (m.getMetricKey() != null) {
+      sb.append(m.getMetricKey());
+    }
+    sb.append("|");
+    if (m.getCharacteristic() != null) {
+      sb.append(m.getCharacteristic().key());
+    }
+    sb.append("|");
+    if (m.getPersonId() != null) {
+      sb.append(m.getPersonId());
+    }
+    if (m instanceof RuleMeasure) {
+      sb.append("|");
+      sb.append(((RuleMeasure) m).ruleKey());
+    }
+    return sb.toString();
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/package-info.java
new file mode 100644 (file)
index 0000000..64f9c87
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * This package is a part of bootstrap process, so we should take care about backward compatibility.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.scan.measure;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index fc82ece746c3539c71bfad28deff0fbe2ad5d316..6edbbab8fbf992941b99a199f173fab3a35929fa 100644 (file)
@@ -59,7 +59,7 @@ import java.util.Collections;
 import static com.google.common.collect.Lists.newArrayList;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.argThat;
+import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -204,7 +204,7 @@ public class DebtDecoratorTest {
       new RuleMeasure(CoreMetrics.TECHNICAL_DEBT,
         org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule()), null, null)
         .setValue(5d * ONE_DAY_IN_MINUTES)
-    ));
+      ));
     decorator.decorate(resource, context);
 
     verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 7d * ONE_DAY_IN_MINUTES);
@@ -225,7 +225,7 @@ public class DebtDecoratorTest {
       new RuleMeasure(CoreMetrics.TECHNICAL_DEBT,
         org.sonar.api.rules.Rule.create(ruleKey2.repository(), ruleKey2.rule())
         , null, null).setValue(10d * ONE_DAY_IN_MINUTES)
-    ));
+      ));
     decorator.decorate(resource, context);
 
     verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 15d * ONE_DAY_IN_MINUTES);
@@ -319,8 +319,7 @@ public class DebtDecoratorTest {
       description.appendText(new StringBuilder()
         .append("value=").append(value).append(",")
         .append("characteristic=").append(characteristic.key()).append(",")
-        .append("metric=").append(metric.getKey()).toString())
-      ;
+        .append("metric=").append(metric.getKey()).toString());
     }
   }
 
@@ -342,8 +341,7 @@ public class DebtDecoratorTest {
       }
       RuleMeasure m = (RuleMeasure) o;
       return ObjectUtils.equals(metric, m.getMetric()) &&
-        ObjectUtils.equals(ruleKey.repository(), m.getRule().getRepositoryKey()) &&
-        ObjectUtils.equals(ruleKey.rule(), m.getRule().getKey()) &&
+        ObjectUtils.equals(ruleKey, m.ruleKey()) &&
         ObjectUtils.equals(value, m.getValue());
     }
 
index 2fc521a64d5e242a8582f5a02a58047b2036f454..cb20f15cde621d3f1841cbbd809d95b509a58d8a 100644 (file)
 package org.sonar.batch.index;
 
 import org.junit.Test;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.MeasuresFilters;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.resources.Directory;
 import org.sonar.api.resources.File;
-import org.sonar.api.utils.SonarException;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.core.IsNot.not;
@@ -51,42 +48,6 @@ public class BucketTest {
     assertThat(packageBucket.getChildren(), hasItem(fileBucket));
   }
 
-  @Test
-  public void shouldAddNewMeasure() {
-    Bucket fileBucket = new Bucket(javaFile);
-    Measure measure = new Measure(ncloc).setValue(1200.0);
-    fileBucket.addMeasure(measure);
-
-    assertThat(fileBucket.getMeasures(MeasuresFilters.all()).size(), is(1));
-    assertThat(fileBucket.getMeasures(MeasuresFilters.metric(ncloc)), is(measure));
-  }
-
-  @Test
-  public void shouldUpdateMeasure() {
-    Bucket fileBucket = new Bucket(javaFile);
-    Measure measure = new Measure(ncloc).setValue(1200.0);
-    fileBucket.addMeasure(measure);
-
-    assertThat(fileBucket.getMeasures(MeasuresFilters.all()).size(), is(1));
-    assertThat(fileBucket.getMeasures(MeasuresFilters.metric(ncloc)).getValue(), is(1200.0));
-
-    measure.setValue(500.0);
-    fileBucket.addMeasure(measure);
-
-    assertThat(fileBucket.getMeasures(MeasuresFilters.all()).size(), is(1));
-    assertThat(fileBucket.getMeasures(MeasuresFilters.metric(ncloc)).getValue(), is(500.0));
-  }
-
-  @Test(expected = SonarException.class)
-  public void shouldFailIfAddingSameMeasures() {
-    Bucket fileBucket = new Bucket(javaFile);
-    Measure measure = new Measure(ncloc).setValue(1200.0);
-    fileBucket.addMeasure(measure);
-
-    measure = new Measure(ncloc).setValue(500.0);
-    fileBucket.addMeasure(measure);
-  }
-
   @Test
   public void shouldBeEquals() {
     assertEquals(new Bucket(directory), new Bucket(directory));
index 4abc71694cf1ffc6c24fa9c1bbbe89fb1b96a349..9a162fbf42549f58af29057a4431a2cb8b037a6b 100644 (file)
@@ -28,7 +28,13 @@ import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilters;
 import org.sonar.api.measures.MetricFinder;
 import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.*;
+import org.sonar.api.resources.Directory;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.Library;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Resource;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.Violation;
@@ -36,6 +42,7 @@ import org.sonar.api.violations.ViolationQuery;
 import org.sonar.batch.ProjectTree;
 import org.sonar.batch.issue.DeprecatedViolations;
 import org.sonar.batch.issue.ModuleIssues;
+import org.sonar.batch.scan.measure.MeasureCache;
 import org.sonar.core.component.ScanGraph;
 
 import java.io.IOException;
@@ -68,7 +75,8 @@ public class DefaultIndexTest {
     ruleFinder = mock(RuleFinder.class);
 
     ProjectTree projectTree = mock(ProjectTree.class);
-    index = new DefaultIndex(mock(PersistenceManager.class), projectTree, metricFinder, mock(ScanGraph.class), deprecatedViolations, mock(ResourceKeyMigration.class));
+    index = new DefaultIndex(mock(PersistenceManager.class), projectTree, metricFinder, mock(ScanGraph.class), deprecatedViolations, mock(ResourceKeyMigration.class),
+      mock(MeasureCache.class));
 
     java.io.File baseDir = temp.newFolder();
     project = new Project("project");
index 0b3363527da66c0f5e81dc1e1106b436e7447e68..f0a2bb666aa0dabd04792658d6798f740c3c4641 100644 (file)
@@ -36,8 +36,11 @@ import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RulePriority;
 import org.sonar.api.utils.SonarException;
+import org.sonar.batch.scan.measure.MeasureCache;
 import org.sonar.core.persistence.AbstractDaoTestCase;
 
+import java.util.Arrays;
+
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -58,21 +61,28 @@ public class MeasurePersisterTest extends AbstractDaoTestCase {
 
   MeasurePersister measurePersister;
   RuleFinder ruleFinder = mock(RuleFinder.class);
-  ResourcePersister resourcePersister = mock(ResourcePersister.class);
   Project project = new Project("foo");
   Directory aDirectory = new Directory("org/foo");
   File aFile = new File("org/foo/Bar.java");
   Snapshot projectSnapshot = snapshot(PROJECT_SNAPSHOT_ID);
   Snapshot packageSnapshot = snapshot(PACKAGE_SNAPSHOT_ID);
 
+  private SnapshotCache snapshotCache;
+
+  private MeasureCache measureCache;
+
   @Before
   public void mockResourcePersister() {
-    when(resourcePersister.getSnapshotOrFail(project)).thenReturn(projectSnapshot);
-    when(resourcePersister.getSnapshotOrFail(aDirectory)).thenReturn(packageSnapshot);
-    when(resourcePersister.getSnapshot(project)).thenReturn(projectSnapshot);
-    when(resourcePersister.getSnapshot(aDirectory)).thenReturn(packageSnapshot);
+    snapshotCache = mock(SnapshotCache.class);
+    measureCache = mock(MeasureCache.class);
+    ResourceCache resourceCache = mock(ResourceCache.class);
+    when(snapshotCache.get("foo")).thenReturn(projectSnapshot);
+    when(snapshotCache.get("foo:org/foo")).thenReturn(packageSnapshot);
+    when(resourceCache.get("foo")).thenReturn(project);
+    when(resourceCache.get("foo:org/foo/Bar.java")).thenReturn(aFile);
+    when(resourceCache.get("foo:org/foo")).thenReturn(aDirectory);
 
-    measurePersister = new MeasurePersister(getMyBatis(), resourcePersister, ruleFinder);
+    measurePersister = new MeasurePersister(getMyBatis(), ruleFinder, measureCache, snapshotCache, resourceCache);
   }
 
   @Test
@@ -80,10 +90,10 @@ public class MeasurePersisterTest extends AbstractDaoTestCase {
     setupData("empty");
 
     Measure measure = new Measure(ncloc()).setValue(1234.0);
-    measurePersister.saveMeasure(project, measure);
+    when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure)));
+    measurePersister.persist();
 
     checkTables("shouldInsertMeasure", "project_measures");
-    assertThat(measure.getId()).isNotNull();
   }
 
   @Test
@@ -91,11 +101,12 @@ public class MeasurePersisterTest extends AbstractDaoTestCase {
     setupData("empty");
 
     Measure measure = new Measure(ncloc()).setValue(1234.0).setAlertText(TOO_LONG);
+    when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure)));
 
     thrown.expect(SonarException.class);
     thrown.expectMessage("Unable to save measure for metric [ncloc] on component [foo]");
 
-    measurePersister.saveMeasure(project, measure);
+    measurePersister.persist();
   }
 
   @Test
@@ -103,13 +114,14 @@ public class MeasurePersisterTest extends AbstractDaoTestCase {
     setupData("empty");
 
     Rule rule = Rule.create("pmd", "key");
-    when(ruleFinder.findByKey("pmd", "key")).thenReturn(rule);
+    when(ruleFinder.findByKey(rule.ruleKey())).thenReturn(rule);
 
     Measure measure = new RuleMeasure(ncloc(), rule, RulePriority.MAJOR, 1).setValue(1234.0);
-    measurePersister.saveMeasure(project, measure);
+    when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure)));
+
+    measurePersister.persist();
 
     checkTables("shouldInsertRuleMeasure", "project_measures");
-    assertThat(measure.getId()).isNotNull();
   }
 
   @Test
@@ -117,117 +129,50 @@ public class MeasurePersisterTest extends AbstractDaoTestCase {
     setupData("empty");
 
     Measure withLargeData = new Measure(ncloc()).setData(LONG);
-    measurePersister.saveMeasure(project, withLargeData);
+    when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, withLargeData)));
 
-    checkTables("shouldInsertMeasureWithLargeData", "project_measures");
+    measurePersister.persist();
 
-    assertThat(withLargeData.getId()).isNotNull();
+    checkTables("shouldInsertMeasureWithLargeData", "project_measures");
   }
 
   @Test
   public void should_not_save_best_values() {
     setupData("empty");
 
-    measurePersister.saveMeasure(aFile, new Measure(coverage()).setValue(100.0));
-
-    assertEmptyTables("project_measures");
-  }
+    Measure measure = new Measure(coverage()).setValue(100.0);
+    when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo:org/foo/Bar.java", "coverage"}, measure)));
 
-  @Test
-  public void should_not_save_memory_only_measures() {
-    setupData("empty");
-
-    measurePersister.saveMeasure(aFile, new Measure("ncloc").setPersistenceMode(PersistenceMode.MEMORY));
+    measurePersister.persist();
 
     assertEmptyTables("project_measures");
   }
 
   @Test
-  public void should_always_save_non_file_measures() {
-    setupData("empty");
-
-    measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(200.0));
-    measurePersister.saveMeasure(aDirectory, new Measure(ncloc()).setValue(300.0));
-
-    checkTables("shouldAlwaysPersistNonFileMeasures", "project_measures");
-  }
-
-  @Test
-  public void should_update_measure() {
-    setupData("data");
-
-    measurePersister.saveMeasure(project, new Measure(coverage()).setValue(12.5).setId(1L));
-    measurePersister.saveMeasure(project, new Measure(coverage()).setData(SHORT).setId(2L));
-    measurePersister.saveMeasure(aDirectory, new Measure(coverage()).setData(LONG).setId(3L));
-
-    checkTables("shouldUpdateMeasure", "project_measures");
-  }
-
-  @Test
-  public void should_add_delayed_measure_several_times() {
+  public void should_not_save_memory_only_measures() {
     setupData("empty");
 
-    Measure measure = new Measure(ncloc());
-
-    measurePersister.setDelayedMode(true);
-    measurePersister.saveMeasure(project, measure.setValue(200.0));
-    measurePersister.saveMeasure(project, measure.setValue(300.0));
-    measurePersister.dump();
+    Measure measure = new Measure("ncloc").setPersistenceMode(PersistenceMode.MEMORY);
+    when(measureCache.entries()).thenReturn(Arrays.asList(new Cache.Entry<Measure>(new String[] {"foo:org/foo/Bar.java", "ncloc"}, measure)));
 
-    checkTables("shouldAddDelayedMeasureSeveralTimes", "project_measures");
-  }
-
-  @Test
-  public void should_delay_saving() {
-    setupData("empty");
-
-    measurePersister.setDelayedMode(true);
-    measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(1234.0).setData(SHORT));
-    measurePersister.saveMeasure(aDirectory, new Measure(ncloc()).setValue(50.0).setData(LONG));
+    measurePersister.persist();
 
     assertEmptyTables("project_measures");
-
-    measurePersister.dump();
-    checkTables("shouldDelaySaving", "project_measures");
-  }
-
-  @Test
-  public void should_display_contextual_info_when_error_during_delay_saving() {
-    setupData("empty");
-
-    measurePersister.setDelayedMode(true);
-
-    measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(1234.0).setData(SHORT).setAlertText(TOO_LONG));
-
-    thrown.expect(SonarException.class);
-    thrown.expectMessage("Unable to save measure for metric [ncloc] on component [foo]");
-
-    measurePersister.dump();
-  }
-
-  @Test
-  public void should_not_delay_saving_with_database_only_measure() {
-    setupData("empty");
-
-    measurePersister.setDelayedMode(true);
-    measurePersister.saveMeasure(project, new Measure(ncloc()).setValue(1234.0).setPersistenceMode(PersistenceMode.DATABASE));
-    measurePersister.saveMeasure(aDirectory, new Measure(ncloc()).setValue(50.0));
-
-    checkTables("shouldInsertMeasure", "project_measures");
   }
 
   @Test
-  public void should_not_save_best_value_measures_in_delayed_mode() {
+  public void should_always_save_non_file_measures() {
     setupData("empty");
 
-    measurePersister.setDelayedMode(true);
-    measurePersister.saveMeasure(aFile, new Measure(coverage()).setValue(100.0));
+    Measure measure1 = new Measure(ncloc()).setValue(200.0);
+    Measure measure2 = new Measure(ncloc()).setValue(300.0);
+    when(measureCache.entries()).thenReturn(Arrays.asList(
+      new Cache.Entry<Measure>(new String[] {"foo", "ncloc"}, measure1),
+      new Cache.Entry<Measure>(new String[] {"foo:org/foo", "ncloc"}, measure2)));
 
-    assertEmptyTables("project_measures");
+    measurePersister.persist();
 
-    measurePersister.dump();
-
-    assertEmptyTables("project_measures");
+    checkTables("shouldAlwaysPersistNonFileMeasures", "project_measures");
   }
 
   @Test
index 9dd6a731f12ef9c82da982910873f45df47f5594..28339d61c9cd8d6cdb68703f6101ce71a923acb9 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.api.resources.Resource;
 import org.sonar.api.test.IsMeasure;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.Durations;
+import org.sonar.batch.index.DefaultIndex;
 import org.sonar.core.qualitygate.db.QualityGateConditionDto;
 import org.sonar.core.timemachine.Periods;
 
@@ -69,6 +70,7 @@ public class QualityGateVerifierTest {
   Periods periods;
   I18n i18n;
   Durations durations;
+  private DefaultIndex index;
 
   @Before
   public void before() {
@@ -89,7 +91,8 @@ public class QualityGateVerifierTest {
     snapshot = mock(Snapshot.class);
     qualityGate = mock(QualityGate.class);
     when(qualityGate.isEnabled()).thenReturn(true);
-    verifier = new QualityGateVerifier(qualityGate, snapshot, periods, i18n, durations);
+    index = mock(DefaultIndex.class);
+    verifier = new QualityGateVerifier(qualityGate, snapshot, periods, i18n, durations, index);
     project = new Project("foo");
   }
 
@@ -130,8 +133,8 @@ public class QualityGateVerifierTest {
 
     verifier.decorate(project, context);
 
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureCoverage, Metric.Level.OK)));
     verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.ALERT_STATUS, Metric.Level.OK.toString())));
     verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.QUALITY_GATE_DETAILS, "{\"level\":\"OK\","
       + "\"conditions\":"
@@ -166,8 +169,8 @@ public class QualityGateVerifierTest {
 
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
 
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
 
   }
 
@@ -184,8 +187,8 @@ public class QualityGateVerifierTest {
 
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, null)));
 
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.ERROR)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureCoverage, Metric.Level.ERROR)));
   }
 
   @Test
@@ -255,9 +258,9 @@ public class QualityGateVerifierTest {
 
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
 
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureComplexity, Metric.Level.OK)));
   }
 
   @Test
@@ -283,9 +286,9 @@ public class QualityGateVerifierTest {
 
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
 
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
-    verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.WARN)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureComplexity, Metric.Level.WARN)));
   }
 
   @Test
@@ -299,7 +302,7 @@ public class QualityGateVerifierTest {
     verifier.decorate(project, context);
 
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.OK)));
   }
 
   @Test
@@ -317,7 +320,7 @@ public class QualityGateVerifierTest {
     verifier.decorate(project, context);
 
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
-    verify(context).saveMeasure(argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
   }
 
   @Test
@@ -332,7 +335,7 @@ public class QualityGateVerifierTest {
     verifier.decorate(project, context);
 
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+    verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.WARN)));
   }
 
   @Test(expected = NotImplementedException.class)
@@ -405,7 +408,7 @@ public class QualityGateVerifierTest {
     verifier.decorate(project, context);
 
     // First call to saveMeasure is for the update of debt
-    verify(context).saveMeasure(argThat(matchesMetric(metric, Level.ERROR, "The Debt > 1h")));
+    verify(index).updateMeasure(eq(project), argThat(matchesMetric(metric, Level.ERROR, "The Debt > 1h")));
     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "The Debt > 1h")));
     verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.QUALITY_GATE_DETAILS, "{\"level\":\"ERROR\","
       + "\"conditions\":"
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java
new file mode 100644 (file)
index 0000000..a8f42fd
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.scan.measure;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.resources.Directory;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.batch.index.Cache.Entry;
+import org.sonar.batch.index.Caches;
+import org.sonar.batch.index.CachesTest;
+
+import java.util.Date;
+import java.util.Iterator;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class MeasureCacheTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  Caches caches;
+
+  @Before
+  public void start() throws Exception {
+    caches = CachesTest.createCacheOnTemp(temp);
+    caches.start();
+  }
+
+  @After
+  public void stop() {
+    caches.stop();
+  }
+
+  @Test
+  public void should_add_measure() throws Exception {
+    MeasureCache cache = new MeasureCache(caches);
+    Project p = new Project("struts");
+
+    assertThat(cache.entries()).hasSize(0);
+
+    assertThat(cache.byResource(p)).hasSize(0);
+
+    Measure m = new Measure(CoreMetrics.NCLOC, 1.0);
+    cache.put(p, m);
+
+    assertThat(cache.contains(p, m)).isTrue();
+    assertThat(cache.entries()).hasSize(1);
+    Iterator<Entry<Measure>> iterator = cache.entries().iterator();
+    iterator.hasNext();
+    Entry<Measure> next = iterator.next();
+    assertThat(next.value()).isEqualTo(m);
+    assertThat(next.key()[0]).isEqualTo("struts");
+
+    assertThat(cache.byResource(p)).hasSize(1);
+    assertThat(cache.byResource(p).iterator().next()).isEqualTo(m);
+
+    Measure mRule = RuleMeasure.createForPriority(CoreMetrics.CRITICAL_VIOLATIONS, RulePriority.BLOCKER, 1.0);
+    cache.put(p, mRule);
+
+    assertThat(cache.entries()).hasSize(2);
+
+    assertThat(cache.byResource(p)).hasSize(2);
+  }
+
+  /**
+   * This test fails when compression is not enabled for measures. PersistIt seems to be ok with
+   * put but fail when reading value.
+   */
+  @Test
+  public void should_add_measure_with_big_data() throws Exception {
+    MeasureCache cache = new MeasureCache(caches);
+    Project p = new Project("struts");
+
+    assertThat(cache.entries()).hasSize(0);
+
+    assertThat(cache.byResource(p)).hasSize(0);
+
+    Measure m = new Measure(CoreMetrics.NCLOC, 1.0).setDate(new Date());
+    m.setAlertText("foooooooooooooooooooooooooooooooooooo");
+    StringBuilder data = new StringBuilder();
+    for (int i = 0; i < 1048575; i++) {
+      data.append("a");
+    }
+    m.setData(data.toString());
+
+    cache.put(p, m);
+
+    assertThat(cache.contains(p, m)).isTrue();
+    assertThat(cache.entries()).hasSize(1);
+    Iterator<Entry<Measure>> iterator = cache.entries().iterator();
+    iterator.hasNext();
+    Entry<Measure> next = iterator.next();
+    assertThat(next.value()).isEqualTo(m);
+    assertThat(next.key()[0]).isEqualTo("struts");
+
+    assertThat(cache.byResource(p)).hasSize(1);
+    assertThat(cache.byResource(p).iterator().next()).isEqualTo(m);
+
+    RuleMeasure mRule = RuleMeasure.createForPriority(CoreMetrics.CRITICAL_VIOLATIONS, RulePriority.BLOCKER, 1.0);
+    mRule.setRuleKey(RuleKey.of("repo", "rule"));
+    cache.put(p, mRule);
+
+    assertThat(cache.entries()).hasSize(2);
+  }
+
+  /**
+   * This test fails when compression is not enabled for measures. Size exceed PersistIt max value.
+   */
+  @Test
+  public void should_add_measure_with_too_big_data_for_persistit() throws Exception {
+    MeasureCache cache = new MeasureCache(caches);
+    Project p = new Project("struts");
+
+    assertThat(cache.entries()).hasSize(0);
+
+    assertThat(cache.byResource(p)).hasSize(0);
+
+    Measure m = new Measure(CoreMetrics.NCLOC, 1.0).setDate(new Date());
+    StringBuilder data = new StringBuilder();
+    for (int i = 0; i < 500000; i++) {
+      data.append("some data");
+    }
+    m.setData(data.toString());
+
+    cache.put(p, m);
+
+    assertThat(cache.contains(p, m)).isTrue();
+    assertThat(cache.entries()).hasSize(1);
+    Iterator<Entry<Measure>> iterator = cache.entries().iterator();
+    iterator.hasNext();
+    Entry<Measure> next = iterator.next();
+    assertThat(next.value()).isEqualTo(m);
+    assertThat(next.key()[0]).isEqualTo("struts");
+
+    assertThat(cache.byResource(p)).hasSize(1);
+    assertThat(cache.byResource(p).iterator().next()).isEqualTo(m);
+
+    RuleMeasure mRule = RuleMeasure.createForPriority(CoreMetrics.CRITICAL_VIOLATIONS, RulePriority.BLOCKER, 1.0);
+    mRule.setRuleKey(RuleKey.of("repo", "rule"));
+    cache.put(p, mRule);
+
+    assertThat(cache.entries()).hasSize(2);
+  }
+
+  @Test
+  public void should_add_measure_with_too_big_data_for_persistit_with_compression() throws Exception {
+    MeasureCache cache = new MeasureCache(caches);
+    Project p = new Project("struts");
+
+    assertThat(cache.entries()).hasSize(0);
+
+    assertThat(cache.byResource(p)).hasSize(0);
+
+    Measure m = new Measure(CoreMetrics.NCLOC, 1.0).setDate(new Date());
+    StringBuilder data = new StringBuilder();
+    for (int i = 0; i < 50000000; i++) {
+      data.append((char) ('z' * Math.random()));
+    }
+    m.setData(data.toString());
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Fail to put element in the cache measures");
+
+    cache.put(p, m);
+  }
+
+  @Test
+  public void should_add_measure_with_same_metric() throws Exception {
+    MeasureCache cache = new MeasureCache(caches);
+    Project p = new Project("struts");
+
+    assertThat(cache.entries()).hasSize(0);
+    assertThat(cache.byResource(p)).hasSize(0);
+
+    Measure m1 = new Measure(CoreMetrics.NCLOC, 1.0);
+    Measure m2 = new Measure(CoreMetrics.NCLOC, 1.0).setCharacteristic(new DefaultCharacteristic().setKey("charac"));
+    Measure m3 = new Measure(CoreMetrics.NCLOC, 1.0).setPersonId(2);
+    Measure m4 = new RuleMeasure(CoreMetrics.NCLOC, RuleKey.of("repo", "rule"), RulePriority.BLOCKER, null);
+    cache.put(p, m1);
+    cache.put(p, m2);
+    cache.put(p, m3);
+    cache.put(p, m4);
+
+    assertThat(cache.entries()).hasSize(4);
+
+    assertThat(cache.byResource(p)).hasSize(4);
+  }
+
+  @Test
+  public void should_get_measures() throws Exception {
+    MeasureCache cache = new MeasureCache(caches);
+    Project p = new Project("struts");
+    Resource dir = new Directory("foo/bar").setEffectiveKey("struts:foo/bar");
+    Resource file1 = new File("foo/bar/File1.txt").setEffectiveKey("struts:foo/bar/File1.txt");
+    Resource file2 = new File("foo/bar/File2.txt").setEffectiveKey("struts:foo/bar/File2.txt");
+
+    assertThat(cache.entries()).hasSize(0);
+
+    assertThat(cache.byResource(p)).hasSize(0);
+    assertThat(cache.byResource(dir)).hasSize(0);
+
+    Measure mFile1 = new Measure(CoreMetrics.NCLOC, 1.0);
+    cache.put(file1, mFile1);
+    Measure mFile2 = new Measure(CoreMetrics.NCLOC, 3.0);
+    cache.put(file2, mFile2);
+
+    assertThat(cache.entries()).hasSize(2);
+    assertThat(cache.byResource(p)).hasSize(0);
+    assertThat(cache.byResource(dir)).hasSize(0);
+
+    Measure mDir = new Measure(CoreMetrics.NCLOC, 4.0);
+    cache.put(dir, mDir);
+
+    assertThat(cache.entries()).hasSize(3);
+    assertThat(cache.byResource(p)).hasSize(0);
+    assertThat(cache.byResource(dir)).hasSize(1);
+    assertThat(cache.byResource(dir).iterator().next()).isEqualTo(mDir);
+
+    Measure mProj = new Measure(CoreMetrics.NCLOC, 4.0);
+    cache.put(p, mProj);
+
+    assertThat(cache.entries()).hasSize(4);
+    assertThat(cache.byResource(p)).hasSize(1);
+    assertThat(cache.byResource(p).iterator().next()).isEqualTo(mProj);
+    assertThat(cache.byResource(dir)).hasSize(1);
+    assertThat(cache.byResource(dir).iterator().next()).isEqualTo(mDir);
+  }
+}
index 866785feeefc08867da5df78e147e2aea962bb28..4eb61021ca55b4d1d3de6f50bde66d6a05ae6caa 100644 (file)
@@ -30,6 +30,8 @@ import org.sonar.api.rules.Violation;
 import org.sonar.api.violations.ViolationQuery;
 import org.sonar.graph.DirectedGraphAccessor;
 
+import javax.annotation.CheckForNull;
+
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
@@ -118,8 +120,10 @@ public abstract class SonarIndex implements DirectedGraphAccessor<Resource, Depe
   @Deprecated
   public abstract Resource addResource(Resource resource);
 
+  @CheckForNull
   public abstract Measure getMeasure(Resource resource, Metric metric);
 
+  @CheckForNull
   public abstract <M> M getMeasures(Resource resource, MeasuresFilter<M> filter);
 
   /**
index e2448adb5da7f0b512150a9b9dfe30039887d05c..3d38e52aea8595344b9c4957a4f65fc0364b11fc 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.api.technicaldebt.batch.Requirement;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
+import java.io.Serializable;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.Date;
@@ -37,7 +38,7 @@ import java.util.Date;
  *
  * @since 1.10
  */
-public class Measure {
+public class Measure implements Serializable {
   private static final String INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5 = "Index should be in range from 1 to 5";
 
   protected static final int MAX_TEXT_SIZE = 96;
@@ -47,8 +48,6 @@ public class Measure {
    */
   public static final int DEFAULT_PRECISION = 1;
 
-  // for internal use
-  private Long id;
   protected String metricKey;
   protected Metric metric;
   protected Double value;
@@ -413,20 +412,11 @@ public class Measure {
   }
 
   /**
-   * @return the measure id - Internal use only
-   */
-  public Long getId() {
-    return id;
-  }
-
-  /**
-   * Sets the measure id - Internal use only
-   *
-   * @param id the id
-   * @return the measure object instance
+   * Called by views when cloning measures
+   * @deprecated since 4.4 not used
    */
+  @Deprecated
   public Measure setId(Long id) {
-    this.id = id;
     return this;
   }
 
@@ -646,7 +636,7 @@ public class Measure {
     return metric.isOptimizedBestValue() == Boolean.TRUE
       && metric.getBestValue() != null
       && (value == null || NumberUtils.compare(metric.getBestValue(), value) == 0)
-      && allNull(id, alertStatus, description, tendency, url, data)
+      && allNull(alertStatus, description, tendency, url, data)
       && isZeroVariation(variation1, variation2, variation3, variation4, variation5);
   }
 
index 4ed4a8ffb1b1512f1662770f65a69687add63085..f11a225e6686e7d3da552592626765b68917a2b2 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.api.measures;
 
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.technicaldebt.batch.Characteristic;
 import org.sonar.api.technicaldebt.batch.Requirement;
@@ -37,8 +38,15 @@ public final class MeasuresFilters {
 
   public static MeasuresFilter<Collection<Measure>> all() {
     return new MeasuresFilter<Collection<Measure>>() {
+      @Override
       public Collection<Measure> filter(Collection<Measure> measures) {
-        return measures;
+        Collection<Measure> all = new ArrayList<Measure>();
+        for (Measure measure : measures) {
+          if (measure != null) {
+            all.add(measure);
+          }
+        }
+        return all;
       }
     };
   }
@@ -49,7 +57,7 @@ public final class MeasuresFilters {
 
   public static MeasuresFilter<Measure> metric(final String metricKey) {
     return new MetricFilter<Measure>(metricKey) {
-
+      @Override
       public Measure filter(Collection<Measure> measures) {
         if (measures == null) {
           return null;
@@ -70,6 +78,7 @@ public final class MeasuresFilters {
   public static MeasuresFilter<Measure> characteristic(final Metric metric, final Characteristic characteristic) {
     return new MetricFilter<Measure>(metric) {
 
+      @Override
       public Measure filter(Collection<Measure> measures) {
         if (measures == null) {
           return null;
@@ -100,6 +109,7 @@ public final class MeasuresFilters {
   public static MeasuresFilter<Measure> requirement(final Metric metric, final Requirement requirement) {
     return new MetricFilter<Measure>(metric) {
 
+      @Override
       public Measure filter(Collection<Measure> measures) {
         if (measures == null) {
           return null;
@@ -128,6 +138,7 @@ public final class MeasuresFilters {
    */
   public static MeasuresFilter<Measure> measure(final Measure measure) {
     return new MeasuresFilter<Measure>() {
+      @Override
       public Measure filter(Collection<Measure> measures) {
         if (measures == null) {
           return null;
@@ -143,7 +154,7 @@ public final class MeasuresFilters {
   }
 
   public static MeasuresFilter<RuleMeasure> rule(final Metric metric, final Rule rule) {
-    return new RuleFilter(metric, rule);
+    return new RuleFilter(metric, rule.ruleKey());
   }
 
   public static MeasuresFilter<Collection<RuleMeasure>> rules(final Metric metric) {
@@ -151,9 +162,10 @@ public final class MeasuresFilters {
 
       private boolean apply(Measure measure) {
         return measure instanceof RuleMeasure && metric.equals(measure.getMetric())
-          && measure.getPersonId() == null && ((RuleMeasure) measure).getRule() != null;
+          && measure.getPersonId() == null && ((RuleMeasure) measure).ruleKey() != null;
       }
 
+      @Override
       public Collection<RuleMeasure> filter(Collection<Measure> measures) {
         if (measures == null) {
           return null;
@@ -202,6 +214,7 @@ public final class MeasuresFilters {
 
     abstract boolean doApply(RuleMeasure ruleMeasure);
 
+    @Override
     public M filter(Collection<Measure> measures) {
       if (measures == null) {
         return null;
@@ -216,17 +229,17 @@ public final class MeasuresFilters {
   }
 
   private static class RuleFilter extends AbstractRuleMeasureFilter<RuleMeasure> {
-    private Rule rule;
+    private RuleKey ruleKey;
 
-    protected RuleFilter(Metric metric, Rule rule) {
+    protected RuleFilter(Metric metric, RuleKey ruleKey) {
       super(metric);
-      this.rule = rule;
+      this.ruleKey = ruleKey;
     }
 
     @Override
     boolean doApply(RuleMeasure measure) {
-      return measure.getRule() != null
-        && rule.equals(measure.getRule());
+      return measure.ruleKey() != null
+        && ruleKey.equals(measure.ruleKey());
     }
   }
 }
index dc7d507153b78b164850331937ec8eb240ce3d30..bc809c88884f32cd96b292de045654393078e4fb 100644 (file)
@@ -27,7 +27,16 @@ import org.sonar.api.ServerExtension;
 import org.sonar.api.batch.InstantiationStrategy;
 
 import javax.annotation.Nullable;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+
+import java.io.Serializable;
 
 /**
  * This class represents the definition of a metric in Sonar.
@@ -37,7 +46,7 @@ import javax.persistence.*;
 @Table(name = "metrics")
 @Entity(name = "Metric")
 @InstantiationStrategy(InstantiationStrategy.PER_BATCH)
-public class Metric implements ServerExtension, BatchExtension {
+public class Metric implements ServerExtension, BatchExtension, Serializable {
 
   /**
    * A metric bigger value means a degradation
@@ -80,7 +89,7 @@ public class Metric implements ServerExtension, BatchExtension {
   private Integer id;
 
   @Transient
-  private Formula formula;
+  private transient Formula formula;
 
   @Column(name = "name", updatable = false, nullable = false, length = 64)
   private String key;
@@ -206,7 +215,7 @@ public class Metric implements ServerExtension, BatchExtension {
    */
   @Deprecated
   public Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, @Nullable String domain,
-      boolean userManaged) {
+    boolean userManaged) {
     this.key = key;
     this.description = description;
     this.type = type;
index b0e1199a5c128f160d039865da1b36e4a67a6365..b0932018c0389b864c0111e3f6d8a1b48673ab8c 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.api.measures;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
 import org.apache.commons.lang.builder.ToStringBuilder;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RulePriority;
 
@@ -32,24 +33,47 @@ import javax.annotation.Nullable;
  */
 public class RuleMeasure extends Measure {
 
-  private Rule rule;
+  private RuleKey ruleKey;
   private RulePriority rulePriority;
 
   /**
    * This constructor is for internal use only. Please use static methods createForXXX().
+   * @deprecated since 4.4 use {@link #RuleMeasure(Metric, RuleKey, RulePriority, Integer)}
    */
+  @Deprecated
   public RuleMeasure(Metric metric, @Nullable Rule rule, @Nullable RulePriority rulePriority, @Nullable Integer ruleCategory) {
+    this(metric, rule.ruleKey(), rulePriority, ruleCategory);
+  }
+
+  public RuleMeasure(Metric metric, @Nullable RuleKey ruleKey, @Nullable RulePriority rulePriority, @Nullable Integer ruleCategory) {
     super(metric);
-    this.rule = rule;
+    this.ruleKey = ruleKey;
     this.rulePriority = rulePriority;
   }
 
+  public RuleKey ruleKey() {
+    return ruleKey;
+  }
+
+  public RuleMeasure setRuleKey(RuleKey ruleKey) {
+    this.ruleKey = ruleKey;
+    return this;
+  }
+
+  /**
+   * @deprecated since 4.4 use {@link #ruleKey()}
+   */
+  @Deprecated
   public Rule getRule() {
-    return rule;
+    return Rule.create(ruleKey.repository(), ruleKey.rule());
   }
 
+  /**
+   * @deprecated since 4.4 use {@link #setRuleKey()}
+   */
+  @Deprecated
   public RuleMeasure setRule(Rule rule) {
-    this.rule = rule;
+    this.ruleKey = rule.ruleKey();
     return this;
   }
 
@@ -115,10 +139,10 @@ public class RuleMeasure extends Measure {
     }
     RuleMeasure other = (RuleMeasure) obj;
     return new EqualsBuilder()
-        .append(getMetric(), other.getMetric())
-        .append(personId, other.personId)
-        .append(rule, other.rule)
-        .isEquals();
+      .append(getMetric(), other.getMetric())
+      .append(personId, other.personId)
+      .append(ruleKey, other.ruleKey)
+      .isEquals();
   }
 
   @Override
@@ -129,35 +153,42 @@ public class RuleMeasure extends Measure {
   @Override
   public int hashCode() {
     return new HashCodeBuilder(17, 37)
-        .append(getMetric())
-        .append(personId)
-        .append(rule)
-        .toHashCode();
+      .append(getMetric())
+      .append(personId)
+      .append(ruleKey)
+      .toHashCode();
   }
 
   @Override
   public String toString() {
     return new ToStringBuilder(this)
-        .append("id", getId())
-        .append("metric", metric)
-        .append("personId", personId)
-        .append("rule", rule)
-        .append("value", value)
-        .append("data", data)
-        .append("description", description)
-        .append("alertStatus", alertStatus)
-        .append("alertText", alertText)
-        .append("tendency", tendency)
-        .append("severity", rulePriority)
-        .toString();
+      .append("metric", metric)
+      .append("personId", personId)
+      .append("ruleKey", ruleKey)
+      .append("value", value)
+      .append("data", data)
+      .append("description", description)
+      .append("alertStatus", alertStatus)
+      .append("alertText", alertText)
+      .append("tendency", tendency)
+      .append("severity", rulePriority)
+      .toString();
   }
 
+  /**
+   * @deprecated since 4.4 use {@link #createForRule(Metric, RuleKey, Double)}
+   */
+  @Deprecated
   public static RuleMeasure createForRule(Metric metric, Rule rule, @Nullable Double value) {
     return new RuleMeasure(metric, rule, null, null).setValue(value);
   }
 
+  public static RuleMeasure createForRule(Metric metric, RuleKey ruleKey, @Nullable Double value) {
+    return new RuleMeasure(metric, ruleKey, null, null).setValue(value);
+  }
+
   public static RuleMeasure createForPriority(Metric metric, RulePriority priority, @Nullable Double value) {
-    return new RuleMeasure(metric, null, priority, null).setValue(value);
+    return new RuleMeasure(metric, (RuleKey) null, priority, null).setValue(value);
   }
 
   /**
@@ -165,6 +196,6 @@ public class RuleMeasure extends Measure {
    */
   @Deprecated
   public static RuleMeasure createForCategory(Metric metric, Integer category, @Nullable Double value) {
-    return new RuleMeasure(metric, null, null, category).setValue(value);
+    return new RuleMeasure(metric, (RuleKey) null, null, category).setValue(value);
   }
 }
index 39453f963ff39a9d51dda44f7e402a4bf6cfa507..402a5ec9f9d12e53e8cf5a49064174b15d10668e 100644 (file)
@@ -34,7 +34,19 @@ import org.sonar.check.Cardinality;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -279,15 +291,15 @@ public class Rule {
 
   public RuleParam createParameter() {
     RuleParam parameter = new RuleParam()
-        .setRule(this);
+      .setRule(this);
     params.add(parameter);
     return parameter;
   }
 
   public RuleParam createParameter(String key) {
     RuleParam parameter = new RuleParam()
-        .setKey(key)
-        .setRule(this);
+      .setKey(key)
+      .setRule(this);
     params.add(parameter);
     return parameter;
   }
@@ -468,7 +480,6 @@ public class Rule {
     return this;
   }
 
-
   /**
    * For internal use only.
    *
@@ -499,34 +510,34 @@ public class Rule {
     }
     Rule other = (Rule) obj;
     return new EqualsBuilder()
-        .append(pluginName, other.getRepositoryKey())
-        .append(key, other.getKey())
-        .isEquals();
+      .append(pluginName, other.getRepositoryKey())
+      .append(key, other.getKey())
+      .isEquals();
   }
 
   @Override
   public int hashCode() {
     return new HashCodeBuilder(17, 37)
-        .append(pluginName)
-        .append(key)
-        .toHashCode();
+      .append(pluginName)
+      .append(key)
+      .toHashCode();
   }
 
   @Override
   public String toString() {
     // Note that ReflectionToStringBuilder will not work here - see SONAR-3077
     return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
-        .append("id", id)
-        .append("name", name)
-        .append("key", key)
-        .append("configKey", configKey)
-        .append("plugin", pluginName)
-        .append("severity", priority)
-        .append("cardinality", cardinality)
-        .append("status", status)
-        .append("language", language)
-        .append("parent", parent)
-        .toString();
+      .append("id", id)
+      .append("name", name)
+      .append("key", key)
+      .append("configKey", configKey)
+      .append("plugin", pluginName)
+      .append("severity", priority)
+      .append("cardinality", cardinality)
+      .append("status", status)
+      .append("language", language)
+      .append("parent", parent)
+      .toString();
   }
 
   @CheckForNull
index fadad455575287ea637f92684e01394927319260..b649a4e466acacbfdffa8bc94cc37531dadbffb7 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.api.technicaldebt.batch;
 
 import javax.annotation.CheckForNull;
 
+import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
 
@@ -30,7 +31,7 @@ import java.util.List;
  * @deprecated since 4.3
  */
 @Deprecated
-public interface Characteristic {
+public interface Characteristic extends Serializable {
 
   Integer id();
 
index 606d2a3fb1822ea65cec7c25e34c3c6d600fdea3..71204e9ffb837dac9eb4947825614c36614f1854 100644 (file)
@@ -24,6 +24,7 @@ import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.WorkUnit;
 import org.sonar.api.utils.internal.WorkDuration;
 
+import java.io.Serializable;
 import java.util.Date;
 
 /**
@@ -31,7 +32,7 @@ import java.util.Date;
  * @deprecated since 4.3
  */
 @Deprecated
-public interface Requirement {
+public interface Requirement extends Serializable {
 
   Integer id();
 
index f9f569f199b55e4d1c36027c9b558dbc9e538f5c..ea075ed2a9900a37623fc787658a7a81ec42b63c 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.rules.RulePriority;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 
 import static org.hamcrest.core.Is.is;
@@ -37,8 +38,8 @@ public class MeasuresFiltersTest {
     MeasuresFilter<Measure> filter = MeasuresFilters.metric(CoreMetrics.VIOLATIONS);
 
     Collection<Measure> measures = Arrays.asList(
-        RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 50.0),
-        new Measure(CoreMetrics.VIOLATIONS, 500.0));
+      RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 50.0),
+      new Measure(CoreMetrics.VIOLATIONS, 500.0));
 
     assertThat(filter.filter(measures).getValue(), is(500.0));
   }
@@ -46,10 +47,13 @@ public class MeasuresFiltersTest {
   @Test
   public void all() {
     Collection<Measure> measures = Arrays.asList(
-        RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 50.0),
-        new Measure(CoreMetrics.VIOLATIONS, 500.0));
+      RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 50.0),
+      new Measure(CoreMetrics.VIOLATIONS, 500.0));
 
-    assertThat(MeasuresFilters.all().filter(measures).size(), is(2));
+    Iterator<Measure> filteredMeasures = MeasuresFilters.all().filter(measures).iterator();
+    filteredMeasures.next();
+    filteredMeasures.next();
+    assertThat(filteredMeasures.hasNext(), is(false));
   }
 
   @Test
@@ -58,13 +62,13 @@ public class MeasuresFiltersTest {
     Rule rule2 = new Rule("pmd", "key2");
     MeasuresFilter<RuleMeasure> filter = MeasuresFilters.rule(CoreMetrics.VIOLATIONS, rule1);
     List<Measure> measures = Arrays.asList(
-        RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule1, 50.0),
-        RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule2, 10.0),
-        RuleMeasure.createForRule(CoreMetrics.VIOLATIONS_DENSITY, rule2, 3.3),
+      RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule1, 50.0),
+      RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule2, 10.0),
+      RuleMeasure.createForRule(CoreMetrics.VIOLATIONS_DENSITY, rule2, 3.3),
 
-        RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 400.0),
-        RuleMeasure.createForPriority(CoreMetrics.COVERAGE, RulePriority.CRITICAL, 400.0),
-        new Measure(CoreMetrics.VIOLATIONS, 500.0));
+      RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 400.0),
+      RuleMeasure.createForPriority(CoreMetrics.COVERAGE, RulePriority.CRITICAL, 400.0),
+      new Measure(CoreMetrics.VIOLATIONS, 500.0));
 
     assertThat(filter.filter(measures).getValue(), is(50.0));
   }
@@ -75,13 +79,13 @@ public class MeasuresFiltersTest {
     Rule rule2 = new Rule("pmd", "key2");
     MeasuresFilter<Collection<RuleMeasure>> filter = MeasuresFilters.rules(CoreMetrics.VIOLATIONS);
     List<Measure> measures = Arrays.asList(
-        RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule1, 50.0),
-        RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule2, 10.0),
-        RuleMeasure.createForRule(CoreMetrics.VIOLATIONS_DENSITY, rule2, 3.3),
+      RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule1, 50.0),
+      RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, rule2, 10.0),
+      RuleMeasure.createForRule(CoreMetrics.VIOLATIONS_DENSITY, rule2, 3.3),
 
-        RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 400.0),
-        RuleMeasure.createForPriority(CoreMetrics.COVERAGE, RulePriority.CRITICAL, 400.0),
-        new Measure(CoreMetrics.VIOLATIONS, 500.0));
+      RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 400.0),
+      RuleMeasure.createForPriority(CoreMetrics.COVERAGE, RulePriority.CRITICAL, 400.0),
+      new Measure(CoreMetrics.VIOLATIONS, 500.0));
 
     assertThat(filter.filter(measures).size(), is(2));
   }
@@ -90,10 +94,10 @@ public class MeasuresFiltersTest {
   public void measure() {
     MeasuresFilter<Measure> filter = MeasuresFilters.measure(new Measure(CoreMetrics.VIOLATIONS));
     List<Measure> measures = Arrays.asList(
-        new Measure(CoreMetrics.COMMENT_LINES, 50.0),
-        new Measure(CoreMetrics.VIOLATIONS, 10.0),
-        RuleMeasure.createForCategory(CoreMetrics.VIOLATIONS, 2, 12.0),
-        new Measure(CoreMetrics.COVERAGE, 15.0));
+      new Measure(CoreMetrics.COMMENT_LINES, 50.0),
+      new Measure(CoreMetrics.VIOLATIONS, 10.0),
+      RuleMeasure.createForCategory(CoreMetrics.VIOLATIONS, 2, 12.0),
+      new Measure(CoreMetrics.COVERAGE, 15.0));
 
     assertThat(filter.filter(measures).getValue(), is(10.0));
   }
index 091d8c781cd9830fd55f6bb8596947d25b2d07de..3f9f67ce23645ed2b8bd27a9128662cf0bc0f5d2 100644 (file)
@@ -46,7 +46,7 @@ public class IsRuleMeasure extends ArgumentMatcher<Measure> {
     }
     RuleMeasure m = (RuleMeasure) o;
     return ObjectUtils.equals(metric, m.getMetric()) &&
-      ObjectUtils.equals(rule, m.getRule()) &&
+      ObjectUtils.equals(rule.ruleKey(), m.ruleKey()) &&
       NumberUtils.compare(value, m.getValue()) == 0;
   }
 }