]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5644, SONAR-5473 Create new SCM extension point and fetch SCM data using WS
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 23 Sep 2014 08:51:34 +0000 (10:51 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Thu, 2 Oct 2014 15:52:23 +0000 (17:52 +0200)
30 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java [deleted file]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java [new file with mode: 0644]
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/FileData.java [new file with mode: 0644]
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java
sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java
sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
sonar-batch/src/main/java/org/sonar/batch/referential/DefaultProjectReferentialsLoader.java
sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdapter.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PreviousFileHashLoader.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java
sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java
sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivityConfiguration.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
sonar-batch/src/test/java/org/sonar/batch/referential/DefaultProjectReferentialsLoaderTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionFactoryTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionTest.java
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputFileFilter.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/measure/internal/DefaultMeasure.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java

index 1476e9aefe77ada4e9f835887400b13eff1d295f..dbf7b8af7d1c71cf42d104a1ad32e8d863dec227 100644 (file)
 package org.sonar.plugins.core;
 
 import com.google.common.collect.ImmutableList;
-import org.sonar.api.*;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.Properties;
+import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
+import org.sonar.api.SonarPlugin;
 import org.sonar.api.checks.NoSonarFilter;
 import org.sonar.core.timemachine.Periods;
 import org.sonar.plugins.core.batch.IndexProjectPostJob;
@@ -32,17 +36,81 @@ import org.sonar.plugins.core.dashboards.GlobalDefaultDashboard;
 import org.sonar.plugins.core.dashboards.ProjectDefaultDashboard;
 import org.sonar.plugins.core.dashboards.ProjectIssuesDashboard;
 import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard;
-import org.sonar.plugins.core.issue.*;
-import org.sonar.plugins.core.issue.notification.*;
+import org.sonar.plugins.core.issue.CountFalsePositivesDecorator;
+import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator;
+import org.sonar.plugins.core.issue.InitialOpenIssuesSensor;
+import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
+import org.sonar.plugins.core.issue.IssueHandlers;
+import org.sonar.plugins.core.issue.IssueTracking;
+import org.sonar.plugins.core.issue.IssueTrackingDecorator;
+import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher;
+import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate;
+import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher;
+import org.sonar.plugins.core.issue.notification.NewIssuesEmailTemplate;
+import org.sonar.plugins.core.issue.notification.NewIssuesNotificationDispatcher;
+import org.sonar.plugins.core.issue.notification.SendIssueNotificationsPostJob;
 import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
 import org.sonar.plugins.core.measurefilters.ProjectFilter;
 import org.sonar.plugins.core.notifications.alerts.NewAlerts;
 import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
-import org.sonar.plugins.core.sensors.*;
-import org.sonar.plugins.core.timemachine.*;
-import org.sonar.plugins.core.widgets.*;
-import org.sonar.plugins.core.widgets.issues.*;
-import org.sonar.plugins.core.widgets.measures.*;
+import org.sonar.plugins.core.sensors.BranchCoverageDecorator;
+import org.sonar.plugins.core.sensors.CommentDensityDecorator;
+import org.sonar.plugins.core.sensors.CoverageDecorator;
+import org.sonar.plugins.core.sensors.CoverageMeasurementFilter;
+import org.sonar.plugins.core.sensors.DirectoriesDecorator;
+import org.sonar.plugins.core.sensors.FileHashSensor;
+import org.sonar.plugins.core.sensors.FilesDecorator;
+import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator;
+import org.sonar.plugins.core.sensors.ItCoverageDecorator;
+import org.sonar.plugins.core.sensors.ItLineCoverageDecorator;
+import org.sonar.plugins.core.sensors.LineCoverageDecorator;
+import org.sonar.plugins.core.sensors.ManualMeasureDecorator;
+import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator;
+import org.sonar.plugins.core.sensors.OverallCoverageDecorator;
+import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator;
+import org.sonar.plugins.core.sensors.ProjectLinksSensor;
+import org.sonar.plugins.core.sensors.UnitTestDecorator;
+import org.sonar.plugins.core.sensors.VersionEventsSensor;
+import org.sonar.plugins.core.timemachine.NewCoverageAggregator;
+import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer;
+import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer;
+import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer;
+import org.sonar.plugins.core.timemachine.TendencyDecorator;
+import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister;
+import org.sonar.plugins.core.timemachine.VariationDecorator;
+import org.sonar.plugins.core.widgets.AlertsWidget;
+import org.sonar.plugins.core.widgets.BubbleChartWidget;
+import org.sonar.plugins.core.widgets.ComplexityWidget;
+import org.sonar.plugins.core.widgets.CoverageWidget;
+import org.sonar.plugins.core.widgets.CustomMeasuresWidget;
+import org.sonar.plugins.core.widgets.DebtOverviewWidget;
+import org.sonar.plugins.core.widgets.DescriptionWidget;
+import org.sonar.plugins.core.widgets.DocumentationCommentsWidget;
+import org.sonar.plugins.core.widgets.DuplicationsWidget;
+import org.sonar.plugins.core.widgets.EventsWidget;
+import org.sonar.plugins.core.widgets.HotspotMetricWidget;
+import org.sonar.plugins.core.widgets.HotspotMostViolatedRulesWidget;
+import org.sonar.plugins.core.widgets.ItCoverageWidget;
+import org.sonar.plugins.core.widgets.ProjectFileCloudWidget;
+import org.sonar.plugins.core.widgets.SizeWidget;
+import org.sonar.plugins.core.widgets.TechnicalDebtPyramidWidget;
+import org.sonar.plugins.core.widgets.TimeMachineWidget;
+import org.sonar.plugins.core.widgets.TimelineWidget;
+import org.sonar.plugins.core.widgets.TreemapWidget;
+import org.sonar.plugins.core.widgets.WelcomeWidget;
+import org.sonar.plugins.core.widgets.issues.ActionPlansWidget;
+import org.sonar.plugins.core.widgets.issues.FalsePositiveIssuesWidget;
+import org.sonar.plugins.core.widgets.issues.IssueFilterWidget;
+import org.sonar.plugins.core.widgets.issues.IssuesWidget;
+import org.sonar.plugins.core.widgets.issues.MyUnresolvedIssuesWidget;
+import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesPerAssigneeWidget;
+import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesStatusesWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsBubbleChartWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsCloudWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsHistogramWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsPieChartWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsTreemapWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterListWidget;
 
 import java.util.List;
 
@@ -186,7 +254,27 @@ import java.util.List;
     global = false,
     defaultValue = "admin",
     type = PropertyType.STRING,
-    multiValues = true)
+    multiValues = true),
+  @Property(
+    key = CoreProperties.SCM_ENABLED_KEY,
+    defaultValue = "true",
+    name = "Activation of the SCM Activity step",
+    description = "This property can be set to false in order to deactivate the SCM Activity step.",
+    module = false,
+    project = true,
+    global = true,
+    type = PropertyType.BOOLEAN
+  ),
+  @Property(
+    key = CoreProperties.SCM_PROVIDER_KEY,
+    defaultValue = "",
+    name = "Key of the SCM provider for this project",
+    description = "Force the provider to be used to get SCM information for this project. By default auto-detection is done. Exemple: svn, git.",
+    module = false,
+    project = true,
+    global = false,
+    type = PropertyType.BOOLEAN
+  )
 })
 public final class CorePlugin extends SonarPlugin {
 
index 5632e4956810f20a8668905943e0f82da7330931..d7d94be4c57c5f6ef3e7796cc91e74a97d0adc2f 100644 (file)
@@ -33,6 +33,7 @@ import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
 import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
 import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
 import org.sonar.api.config.Settings;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.resources.Project;
@@ -209,20 +210,23 @@ public class JavaCpdEngine extends CpdEngine {
       .setFromCore()
       .save();
     // Save
-    context.<Integer>newMeasure()
+    ((DefaultMeasure<Integer>) context.<Integer>newMeasure()
       .forMetric(CoreMetrics.DUPLICATED_FILES)
       .onFile(inputFile)
-      .withValue(1)
+      .withValue(1))
+      .setFromCore()
       .save();
-    context.<Integer>newMeasure()
+    ((DefaultMeasure<Integer>) context.<Integer>newMeasure()
       .forMetric(CoreMetrics.DUPLICATED_LINES)
       .onFile(inputFile)
-      .withValue(duplicatedLines.size())
+      .withValue(duplicatedLines.size()))
+      .setFromCore()
       .save();
-    context.<Integer>newMeasure()
+    ((DefaultMeasure<Integer>) context.<Integer>newMeasure()
       .forMetric(CoreMetrics.DUPLICATED_BLOCKS)
       .onFile(inputFile)
-      .withValue(duplicatedBlocks)
+      .withValue(duplicatedBlocks))
+      .setFromCore()
       .save();
 
     DuplicationBuilder builder = context.duplicationBuilder(inputFile);
index c7e43d9e0abfd9c3779acff2ec5649644d15ceca..e1988ac37ab97bef960c1b7c94568d107955a4d9 100644 (file)
@@ -22,10 +22,10 @@ package org.sonar.xoo;
 import org.sonar.api.SonarPlugin;
 import org.sonar.xoo.lang.CoveragePerTestSensor;
 import org.sonar.xoo.lang.MeasureSensor;
-import org.sonar.xoo.lang.ScmActivitySensor;
 import org.sonar.xoo.lang.SymbolReferencesSensor;
 import org.sonar.xoo.lang.SyntaxHighlightingSensor;
 import org.sonar.xoo.lang.TestCaseSensor;
+import org.sonar.xoo.lang.XooScmProvider;
 import org.sonar.xoo.lang.XooTokenizerSensor;
 import org.sonar.xoo.rule.CreateIssueByInternalKeySensor;
 import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor;
@@ -51,9 +51,11 @@ public class XooPlugin extends SonarPlugin {
       XooRulesDefinition.class,
       XooQualityProfile.class,
 
+      // SCM
+      XooScmProvider.class,
+
       // sensors
       MeasureSensor.class,
-      ScmActivitySensor.class,
       SyntaxHighlightingSensor.class,
       SymbolReferencesSensor.class,
       XooTokenizerSensor.class,
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java
deleted file mode 100644 (file)
index 12663b0..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.xoo.lang;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Charsets;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.fs.FileSystem;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.sensor.Sensor;
-import org.sonar.api.batch.sensor.SensorContext;
-import org.sonar.api.batch.sensor.SensorDescriptor;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.FileLinesContext;
-import org.sonar.api.measures.FileLinesContextFactory;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.xoo.Xoo;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Date;
-import java.util.List;
-
-public class ScmActivitySensor implements Sensor {
-
-  private static final Logger LOG = LoggerFactory.getLogger(ScmActivitySensor.class);
-
-  private static final String SCM_EXTENSION = ".scm";
-
-  private final FileSystem fs;
-  private final FileLinesContextFactory fileLinesContextFactory;
-
-  public ScmActivitySensor(FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem) {
-    this.fs = fileSystem;
-    this.fileLinesContextFactory = fileLinesContextFactory;
-  }
-
-  @Override
-  public void describe(SensorDescriptor descriptor) {
-    descriptor
-      .name(this.getClass().getSimpleName())
-      .provides(CoreMetrics.SCM_AUTHORS_BY_LINE,
-        CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE,
-        CoreMetrics.SCM_REVISIONS_BY_LINE)
-      .workOnLanguages(Xoo.KEY);
-  }
-
-  @Override
-  public void execute(SensorContext context) {
-    for (InputFile inputFile : fs.inputFiles(fs.predicates().hasLanguage(Xoo.KEY))) {
-      processFile(inputFile);
-    }
-
-  }
-
-  @VisibleForTesting
-  protected void processFile(InputFile inputFile) {
-    File ioFile = inputFile.file();
-    File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION);
-    if (!scmDataFile.exists()) {
-      LOG.debug("Skipping SCM data injection for " + inputFile.relativePath());
-      return;
-    }
-
-    FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile);
-    try {
-      List<String> lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name());
-      int lineNumber = 0;
-      for (String line : lines) {
-        lineNumber++;
-        if (StringUtils.isNotBlank(line)) {
-          // revision,author,dateTime
-          String[] fields = StringUtils.split(line, ',');
-          if (fields.length < 3) {
-            throw new IllegalStateException("Not enough fields on line " + lineNumber);
-          }
-          String revision = fields[0];
-          String author = fields[1];
-          // Will throw an exception, when date is not in format "yyyy-MM-dd"
-          Date date = DateUtils.parseDate(fields[2]);
-
-          fileLinesContext.setStringValue(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY, lineNumber, revision);
-          fileLinesContext.setStringValue(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY, lineNumber, author);
-          fileLinesContext.setStringValue(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, lineNumber, DateUtils.formatDateTime(date));
-        }
-      }
-    } catch (IOException e) {
-      throw new IllegalStateException(e);
-    }
-    fileLinesContext.save();
-  }
-}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooScmProvider.java
new file mode 100644 (file)
index 0000000..71582ef
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.xoo.lang;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.batch.scm.ScmProvider;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.DateUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class XooScmProvider implements ScmProvider {
+
+  private static final Logger LOG = LoggerFactory.getLogger(XooScmProvider.class);
+
+  private static final String SCM_EXTENSION = ".scm";
+
+  private final Settings settings;
+
+  public XooScmProvider(Settings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  public String key() {
+    return "xoo";
+  }
+
+  @Override
+  public boolean supports(File baseDir) {
+    return false;
+  }
+
+  @Override
+  public void blame(Iterable<InputFile> files, BlameResultHandler handler) {
+    for (InputFile inputFile : files) {
+      processFile(inputFile, handler);
+    }
+  }
+
+  @VisibleForTesting
+  protected void processFile(InputFile inputFile, BlameResultHandler handler) {
+    File ioFile = inputFile.file();
+    File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION);
+    if (!scmDataFile.exists()) {
+      throw new IllegalStateException("Missing file " + scmDataFile);
+    }
+
+    try {
+      List<String> lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name());
+      List<BlameLine> blame = new ArrayList<BlameLine>(lines.size());
+      int lineNumber = 0;
+      for (String line : lines) {
+        lineNumber++;
+        if (StringUtils.isNotBlank(line)) {
+          // revision,author,dateTime
+          String[] fields = StringUtils.split(line, ',');
+          if (fields.length < 3) {
+            throw new IllegalStateException("Not enough fields on line " + lineNumber);
+          }
+          String revision = fields[0];
+          String author = fields[1];
+          // Will throw an exception, when date is not in format "yyyy-MM-dd"
+          Date date = DateUtils.parseDate(fields[2]);
+
+          blame.add(new BlameLine(date, revision, author));
+        }
+      }
+      handler.handle(inputFile, blame);
+    } catch (IOException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/FileData.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/FileData.java
new file mode 100644 (file)
index 0000000..fc7e7be
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.protocol.input;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class FileData {
+
+  private final String hash;
+  private final String scmLastCommitDatetimesByLine;
+  private final String scmRevisionsByLine;
+  private final String scmAuthorsByLine;
+
+  public FileData(@Nullable String hash, @Nullable String scmLastCommitDatetimesByLine, @Nullable String scmRevisionsByLine, @Nullable String scmAuthorsByLine) {
+    this.hash = hash;
+    this.scmLastCommitDatetimesByLine = scmLastCommitDatetimesByLine;
+    this.scmRevisionsByLine = scmRevisionsByLine;
+    this.scmAuthorsByLine = scmAuthorsByLine;
+  }
+
+  @CheckForNull
+  public String hash() {
+    return hash;
+  }
+
+  @CheckForNull
+  public String scmLastCommitDatetimesByLine() {
+    return scmLastCommitDatetimesByLine;
+  }
+
+  @CheckForNull
+  public String scmRevisionsByLine() {
+    return scmRevisionsByLine;
+  }
+
+  @CheckForNull
+  public String scmAuthorsByLine() {
+    return scmAuthorsByLine;
+  }
+
+}
index 6a8d5c8691e6c5457bb2c0ec7c1823b00d258381..5e2bfafd59d67863020f26ac274184358116d6b2 100644 (file)
@@ -21,6 +21,8 @@ package org.sonar.batch.protocol.input;
 
 import com.google.gson.Gson;
 
+import javax.annotation.CheckForNull;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -37,6 +39,7 @@ public class ProjectReferentials {
   private Map<String, QProfile> qprofilesByLanguage = new HashMap<String, QProfile>();
   private Collection<ActiveRule> activeRules = new ArrayList<ActiveRule>();
   private Map<String, Map<String, String>> settingsByModule = new HashMap<String, Map<String, String>>();
+  private Map<String, FileData> fileDataPerPath = new HashMap<String, FileData>();
 
   public Map<String, String> settings(String projectKey) {
     return settingsByModule.containsKey(projectKey) ? settingsByModule.get(projectKey) : Collections.<String, String>emptyMap();
@@ -70,6 +73,15 @@ public class ProjectReferentials {
     return this;
   }
 
+  public Map<String, FileData> fileDataPerPath() {
+    return fileDataPerPath;
+  }
+
+  @CheckForNull
+  public FileData fileDataPerPath(String path) {
+    return fileDataPerPath.get(path);
+  }
+
   public long timestamp() {
     return timestamp;
   }
index 9196a831ec97289ddf360dd8c63908aa0c419bdd..ed98c51e46b40cdffaa2922a7086d2b62a2675f5 100644 (file)
@@ -49,6 +49,7 @@ public class ProjectReferentialsTest {
     activeRule.addParam("param1", "value1");
     ref.addActiveRule(activeRule);
     ref.setTimestamp(10);
+    ref.fileDataPerPath().put("src/main/java/Foo.java", new FileData("xyz", "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
 
     System.out.println(ref.toJson());
     JSONAssert
@@ -56,16 +57,19 @@ public class ProjectReferentialsTest {
         "{timestamp:10,"
           + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"Mar 14, 1984 12:00:00 AM\"}},"
           + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule,language:java,params:{param1:value1}}],"
-          + "settingsByModule:{foo:{prop1:value1,prop2:value2,prop:value}}}",
+          + "settingsByModule:{foo:{prop1:value1,prop2:value2,prop:value}},"
+          + "fileDataPerPath:{\"src/main/java/Foo.java\":{hash:xyz,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}}",
         ref.toJson(), true);
   }
 
   @Test
   public void testFromJson() throws JSONException, ParseException {
-    ProjectReferentials ref = ProjectReferentials.fromJson("{timestamp:1,"
-      + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"Mar 14, 1984 12:00:00 AM\"}},"
-      + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule1,language:java,params:{param1:value1}}],"
-      + "settingsByModule:{foo:{prop:value}}}");
+    ProjectReferentials ref = ProjectReferentials
+      .fromJson("{timestamp:1,"
+        + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"Mar 14, 1984 12:00:00 AM\"}},"
+        + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule1,language:java,params:{param1:value1}}],"
+        + "settingsByModule:{foo:{prop:value}},"
+        + "fileDataPerPath:{\"src/main/java/Foo.java\":{hash:xyz,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}}");
 
     assertThat(ref.timestamp()).isEqualTo(1);
 
@@ -83,5 +87,10 @@ public class ProjectReferentialsTest {
     assertThat(qProfile.name()).isEqualTo("Java");
     assertThat(qProfile.rulesUpdatedAt()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984"));
     assertThat(ref.settings("foo")).includes(MapAssert.entry("prop", "value"));
+
+    assertThat(ref.fileDataPerPath("src/main/java/Foo.java").hash()).isEqualTo("xyz");
+    assertThat(ref.fileDataPerPath("src/main/java/Foo.java").scmAuthorsByLine()).isEqualTo("1=henryju,2=gaudin");
+    assertThat(ref.fileDataPerPath("src/main/java/Foo.java").scmLastCommitDatetimesByLine()).isEqualTo("1=12345,2=3456");
+    assertThat(ref.fileDataPerPath("src/main/java/Foo.java").scmRevisionsByLine()).isEqualTo("1=345,2=345");
   }
 }
index 565417efad0b56885319a45708b963a04153899a..40df91b4ab486ff144f2964a047a37eab2b17a1b 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.batch.TimeMachine;
 import org.sonar.api.batch.TimeMachineQuery;
 import org.sonar.api.database.DatabaseSession;
 import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.ResourceModel;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.Metric;
@@ -38,6 +39,7 @@ import org.sonar.batch.index.DefaultIndex;
 import javax.annotation.Nullable;
 import javax.persistence.Query;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -148,6 +150,41 @@ public class DefaultTimeMachine implements TimeMachine {
     return jpaQuery.getResultList();
   }
 
+  // Temporary implementation for SONAR-5473
+  public List<MeasureModel> query(String resourceKey, Integer... metricIds) {
+    StringBuilder sb = new StringBuilder();
+    Map<String, Object> params = Maps.newHashMap();
+
+    sb.append("SELECT m");
+    sb.append(" FROM ")
+      .append(MeasureModel.class.getSimpleName())
+      .append(" m, ")
+      .append(ResourceModel.class.getSimpleName())
+      .append(" r, ")
+      .append(Snapshot.class.getSimpleName())
+      .append(" s WHERE m.snapshotId=s.id AND s.resourceId=r.id AND r.kee=:kee AND s.status=:status AND s.qualifier<>:lib");
+    params.put("kee", resourceKey);
+    params.put("status", Snapshot.STATUS_PROCESSED);
+    params.put("lib", Qualifiers.LIBRARY);
+
+    sb.append(" AND m.characteristicId IS NULL");
+    sb.append(" AND m.personId IS NULL");
+    sb.append(" AND m.ruleId IS NULL AND m.rulePriority IS NULL");
+    if (metricIds.length > 0) {
+      sb.append(" AND m.metricId IN (:metricIds) ");
+      params.put("metricIds", Arrays.asList(metricIds));
+    }
+    sb.append(" AND s.last=true ");
+    sb.append(" ORDER BY s.createdAt ");
+
+    Query jpaQuery = session.createQuery(sb.toString());
+
+    for (Map.Entry<String, Object> entry : params.entrySet()) {
+      jpaQuery.setParameter(entry.getKey(), entry.getValue());
+    }
+    return jpaQuery.getResultList();
+  }
+
   public Map<Integer, Metric> getMetricsById(TimeMachineQuery query) {
     Collection<Metric> metrics = metricFinder.findAll(query.getMetricKeys());
     Map<Integer, Metric> result = Maps.newHashMap();
index 8fcf38baafc6a06c8488af7fdb9337d86cb23cd1..c600e01f7e98befe1005a0f8642789b9d5e166ac 100644 (file)
@@ -30,6 +30,8 @@ import org.sonar.batch.maven.DefaultMavenPluginExecutor;
 import org.sonar.batch.maven.MavenProjectBootstrapper;
 import org.sonar.batch.maven.MavenProjectBuilder;
 import org.sonar.batch.maven.MavenProjectConverter;
+import org.sonar.batch.scm.ScmActivityConfiguration;
+import org.sonar.batch.scm.ScmActivitySensor;
 import org.sonar.core.config.CorePropertyDefinitions;
 
 import java.util.Collection;
@@ -51,7 +53,11 @@ public class BatchComponents {
       SubProjectDsmDecorator.class,
       DirectoryDsmDecorator.class,
       DirectoryTangleIndexDecorator.class,
-      FileTangleIndexDecorator.class
+      FileTangleIndexDecorator.class,
+
+      // SCM
+      ScmActivityConfiguration.class,
+      ScmActivitySensor.class
       );
     components.addAll(CorePropertyDefinitions.all());
     return components;
index 7174ad39c7b1f99c53c6c6c9c8d71e2c019fcab6..870bf5ab8751c6b010c06bab4bbdfff2e1c9bbcf 100644 (file)
@@ -215,7 +215,7 @@ public class DefaultIndex extends SonarIndex {
       if (metric == null) {
         throw new SonarException("Unknown metric: " + measure.getMetricKey());
       }
-      if (!isTechnicalProjectCopy(resource) && DefaultSensorContext.INTERNAL_METRICS.contains(metric)) {
+      if (!isTechnicalProjectCopy(resource) && !measure.isFromCore() && DefaultSensorContext.INTERNAL_METRICS.contains(metric)) {
         LOG.warn("Metric " + metric.key() + " is an internal metric computed by SonarQube. Please update your plugin.");
         return measure;
       }
index bcde2f88a05cc928e35a2480969e4d3097709353..98c46ef117254004277affe0af627ea186fc0a60 100644 (file)
  */
 package org.sonar.batch.referential;
 
+import com.google.common.collect.ImmutableList;
 import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.batch.DefaultTimeMachine;
 import org.sonar.batch.bootstrap.AnalysisMode;
 import org.sonar.batch.bootstrap.ServerClient;
 import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.FileData;
 import org.sonar.batch.protocol.input.ProjectReferentials;
 import org.sonar.batch.rule.ModuleQProfiles;
+import org.sonar.batch.scan.filesystem.PreviousFileHashLoader;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
 
 public class DefaultProjectReferentialsLoader implements ProjectReferentialsLoader {
 
   private static final String BATCH_PROJECT_URL = "/batch/project";
 
+  private static final List<Metric> METRICS = ImmutableList.<Metric>of(
+    CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE,
+    CoreMetrics.SCM_REVISIONS_BY_LINE,
+    CoreMetrics.SCM_AUTHORS_BY_LINE);
+
   private final ServerClient serverClient;
   private final AnalysisMode analysisMode;
+  private final PreviousFileHashLoader fileHashLoader;
+  private final MetricFinder metricFinder;
+  private final DefaultTimeMachine defaultTimeMachine;
 
-  public DefaultProjectReferentialsLoader(ServerClient serverClient, AnalysisMode analysisMode) {
+  public DefaultProjectReferentialsLoader(ServerClient serverClient, AnalysisMode analysisMode, PreviousFileHashLoader fileHashLoader, MetricFinder finder,
+    DefaultTimeMachine defaultTimeMachine) {
     this.serverClient = serverClient;
     this.analysisMode = analysisMode;
+    this.fileHashLoader = fileHashLoader;
+    this.metricFinder = finder;
+    this.defaultTimeMachine = defaultTimeMachine;
   }
 
   @Override
   public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
-    String url = BATCH_PROJECT_URL + "?key=" + reactor.getRoot().getKeyWithBranch();
+    String projectKey = reactor.getRoot().getKeyWithBranch();
+    String url = BATCH_PROJECT_URL + "?key=" + projectKey;
     if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) {
       try {
         url += "&profile=" + URLEncoder.encode(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP), "UTF-8");
@@ -52,6 +75,30 @@ public class DefaultProjectReferentialsLoader implements ProjectReferentialsLoad
       }
     }
     url += "&preview=" + analysisMode.isPreview();
-    return ProjectReferentials.fromJson(serverClient.request(url));
+    ProjectReferentials ref = ProjectReferentials.fromJson(serverClient.request(url));
+
+    Integer lastCommitsId = metricFinder.findByKey(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE.key()).getId();
+    Integer revisionsId = metricFinder.findByKey(CoreMetrics.SCM_REVISIONS_BY_LINE.key()).getId();
+    Integer authorsId = metricFinder.findByKey(CoreMetrics.SCM_AUTHORS_BY_LINE.key()).getId();
+    for (Map.Entry<String, String> hashByPaths : fileHashLoader.hashByRelativePath().entrySet()) {
+      String path = hashByPaths.getKey();
+      String hash = hashByPaths.getValue();
+      String lastCommits = null;
+      String revisions = null;
+      String authors = null;
+      List<MeasureModel> measures = defaultTimeMachine.query(projectKey + ":" + path, lastCommitsId, revisionsId, authorsId);
+      for (MeasureModel m : measures) {
+        if (m.getMetricId() == lastCommitsId) {
+          lastCommits = m.getData(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
+        } else if (m.getMetricId() == revisionsId) {
+          revisions = m.getData(CoreMetrics.SCM_REVISIONS_BY_LINE);
+        }
+        if (m.getMetricId() == authorsId) {
+          authors = m.getData(CoreMetrics.SCM_AUTHORS_BY_LINE);
+        }
+      }
+      ref.fileDataPerPath().put(path, new FileData(hash, lastCommits, revisions, authors));
+    }
+    return ref;
   }
 }
index 6c9b212e3f6eb55e24e98d155d3ec73c88341902..90c00db0ef54a6b53412f0041b50ac9354a748a7 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.batch.sensor.issue.Issue;
 import org.sonar.api.batch.sensor.issue.Issue.Severity;
 import org.sonar.api.batch.sensor.measure.Measure;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
 import org.sonar.api.batch.sensor.test.TestCase;
 import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
 import org.sonar.api.component.ResourcePerspectives;
@@ -95,18 +96,20 @@ public class SensorContextAdapter extends BaseSensorContext {
   }
 
   @Override
-  public void store(Measure measure) {
+  public void store(Measure newMeasure) {
+    DefaultMeasure measure = (DefaultMeasure) newMeasure;
     org.sonar.api.measures.Metric m = findMetricOrFail(measure.metric().key());
     org.sonar.api.measures.Measure measureToSave = new org.sonar.api.measures.Measure(m);
-    setValueAccordingToMetricType(measure, m, measureToSave);
-    if (measure.inputFile() != null) {
-      Formula formula = measure.metric() instanceof org.sonar.api.measures.Metric ?
-        ((org.sonar.api.measures.Metric) measure.metric()).getFormula() : null;
+    setValueAccordingToMetricType(newMeasure, m, measureToSave);
+    measureToSave.setFromCore(measure.isFromCore());
+    if (newMeasure.inputFile() != null) {
+      Formula formula = newMeasure.metric() instanceof org.sonar.api.measures.Metric ?
+        ((org.sonar.api.measures.Metric) newMeasure.metric()).getFormula() : null;
       if (formula instanceof SumChildDistributionFormula
         && !Scopes.isHigherThanOrEquals(Scopes.FILE, ((SumChildDistributionFormula) formula).getMinimumScopeToPersist())) {
         measureToSave.setPersistenceMode(PersistenceMode.MEMORY);
       }
-      sensorContext.saveMeasure(measure.inputFile(), measureToSave);
+      sensorContext.saveMeasure(newMeasure.inputFile(), measureToSave);
     } else {
       sensorContext.saveMeasure(measureToSave);
     }
index 0fa91ea3c938ed0dffc322eb6a72131b4c8eabfe..b86b501225ec98f34da80f2cfa9365c19e7e28ad 100644 (file)
@@ -48,14 +48,14 @@ public class PreviousFileHashLoader implements BatchComponent {
   /**
    * Extract hash of the files parsed during the previous analysis
    */
-  Map<String, String> hashByRelativePath() {
+  public Map<String, String> hashByRelativePath() {
     Map<String, String> map = Maps.newHashMap();
     PastSnapshot pastSnapshot = pastSnapshotFinder.findPreviousAnalysis(snapshot);
     if (pastSnapshot.isRelatedToSnapshot()) {
       Collection<SnapshotDataDto> selectSnapshotData = dao.selectSnapshotData(
         pastSnapshot.getProjectSnapshot().getId().longValue(),
         Arrays.asList(SnapshotDataTypes.FILE_HASHES)
-      );
+        );
       if (!selectSnapshotData.isEmpty()) {
         SnapshotDataDto snapshotDataDto = selectSnapshotData.iterator().next();
         String data = snapshotDataDto.getData();
index 159a1f7f48e229af50b9c6f16dd0e5fd5cc5babe..96840779d22daff3e37fc5af3fa53b604be1d146 100644 (file)
@@ -21,20 +21,23 @@ package org.sonar.batch.scan.filesystem;
 
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.fs.InputFile;
-
-import java.util.Map;
+import org.sonar.batch.protocol.input.FileData;
+import org.sonar.batch.protocol.input.ProjectReferentials;
 
 class StatusDetection {
 
-  private final Map<String, String> previousHashByRelativePath;
+  private final ProjectReferentials projectReferentials;
 
-  StatusDetection(Map<String, String> previousHashByRelativePath) {
-    this.previousHashByRelativePath = previousHashByRelativePath;
+  StatusDetection(ProjectReferentials projectReferentials) {
+    this.projectReferentials = projectReferentials;
   }
 
   InputFile.Status status(String relativePath, String hash) {
-    String previousHash = previousHashByRelativePath.get(relativePath);
-
+    FileData fileDataPerPath = projectReferentials.fileDataPerPath(relativePath);
+    if (fileDataPerPath == null) {
+      return InputFile.Status.ADDED;
+    }
+    String previousHash = fileDataPerPath.hash();
     if (StringUtils.equals(hash, previousHash)) {
       return InputFile.Status.SAME;
     }
index 53729dde5925458f15cd3d8ac8b3d6a48497c294..830cb346c09f0885c72fdc2957fc00f65d7a3f42 100644 (file)
 package org.sonar.batch.scan.filesystem;
 
 import org.sonar.api.BatchComponent;
-
-import java.util.Collections;
+import org.sonar.batch.protocol.input.ProjectReferentials;
 
 public class StatusDetectionFactory implements BatchComponent {
 
-  private final PreviousFileHashLoader previousFileHashLoader;
-
-  public StatusDetectionFactory(PreviousFileHashLoader l) {
-    this.previousFileHashLoader = l;
-  }
+  private final ProjectReferentials projectReferentials;
 
-  /**
-   * Used by scan2
-   */
-  public StatusDetectionFactory() {
-    this.previousFileHashLoader = null;
+  public StatusDetectionFactory(ProjectReferentials projectReferentials) {
+    this.projectReferentials = projectReferentials;
   }
 
   StatusDetection create() {
-    return new StatusDetection(previousFileHashLoader != null ? previousFileHashLoader.hashByRelativePath() : Collections.<String, String>emptyMap());
+    return new StatusDetection(projectReferentials);
   }
 }
index e8982ab4da7af7d4be4b4eaf0f1be3ce8489d501..0438d44c763e020e037f93ffa14656e28941496e 100644 (file)
@@ -59,7 +59,9 @@ public class DefaultSensorContext extends BaseSensorContext {
 
   private static final Logger LOG = LoggerFactory.getLogger(DefaultSensorContext.class);
 
-  public static final List<Metric> INTERNAL_METRICS = Arrays.<Metric>asList(CoreMetrics.DEPENDENCY_MATRIX,
+  public static final List<Metric> INTERNAL_METRICS = Arrays.<Metric>asList(
+    // Computed by DsmDecorator
+    CoreMetrics.DEPENDENCY_MATRIX,
     CoreMetrics.DIRECTORY_CYCLES,
     CoreMetrics.DIRECTORY_EDGES_WEIGHT,
     CoreMetrics.DIRECTORY_FEEDBACK_EDGES,
@@ -69,7 +71,17 @@ public class DefaultSensorContext extends BaseSensorContext {
     CoreMetrics.FILE_EDGES_WEIGHT,
     CoreMetrics.FILE_FEEDBACK_EDGES,
     CoreMetrics.FILE_TANGLE_INDEX,
-    CoreMetrics.FILE_TANGLES
+    CoreMetrics.FILE_TANGLES,
+    // Computed by ScmActivitySensor
+    CoreMetrics.SCM_AUTHORS_BY_LINE,
+    CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE,
+    CoreMetrics.SCM_REVISIONS_BY_LINE,
+    // Computed by core duplication plugin
+    CoreMetrics.DUPLICATIONS_DATA,
+    CoreMetrics.DUPLICATION_LINES_DATA,
+    CoreMetrics.DUPLICATED_FILES,
+    CoreMetrics.DUPLICATED_LINES,
+    CoreMetrics.DUPLICATED_BLOCKS
     );
   private final MeasureCache measureCache;
   private final IssueCache issueCache;
@@ -97,8 +109,8 @@ public class DefaultSensorContext extends BaseSensorContext {
   @Override
   public void store(Measure newMeasure) {
     DefaultMeasure<Serializable> measure = (DefaultMeasure<Serializable>) newMeasure;
-    if (INTERNAL_METRICS.contains(measure.metric())) {
-      LOG.warn("Metric " + measure.metric().key() + " is an internal metric computed by SonarQube. Please update your plugin.");
+    if (!measure.isFromCore() && INTERNAL_METRICS.contains(measure.metric())) {
+      LOG.warn("Metric " + measure.metric().key() + " is an internal metric computed by SonarQube. Please remove or update offending plugin.");
       return;
     }
     InputFile inputFile = measure.inputFile();
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivityConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivityConfiguration.java
new file mode 100644 (file)
index 0000000..3652606
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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.scm;
+
+import com.google.common.base.Joiner;
+import org.picocontainer.Startable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.scm.ScmProvider;
+import org.sonar.api.config.Settings;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public final class ScmActivityConfiguration implements BatchComponent, Startable {
+  private static final Logger LOG = LoggerFactory.getLogger(ScmActivityConfiguration.class);
+
+  private final ProjectReactor projectReactor;
+  private final Settings settings;
+  private final Map<String, ScmProvider> providerPerKey = new LinkedHashMap<String, ScmProvider>();
+
+  private ScmProvider provider;
+
+  public ScmActivityConfiguration(ProjectReactor projectReactor, Settings settings, ScmProvider... providers) {
+    this.projectReactor = projectReactor;
+    this.settings = settings;
+    for (ScmProvider scmProvider : providers) {
+      providerPerKey.put(scmProvider.key(), scmProvider);
+    }
+  }
+
+  public ScmActivityConfiguration(ProjectReactor projectReactor, Settings settings) {
+    this(projectReactor, settings, new ScmProvider[0]);
+  }
+
+  @Override
+  public void start() {
+    if (!settings.getBoolean(CoreProperties.SCM_ENABLED_KEY)) {
+      LOG.debug("SCM Step is disabled by configuration");
+      return;
+    }
+    if (settings.hasKey(CoreProperties.SCM_PROVIDER_KEY)) {
+      String forcedProviderKey = settings.getString(CoreProperties.SCM_PROVIDER_KEY);
+      if (providerPerKey.containsKey(forcedProviderKey)) {
+        this.provider = providerPerKey.get(forcedProviderKey);
+      } else {
+        throw new IllegalArgumentException("SCM provider was set to \"" + forcedProviderKey + "\" but no provider found for this key. Supported providers are "
+          + Joiner.on(",").join(providerPerKey.keySet()));
+      }
+    } else {
+      // Autodetection
+      for (ScmProvider provider : providerPerKey.values()) {
+        if (provider.supports(projectReactor.getRoot().getBaseDir())) {
+          if (this.provider == null) {
+            this.provider = provider;
+          } else {
+            throw new IllegalStateException("SCM provider autodetection failed. Both " + this.provider.key() + " and " + provider.key()
+              + " claim to support this project. Please use " + CoreProperties.SCM_PROVIDER_KEY + " to define SCM of your project.");
+          }
+        }
+      }
+      if (this.provider == null) {
+        throw new IllegalStateException("SCM provider autodetection failed. No provider claim to support this project. Please use " + CoreProperties.SCM_PROVIDER_KEY
+          + " to define SCM of your project.");
+      }
+    }
+  }
+
+  public ScmProvider provider() {
+    return provider;
+  }
+
+  @Override
+  public void stop() {
+
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java b/sonar-batch/src/main/java/org/sonar/batch/scm/ScmActivitySensor.java
new file mode 100644 (file)
index 0000000..4bdf516
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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.scm;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputFile.Status;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.batch.scm.ScmProvider.BlameResultHandler;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.PropertiesBuilder;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.protocol.input.FileData;
+import org.sonar.batch.protocol.input.ProjectReferentials;
+
+import java.nio.charset.Charset;
+import java.text.Normalizer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public final class ScmActivitySensor implements Sensor {
+
+  private static final Logger LOG = LoggerFactory.getLogger(ScmActivitySensor.class);
+
+  private static final Pattern NON_ASCII_CHARS = Pattern.compile("[^\\x00-\\x7F]");
+  private static final Pattern ACCENT_CODES = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+
+  private final ScmActivityConfiguration configuration;
+  private final FileSystem fs;
+  private final ProjectReferentials projectReferentials;
+
+  public ScmActivitySensor(ScmActivityConfiguration configuration, ProjectReferentials projectReferentials, FileSystem fs) {
+    this.configuration = configuration;
+    this.projectReferentials = projectReferentials;
+    this.fs = fs;
+  }
+
+  @Override
+  public void describe(SensorDescriptor descriptor) {
+    descriptor
+      .name("SCM Activity Sensor")
+      .provides(CoreMetrics.SCM_AUTHORS_BY_LINE,
+        CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE,
+        CoreMetrics.SCM_REVISIONS_BY_LINE);
+  }
+
+  @Override
+  public void execute(final SensorContext context) {
+    if (configuration.provider() == null) {
+      LOG.info("No SCM provider");
+      return;
+    }
+
+    TimeProfiler profiler = new TimeProfiler().start("Retrieve SCM blame information with encoding " + Charset.defaultCharset());
+
+    List<InputFile> filesToBlame = new LinkedList<InputFile>();
+    for (InputFile f : fs.inputFiles(fs.predicates().all())) {
+      FileData fileData = projectReferentials.fileDataPerPath(f.relativePath());
+      if (f.status() == Status.SAME
+        && fileData != null
+        && fileData.scmAuthorsByLine() != null
+        && fileData.scmLastCommitDatetimesByLine() != null
+        && fileData.scmRevisionsByLine() != null) {
+        saveMeasures(context, f, fileData.scmAuthorsByLine(), fileData.scmLastCommitDatetimesByLine(), fileData.scmRevisionsByLine());
+      } else {
+        filesToBlame.add(f);
+      }
+    }
+    configuration.provider().blame(filesToBlame, new BlameResultHandler() {
+
+      @Override
+      public void handle(InputFile file, List<BlameLine> lines) {
+
+        PropertiesBuilder<Integer, String> authors = propertiesBuilder(CoreMetrics.SCM_AUTHORS_BY_LINE);
+        PropertiesBuilder<Integer, String> dates = propertiesBuilder(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
+        PropertiesBuilder<Integer, String> revisions = propertiesBuilder(CoreMetrics.SCM_REVISIONS_BY_LINE);
+
+        int lineNumber = 1;
+        for (BlameLine line : lines) {
+          authors.add(lineNumber, normalizeString(line.getAuthor()));
+          dates.add(lineNumber, DateUtils.formatDateTime(line.getDate()));
+          revisions.add(lineNumber, line.getRevision());
+
+          lineNumber++;
+          // SONARPLUGINS-3097 For some SCM blame is missing on last empty line
+          if (lineNumber > lines.size() && lineNumber == file.lines()) {
+            authors.add(lineNumber, normalizeString(line.getAuthor()));
+            dates.add(lineNumber, DateUtils.formatDateTime(line.getDate()));
+            revisions.add(lineNumber, line.getRevision());
+          }
+        }
+
+        saveMeasures(context, file, authors.buildData(), dates.buildData(), revisions.buildData());
+
+      }
+    });
+    profiler.stop();
+  }
+
+  private String normalizeString(String inputString) {
+    String lowerCasedString = inputString.toLowerCase();
+    String stringWithoutAccents = removeAccents(lowerCasedString);
+    return removeNonAsciiCharacters(stringWithoutAccents);
+  }
+
+  private String removeAccents(String inputString) {
+    String unicodeDecomposedString = Normalizer.normalize(inputString, Normalizer.Form.NFD);
+    return ACCENT_CODES.matcher(unicodeDecomposedString).replaceAll("");
+  }
+
+  private String removeNonAsciiCharacters(String inputString) {
+    return NON_ASCII_CHARS.matcher(inputString).replaceAll("_");
+  }
+
+  private static PropertiesBuilder<Integer, String> propertiesBuilder(Metric metric) {
+    return new PropertiesBuilder<Integer, String>(metric);
+  }
+
+  /**
+   * This method is synchronized since it is allowed for plugins to compute blame in parallel.
+   */
+  private synchronized void saveMeasures(SensorContext context, InputFile f, String scmAuthorsByLine, String scmLastCommitDatetimesByLine, String scmRevisionsByLine) {
+    ((DefaultMeasure<String>) context.<String>newMeasure()
+      .onFile(f)
+      .forMetric(CoreMetrics.SCM_AUTHORS_BY_LINE)
+      .withValue(scmAuthorsByLine))
+      .setFromCore()
+      .save();
+    ((DefaultMeasure<String>) context.<String>newMeasure()
+      .onFile(f)
+      .forMetric(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)
+      .withValue(scmLastCommitDatetimesByLine))
+      .setFromCore()
+      .save();
+    ((DefaultMeasure<String>) context.<String>newMeasure()
+      .onFile(f)
+      .forMetric(CoreMetrics.SCM_REVISIONS_BY_LINE)
+      .withValue(scmRevisionsByLine))
+      .setFromCore()
+      .save();
+  }
+}
index 2044ee9910aa93d58a53f9c83871e57b436c511a..d0f60bb4858c6b0b65c2a248ecdc0594e9478b61 100644 (file)
@@ -66,7 +66,7 @@ public class MeasuresMediumTest {
       .newScanTask(new File(projectDir, "sonar-project.properties"))
       .start();
 
-    assertThat(result.measures()).hasSize(19);
+    assertThat(result.measures()).hasSize(13);
   }
 
   @Test
@@ -103,7 +103,7 @@ public class MeasuresMediumTest {
   }
 
   @Test
-  public void testDistributionMeasure() throws IOException {
+  public void testScmMeasure() throws IOException {
 
     File baseDir = temp.newFolder();
     File srcDir = new File(baseDir, "src");
@@ -132,6 +132,8 @@ public class MeasuresMediumTest {
         .put("sonar.projectVersion", "1.0-SNAPSHOT")
         .put("sonar.projectDescription", "Description of Foo Project")
         .put("sonar.sources", "src")
+        .put("sonar.scm.enabled", "true")
+        .put("sonar.scm.provider", "xoo")
         .build())
       .start();
 
index d93f9aecbe171a5efb94632acb17b73b8b5aeefd..95821910b74bfe8c54ec6d471c779c32c05b4dfe 100644 (file)
@@ -24,10 +24,14 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.batch.DefaultTimeMachine;
 import org.sonar.batch.bootstrap.AnalysisMode;
 import org.sonar.batch.bootstrap.ServerClient;
 import org.sonar.batch.bootstrap.TaskProperties;
 import org.sonar.batch.rule.ModuleQProfiles;
+import org.sonar.batch.scan.filesystem.PreviousFileHashLoader;
 
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -46,7 +50,9 @@ public class DefaultProjectReferentialsLoaderTest {
   public void prepare() {
     serverClient = mock(ServerClient.class);
     analysisMode = mock(AnalysisMode.class);
-    loader = new DefaultProjectReferentialsLoader(serverClient, analysisMode);
+    MetricFinder metricFinder = mock(MetricFinder.class);
+    when(metricFinder.findByKey(anyString())).thenReturn(new Metric().setId(1));
+    loader = new DefaultProjectReferentialsLoader(serverClient, analysisMode, mock(PreviousFileHashLoader.class), metricFinder, mock(DefaultTimeMachine.class));
     when(serverClient.request(anyString())).thenReturn("");
     reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
     taskProperties = new TaskProperties(Maps.<String, String>newHashMap(), "");
index ee24cbaec0e5f9b1bccb9b7bd5fb8d617bd0d74c..46bf71ecc8b6110fd8a390aecb73f57713cd4f40 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.batch.scan.filesystem;
 
 import org.junit.Test;
+import org.sonar.batch.protocol.input.ProjectReferentials;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -27,7 +28,7 @@ import static org.mockito.Mockito.mock;
 public class StatusDetectionFactoryTest {
   @Test
   public void testCreate() throws Exception {
-    StatusDetectionFactory factory = new StatusDetectionFactory(mock(PreviousFileHashLoader.class));
+    StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectReferentials.class));
     StatusDetection detection = factory.create();
     assertThat(detection).isNotNull();
   }
index 1c4ad73733bfc86e3eadf2d53bbbb4bcd499d8c4..ecadbe3b1be2dc41a0fe7aa0f190c25edc5ef5e4 100644 (file)
  */
 package org.sonar.batch.scan.filesystem;
 
-import com.google.common.collect.ImmutableMap;
 import org.junit.Test;
 import org.sonar.api.batch.fs.InputFile;
+import org.sonar.batch.protocol.input.FileData;
+import org.sonar.batch.protocol.input.ProjectReferentials;
 
 import static org.fest.assertions.Assertions.assertThat;
 
 public class StatusDetectionTest {
   @Test
   public void detect_status() throws Exception {
-    StatusDetection statusDetection = new StatusDetection(ImmutableMap.of(
-      "src/Foo.java", "ABCDE",
-      "src/Bar.java", "FGHIJ"
-    ));
+    ProjectReferentials ref = new ProjectReferentials();
+    ref.fileDataPerPath().put("src/Foo.java", new FileData("ABCDE", null, null, null));
+    ref.fileDataPerPath().put("src/Bar.java", new FileData("FGHIJ", null, null, null));
+    StatusDetection statusDetection = new StatusDetection(ref);
 
     assertThat(statusDetection.status("src/Foo.java", "ABCDE")).isEqualTo(InputFile.Status.SAME);
     assertThat(statusDetection.status("src/Foo.java", "XXXXX")).isEqualTo(InputFile.Status.CHANGED);
index 6dcf672f3c467a001d6398576bac35bc65fc4317..c9b6c5e7b2067967298230c3a80610fc3f68b84d 100644 (file)
@@ -535,4 +535,14 @@ public interface CoreProperties {
    * @since 4.5
    */
   String LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY = "size_metric";
+
+  /**
+   * @since 5.0
+   */
+  String SCM_ENABLED_KEY = "sonar.scm.enabled";
+
+  /**
+   * @since 5.0
+   */
+  String SCM_PROVIDER_KEY = "sonar.scm.provider";
 }
index 1ece5081290dd9d7f7737d3a41a515268e4dca6a..91836823e43a44758b85d53df6d1c1c3c6b2a2a9 100644 (file)
@@ -27,7 +27,6 @@ import org.sonar.api.BatchExtension;
  */
 public interface InputFileFilter extends BatchExtension {
 
-  // TODO requires a context (FileSystem) ?
   boolean accept(InputFile f);
 
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/BlameLine.java
new file mode 100644 (file)
index 0000000..2303b90
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.api.batch.scm;
+
+import java.util.Date;
+
+/**
+ * @since 5.0
+ */
+public class BlameLine {
+
+  private Date date;
+  private String revision;
+  private String author;
+  private String committer;
+
+  /**
+   * @param date of the commit
+   * @param revision of the commit
+   * @param author will also be used as committer identification
+   */
+  public BlameLine(Date date, String revision, String author) {
+    this(date, revision, author, author);
+  }
+
+  /**
+   *
+   * @param date of the commit
+   * @param revision of the commit
+   * @param author the person who wrote the line
+   * @param committer the person who committed the change
+   */
+  public BlameLine(Date date, String revision, String author, String committer) {
+    setDate(date);
+    setRevision(revision);
+    setAuthor(author);
+    setCommitter(committer);
+  }
+
+  public String getRevision() {
+    return revision;
+  }
+
+  public void setRevision(String revision) {
+    this.revision = revision;
+  }
+
+  public String getAuthor() {
+    return author;
+  }
+
+  public void setAuthor(String author) {
+    this.author = author;
+  }
+
+  public String getCommitter() {
+    return committer;
+  }
+
+  public void setCommitter(String committer) {
+    this.committer = committer;
+  }
+
+  /**
+   * @return the commit date
+   */
+  public Date getDate() {
+    if (date != null)
+    {
+      return (Date) date.clone();
+    }
+    return null;
+  }
+
+  public void setDate(Date date) {
+    if (date != null)
+    {
+      this.date = new Date(date.getTime());
+    }
+    else
+    {
+      this.date = null;
+    }
+  }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/ScmProvider.java
new file mode 100644 (file)
index 0000000..1fd3327
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.api.batch.scm;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.fs.InputFile;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @since 5.0
+ */
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public interface ScmProvider extends BatchExtension {
+
+  /**
+   * Unique identifier of the provider. Can be used in SCM URL to define the provider to use.
+   */
+  String key();
+
+  /**
+   * Does this provider able to manage files located in this directory.
+   * Used by autodetection.
+   */
+  boolean supports(File baseDir);
+
+  /**
+   * Compute blame of the provided files. Computation can be done in parallel.
+   * If there is an error that prevent to blame a file then an exception should be raised.
+   */
+  void blame(Iterable<InputFile> files, BlameResultHandler handler);
+
+  /**
+   * Callback for the provider to return results of blame per file.
+   */
+  public static interface BlameResultHandler {
+
+    void handle(InputFile file, List<BlameLine> lines);
+
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/scm/package-info.java
new file mode 100644 (file)
index 0000000..03eafe5
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.api.batch.scm;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index fee7172f9eaa7a030c10dbe32858b6b303c8dd9b..29b5c92bc2c1e8817d0b0c8e448f98a0965ccfa3 100644 (file)
@@ -42,6 +42,7 @@ public class DefaultMeasure<G extends Serializable> implements Measure<G> {
   private Metric<G> metric;
   private G value;
   private boolean saved = false;
+  private boolean fromCore = false;
 
   public DefaultMeasure() {
     this.storage = null;
@@ -84,6 +85,21 @@ public class DefaultMeasure<G extends Serializable> implements Measure<G> {
     return this;
   }
 
+  /**
+   * For internal use.
+   */
+  public boolean isFromCore() {
+    return fromCore;
+  }
+
+  /**
+   * For internal use. Used by core components to bypass check that prevent a plugin to store core measures.
+   */
+  public DefaultMeasure<G> setFromCore() {
+    this.fromCore = true;
+    return this;
+  }
+
   @Override
   public void save() {
     Preconditions.checkNotNull(this.storage, "No persister on this object");
index af2022282529adb07c4a7f7fca9a6bca08719b43..9a75236a5018a5122ecc1a9abaf23a2d6e86fdbf 100644 (file)
@@ -63,6 +63,7 @@ public class Measure<G extends Serializable> implements Serializable {
   protected Requirement requirement;
   protected Integer personId;
   protected PersistenceMode persistenceMode = PersistenceMode.FULL;
+  private boolean fromCore;
 
   public Measure(String metricKey) {
     this.metricKey = metricKey;
@@ -687,6 +688,20 @@ public class Measure<G extends Serializable> implements Serializable {
       && isZeroVariation(variation1, variation2, variation3, variation4, variation5);
   }
 
+  /**
+   * For internal use
+   */
+  public boolean isFromCore() {
+    return fromCore;
+  }
+
+  /**
+   * For internal use
+   */
+  public void setFromCore(boolean fromCore) {
+    this.fromCore = fromCore;
+  }
+
   private static boolean isZeroVariation(Double... variations) {
     for (Double variation : variations) {
       if (!((variation == null) || NumberUtils.compare(variation, 0.0) == 0)) {