aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-batch
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2014-04-17 23:33:27 +0200
committerSimon Brandhof <simon.brandhof@gmail.com>2014-04-17 23:33:49 +0200
commitf2dd46cf5745f7c14f2bce20f5131e4e242f8c7c (patch)
tree11ea192820faf6916a522d59886f923ac5bd6c1f /sonar-batch
parentfa306cf2509a427937642db79e391e48ce85221e (diff)
downloadsonarqube-f2dd46cf5745f7c14f2bce20f5131e4e242f8c7c.tar.gz
sonarqube-f2dd46cf5745f7c14f2bce20f5131e4e242f8c7c.zip
SONAR-4969 Do not mix batch and server classpaths
Diffstat (limited to 'sonar-batch')
-rw-r--r--sonar-batch/pom.xml13
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java47
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java7
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java3
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java61
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java215
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/debt/NewDebtDecorator.java109
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/ignore/IssueExclusionsConfiguration.java147
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java6
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java25
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/maven/DefaultMavenPluginExecutor.java149
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBootstrapper.java62
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBuilder.java63
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectConverter.java310
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/maven/package-info.java (renamed from sonar-batch/src/test/java/org/sonar/batch/issue/ignore/IssueExclusionsConfigurationTest.java)17
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java3
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java3
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java355
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/debt/NewDebtDecoratorTest.java375
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java58
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java17
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/maven/DefaultMavenPluginExecutorTest.java91
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectBootstrapperTest.java73
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectConverterTest.java245
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/maven/SonarMavenProjectBuilderTest.java82
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml18
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml27
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml8
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml11
39 files changed, 2424 insertions, 281 deletions
diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml
index 4d473f38149..caeffd62bcd 100644
--- a/sonar-batch/pom.xml
+++ b/sonar-batch/pom.xml
@@ -7,7 +7,6 @@
<version>4.3-SNAPSHOT</version>
</parent>
- <groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-batch</artifactId>
<name>SonarQube :: Batch</name>
@@ -40,6 +39,16 @@
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-plugin-api</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>jfree</groupId>
+ <artifactId>jcommon</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>jfree</groupId>
+ <artifactId>jfreechat</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
@@ -52,11 +61,13 @@
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
+ <version>3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
+ <version>3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
new file mode 100644
index 00000000000..02c98554fa6
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
@@ -0,0 +1,47 @@
+/*
+ * 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.bootstrap;
+
+import com.google.common.collect.Lists;
+import org.sonar.batch.debt.DebtDecorator;
+import org.sonar.batch.debt.IssueChangelogDebtCalculator;
+import org.sonar.batch.debt.NewDebtDecorator;
+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.core.config.CorePropertyDefinitions;
+
+import java.util.Collection;
+import java.util.List;
+
+public class BatchComponents {
+ public static Collection all() {
+ List components = Lists.newArrayList(
+ // Maven
+ MavenProjectBootstrapper.class, DefaultMavenPluginExecutor.class, MavenProjectConverter.class, MavenProjectBuilder.class,
+
+ // Debt
+ IssueChangelogDebtCalculator.class, DebtDecorator.class, NewDebtDecorator.class
+ );
+ components.addAll(CorePropertyDefinitions.all());
+ return components;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
index 039cc1c210c..bb2b2e431f2 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
@@ -44,6 +44,13 @@ public class ExtensionInstaller {
public ExtensionInstaller install(ComponentContainer container, ExtensionMatcher matcher) {
boolean preview = analysisMode.isPreview();
+
+ // core components
+ for (Object o : BatchComponents.all()) {
+ doInstall(container, matcher, null, preview, o);
+ }
+
+ // plugin extensions
for (Map.Entry<PluginMetadata, Plugin> entry : pluginRepository.getPluginsByMetadata().entrySet()) {
PluginMetadata metadata = entry.getKey();
Plugin plugin = entry.getValue();
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java
index c5992b6475a..d823a0650cf 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java
@@ -24,6 +24,7 @@ import org.sonar.api.config.Settings;
import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.resources.ResourceTypes;
import org.sonar.api.task.Task;
+import org.sonar.api.task.TaskComponent;
import org.sonar.api.task.TaskDefinition;
import org.sonar.api.task.TaskExtension;
import org.sonar.api.utils.SonarException;
@@ -55,7 +56,7 @@ public class TaskContainer extends ComponentContainer {
private void installTaskExtensions() {
getComponentByType(ExtensionInstaller.class).install(this, new ExtensionMatcher() {
public boolean accept(Object extension) {
- return ExtensionUtils.isType(extension, TaskExtension.class);
+ return ExtensionUtils.isType(extension, TaskComponent.class);
}
});
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
index 88de3031103..541a63a6bd7 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
@@ -20,24 +20,19 @@
package org.sonar.batch.components;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchExtension;
import org.sonar.api.CoreProperties;
-import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.Settings;
import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.resources.Qualifiers;
import javax.annotation.Nullable;
-
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.List;
public class PastSnapshotFinder implements BatchExtension {
@@ -196,60 +191,4 @@ public class PastSnapshotFinder implements BatchExtension {
}
}
- public static List<PropertyDefinition> getPropertyDefinitions() {
- return ImmutableList.of(
- PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 1)
- .name("Period 1")
- .description("Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " +
- "analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " +
- "compare to previous analysis</li><li>'previous_version' to compare to the previous version in the project history</li></ul>" +
- "<p>When specifying a number of days or a date, the snapshot selected for comparison is " +
- " the first one available inside the corresponding time range. </p>" +
- "<p>Changing this property only takes effect after subsequent project inspections.<p/>")
- .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1)
- .category(CoreProperties.CATEGORY_GENERAL)
- .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS)
- .build(),
-
- PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 2)
- .name("Period 2")
- .description("See the property 'Period 1'")
- .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2)
- .category(CoreProperties.CATEGORY_GENERAL)
- .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS)
- .build(),
-
- PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 3)
- .name("Period 3")
- .description("See the property 'Period 1'")
- .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3)
- .category(CoreProperties.CATEGORY_GENERAL)
- .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS)
- .build(),
-
- PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 4)
- .name("Period 4")
- .description("Period used to compare measures and track new violations. This property is specific to the project. Values are : " +
- "<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " +
- "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li>" +
- "<li>'previous_version' to compare to the previous version in the project history</li><li>A version, for example 1.2</li></ul>" +
- "<p>When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range. </p>" +
- "<p>Changing this property only takes effect after subsequent project inspections.<p/>")
- .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4)
- .onlyOnQualifiers(Qualifiers.PROJECT)
- .category(CoreProperties.CATEGORY_GENERAL)
- .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS)
- .build(),
-
- PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 5)
- .name("Period 5")
- .description("See the property 'Period 4'")
- .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5)
- .onlyOnQualifiers(Qualifiers.PROJECT)
- .category(CoreProperties.CATEGORY_GENERAL)
- .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS)
- .build()
- );
- }
-
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java
new file mode 100644
index 00000000000..71e9cc36a1c
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java
@@ -0,0 +1,215 @@
+/*
+ * 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.debt;
+
+import com.google.common.annotations.VisibleForTesting;
+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.rule.Rule;
+import org.sonar.api.batch.rule.Rules;
+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.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.PersistenceMode;
+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.RuleFinder;
+import org.sonar.api.technicaldebt.batch.Characteristic;
+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;
+import java.util.Set;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+
+/**
+ * Decorator that computes the technical debt metric
+ */
+@DependsUpon(DecoratorBarriers.ISSUES_TRACKED)
+public final class DebtDecorator implements Decorator {
+
+ private final ResourcePerspectives perspectives;
+ private final TechnicalDebtModel model;
+ private final Rules rules;
+
+ /**
+ * ruleFinder is needed to load "old" rule in order to persist rule measure
+ */
+ private final RuleFinder ruleFinder;
+
+ public DebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, Rules rules, RuleFinder ruleFinder) {
+ this.perspectives = perspectives;
+ this.model = model;
+ this.rules = rules;
+ this.ruleFinder = ruleFinder;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public List<Metric> generatesMetrics() {
+ return Arrays.asList(CoreMetrics.TECHNICAL_DEBT);
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ Issuable issuable = perspectives.as(Issuable.class, resource);
+ if (issuable != null && shouldSaveMeasure(context)) {
+ List<Issue> issues = newArrayList(issuable.issues());
+ saveMeasures(context, issues);
+ }
+ }
+
+ private void saveMeasures(DecoratorContext context, List<Issue> issues) {
+ Long total = 0L;
+ SumMap<RuleKey> ruleDebts = new SumMap<RuleKey>();
+ SumMap<Characteristic> characteristicDebts = new SumMap<Characteristic>();
+
+ // Aggregate rules debt from current issues (and populate current characteristic debt)
+ for (Issue issue : issues) {
+ Long debt = ((DefaultIssue) issue).debtInMinutes();
+ total += computeDebt(debt, issue.ruleKey(), ruleDebts, characteristicDebts);
+ }
+
+ // Aggregate rules debt from children (and populate children characteristics debt)
+ 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);
+ }
+
+ context.saveMeasure(CoreMetrics.TECHNICAL_DEBT, total.doubleValue());
+ saveOnRule(context, ruleDebts);
+ for (Characteristic characteristic : model.characteristics()) {
+ Long debt = characteristicDebts.get(characteristic);
+ saveCharacteristicMeasure(context, characteristic, debt != null ? debt.doubleValue() : 0d, false);
+ }
+ }
+
+ private Long computeDebt(@Nullable Long debt, RuleKey ruleKey, SumMap<RuleKey> ruleDebts, SumMap<Characteristic> characteristicDebts) {
+ if (debt != null) {
+ Rule rule = rules.find(ruleKey);
+ if (rule != null) {
+ String characteristicKey = rule.debtSubCharacteristic();
+ if (characteristicKey != null) {
+ Characteristic characteristic = model.characteristicByKey(characteristicKey);
+ if (characteristic != null) {
+ ruleDebts.add(ruleKey, debt);
+ characteristicDebts.add(characteristic, debt);
+ propagateTechnicalDebtInParents(characteristic.parent(), debt, characteristicDebts);
+ return debt;
+ }
+ }
+ }
+ }
+ return 0L;
+ }
+
+ private void propagateTechnicalDebtInParents(@Nullable Characteristic characteristic, long value, SumMap<Characteristic> characteristicDebts) {
+ if (characteristic != null) {
+ characteristicDebts.add(characteristic, value);
+ propagateTechnicalDebtInParents(characteristic.parent(), value, characteristicDebts);
+ }
+ }
+
+ private void saveOnRule(DecoratorContext context, SumMap<RuleKey> ruleDebts) {
+ for (Map.Entry<RuleKey, Long> entry : ruleDebts.entrySet()) {
+ org.sonar.api.rules.Rule oldRule = ruleFinder.findByKey(entry.getKey());
+ if (oldRule != null) {
+ saveRuleMeasure(context, oldRule, entry.getValue().doubleValue(), ResourceUtils.isEntity(context.getResource()));
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void saveCharacteristicMeasure(DecoratorContext context, Characteristic characteristic, Double value, boolean inMemory) {
+ // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122)
+ // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147)
+ if (value > 0.0 || (ResourceUtils.isProject(context.getResource()) && characteristic.isRoot())) {
+ Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT);
+ measure.setCharacteristic(characteristic);
+ saveMeasure(context, measure, value, inMemory);
+ }
+ }
+
+ @VisibleForTesting
+ void saveRuleMeasure(DecoratorContext context, org.sonar.api.rules.Rule rule, Double value, boolean inMemory) {
+ // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122)
+ // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147)
+ if (value > 0.0) {
+ RuleMeasure measure = new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, rule, null, null);
+ saveMeasure(context, measure, value, inMemory);
+ }
+ }
+
+ private void saveMeasure(DecoratorContext context, Measure measure, Double value, boolean inMemory) {
+ measure.setValue(value);
+ if (inMemory) {
+ measure.setPersistenceMode(PersistenceMode.MEMORY);
+ }
+ context.saveMeasure(measure);
+ }
+
+ private boolean shouldSaveMeasure(DecoratorContext context) {
+ return context.getMeasure(CoreMetrics.TECHNICAL_DEBT) == null;
+ }
+
+ private static class SumMap<E> {
+ private Map<E, Long> sumByKeys;
+
+ public SumMap() {
+ sumByKeys = newHashMap();
+ }
+
+ public void add(@Nullable E key, Long value) {
+ if (key != null) {
+ Long currentValue = sumByKeys.get(key);
+ sumByKeys.put(key, currentValue != null ? currentValue + value : value);
+ }
+ }
+
+ @CheckForNull
+ public Long get(E key) {
+ return sumByKeys.get(key);
+ }
+
+ public Set<Map.Entry<E, Long>> entrySet() {
+ return sumByKeys.entrySet();
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/NewDebtDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/NewDebtDecorator.java
new file mode 100644
index 00000000000..fe738d63de8
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/debt/NewDebtDecorator.java
@@ -0,0 +1,109 @@
+/*
+ * 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.debt;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.batch.*;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.batch.components.Period;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.debt.IssueChangelogDebtCalculator;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * Decorator that computes the technical debt metric
+ */
+@DependsUpon(DecoratorBarriers.ISSUES_TRACKED)
+public final class NewDebtDecorator implements Decorator {
+
+ private final ResourcePerspectives perspectives;
+ private final TimeMachineConfiguration timeMachineConfiguration;
+ private final IssueChangelogDebtCalculator issueChangelogDebtCalculator;
+
+ public NewDebtDecorator(ResourcePerspectives perspectives, TimeMachineConfiguration timeMachineConfiguration,
+ IssueChangelogDebtCalculator issueChangelogDebtCalculator) {
+ this.perspectives = perspectives;
+ this.timeMachineConfiguration = timeMachineConfiguration;
+ this.issueChangelogDebtCalculator = issueChangelogDebtCalculator;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public List<Metric> generatesMetrics() {
+ return ImmutableList.of(
+ CoreMetrics.NEW_TECHNICAL_DEBT
+ );
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ Issuable issuable = perspectives.as(Issuable.class, resource);
+ if (issuable != null && shouldSaveNewMetrics(context)) {
+ List<Issue> issues = newArrayList(issuable.issues());
+ saveMeasures(context, issues);
+ }
+ }
+
+ private void saveMeasures(DecoratorContext context, Collection<Issue> issues) {
+ Measure measure = new Measure(CoreMetrics.NEW_TECHNICAL_DEBT);
+ for (Period period : timeMachineConfiguration.periods()) {
+ Date periodDate = period.getDate();
+ double value = calculateNewTechnicalDebtValue(issues, periodDate);
+ Collection<Measure> children = context.getChildrenMeasures(measure.getMetric());
+ double sum = MeasureUtils.sumOnVariation(true, period.getIndex(), children) + value;
+ measure.setVariation(period.getIndex(), sum);
+ }
+ context.saveMeasure(measure);
+ }
+
+ private long calculateNewTechnicalDebtValue(Collection<Issue> issues, @Nullable Date periodDate) {
+ long result = 0;
+ for (Issue issue : issues) {
+ Long debt = issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, periodDate);
+ if (debt != null) {
+ result += debt;
+ }
+ }
+ return result;
+ }
+
+ private boolean shouldSaveNewMetrics(DecoratorContext context) {
+ return context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT) == null;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/IssueExclusionsConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/IssueExclusionsConfiguration.java
deleted file mode 100644
index 18c2f9fa8b4..00000000000
--- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/IssueExclusionsConfiguration.java
+++ /dev/null
@@ -1,147 +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.batch.issue.ignore;
-
-import com.google.common.collect.ImmutableList;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.PropertyType;
-import org.sonar.api.config.PropertyDefinition;
-import org.sonar.api.config.PropertyFieldDefinition;
-import org.sonar.api.resources.Qualifiers;
-
-import java.util.List;
-
-public final class IssueExclusionsConfiguration {
-
- public static final String SUB_CATEGORY_IGNORE_ISSUES = "issues";
-
- public static final String EXCLUSION_KEY_PREFIX = "sonar.issue.ignore";
- public static final String INCLUSION_KEY_PREFIX = "sonar.issue.enforce";
-
- public static final String MULTICRITERIA_SUFFIX = ".multicriteria";
- public static final String PATTERNS_MULTICRITERIA_EXCLUSION_KEY = EXCLUSION_KEY_PREFIX + MULTICRITERIA_SUFFIX;
- public static final String PATTERNS_MULTICRITERIA_INCLUSION_KEY = INCLUSION_KEY_PREFIX + MULTICRITERIA_SUFFIX;
- public static final String RESOURCE_KEY = "resourceKey";
- private static final String PROPERTY_FILE_PATH_PATTERN = "File Path Pattern";
- public static final String RULE_KEY = "ruleKey";
- private static final String PROPERTY_RULE_KEY_PATTERN = "Rule Key Pattern";
- private static final String PROPERTY_RULE_KEY_PATTERN_HELP = "<br/>A rule key pattern consists of the rule repository name, followed by a colon, followed by a rule key "
- + "or rule name fragment. For example:"
- + "<ul><li>squid:S1195</li><li>squid:*Naming*</li></ul>";
-
- public static final String BLOCK_SUFFIX = ".block";
- public static final String PATTERNS_BLOCK_KEY = EXCLUSION_KEY_PREFIX + BLOCK_SUFFIX;
- public static final String BEGIN_BLOCK_REGEXP = "beginBlockRegexp";
- public static final String END_BLOCK_REGEXP = "endBlockRegexp";
-
- public static final String ALLFILE_SUFFIX = ".allfile";
- public static final String PATTERNS_ALLFILE_KEY = EXCLUSION_KEY_PREFIX + ALLFILE_SUFFIX;
- public static final String FILE_REGEXP = "fileRegexp";
-
- static final int LARGE_SIZE = 40;
- static final int SMALL_SIZE = 10;
-
- private IssueExclusionsConfiguration() {
- // static configuration declaration only
- }
-
- public static List<PropertyDefinition> getPropertyDefinitions() {
- return ImmutableList.of(
- PropertyDefinition.builder(PATTERNS_MULTICRITERIA_EXCLUSION_KEY)
- .category(CoreProperties.CATEGORY_EXCLUSIONS)
- .subCategory(SUB_CATEGORY_IGNORE_ISSUES)
- .name("Ignore Issues on Multiple Criteria")
- .description("Patterns to ignore issues on certain components and for certain coding rules." + PROPERTY_RULE_KEY_PATTERN_HELP)
- .onQualifiers(Qualifiers.PROJECT)
- .index(3)
- .fields(
- PropertyFieldDefinition.build(RULE_KEY)
- .name(PROPERTY_RULE_KEY_PATTERN)
- .description("Pattern to match rules which should be ignored.")
- .type(PropertyType.STRING)
- .indicativeSize(LARGE_SIZE)
- .build(),
- PropertyFieldDefinition.build(RESOURCE_KEY)
- .name(PROPERTY_FILE_PATH_PATTERN)
- .description("Pattern to match files which should be ignored.")
- .type(PropertyType.STRING)
- .indicativeSize(LARGE_SIZE)
- .build())
- .build(),
- PropertyDefinition.builder(PATTERNS_BLOCK_KEY)
- .category(CoreProperties.CATEGORY_EXCLUSIONS)
- .subCategory(SUB_CATEGORY_IGNORE_ISSUES)
- .name("Ignore Issues in Blocks")
- .description("Patterns to ignore all issues on specific blocks of code, while continuing to scan and mark issues on the remainder of the file.")
- .onQualifiers(Qualifiers.PROJECT)
- .index(2)
- .fields(
- PropertyFieldDefinition.build(BEGIN_BLOCK_REGEXP)
- .name("Regular Expression for Start of Block")
- .description("If this regular expression is found in a file, then following lines are ignored until end of block.")
- .type(PropertyType.STRING)
- .indicativeSize(LARGE_SIZE)
- .build(),
- PropertyFieldDefinition.build(END_BLOCK_REGEXP)
- .name("Regular Expression for End of Block")
- .description("If specified, this regular expression is used to determine the end of code blocks to ignore. If not, then block ends at the end of file.")
- .type(PropertyType.STRING)
- .indicativeSize(LARGE_SIZE)
- .build())
- .build(),
- PropertyDefinition.builder(PATTERNS_ALLFILE_KEY)
- .category(CoreProperties.CATEGORY_EXCLUSIONS)
- .subCategory(SUB_CATEGORY_IGNORE_ISSUES)
- .name("Ignore Issues on Files")
- .description("Patterns to ignore all issues on files that contain a block of code matching a given regular expression.")
- .onQualifiers(Qualifiers.PROJECT)
- .index(1)
- .fields(
- PropertyFieldDefinition.build(FILE_REGEXP)
- .name("Regular Expression")
- .description("If this regular expression is found in a file, then the whole file is ignored.")
- .type(PropertyType.STRING)
- .indicativeSize(LARGE_SIZE)
- .build())
- .build(),
- PropertyDefinition.builder(PATTERNS_MULTICRITERIA_INCLUSION_KEY)
- .category(CoreProperties.CATEGORY_EXCLUSIONS)
- .subCategory(SUB_CATEGORY_IGNORE_ISSUES)
- .name("Restrict Scope of Coding Rules")
- .description("Patterns to restrict the application of a rule to only certain components, ignoring all others." + PROPERTY_RULE_KEY_PATTERN_HELP)
- .onQualifiers(Qualifiers.PROJECT)
- .index(4)
- .fields(
- PropertyFieldDefinition.build(RULE_KEY)
- .name(PROPERTY_RULE_KEY_PATTERN)
- .description("Pattern used to match rules which should be restricted.")
- .type(PropertyType.STRING)
- .indicativeSize(LARGE_SIZE)
- .build(),
- PropertyFieldDefinition.build(RESOURCE_KEY)
- .name(PROPERTY_FILE_PATH_PATTERN)
- .description("Pattern used to match files to which the rules should be restricted.")
- .type(PropertyType.STRING)
- .indicativeSize(LARGE_SIZE)
- .build())
- .build());
- }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java
index 03d218b3a3b..e29fc5aaa30 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java
@@ -19,8 +19,6 @@
*/
package org.sonar.batch.issue.ignore.pattern;
-import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
@@ -67,8 +65,8 @@ public abstract class AbstractPatternInitializer implements BatchExtension {
String patternConf = StringUtils.defaultIfBlank(settings.getString(getMulticriteriaConfigurationKey()), "");
for (String id : StringUtils.split(patternConf, ',')) {
String propPrefix = getMulticriteriaConfigurationKey() + "." + id + ".";
- String resourceKeyPattern = settings.getString(propPrefix + IssueExclusionsConfiguration.RESOURCE_KEY);
- String ruleKeyPattern = settings.getString(propPrefix + IssueExclusionsConfiguration.RULE_KEY);
+ String resourceKeyPattern = settings.getString(propPrefix + "resourceKey");
+ String ruleKeyPattern = settings.getString(propPrefix + "ruleKey");
String lineRange = "*";
String[] fields = new String[] { resourceKeyPattern, ruleKeyPattern, lineRange };
PatternDecoder.checkRegularLineConstraints(StringUtils.join(fields, ","), fields);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java
index 79b176b4721..75882be5422 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java
@@ -20,12 +20,11 @@
package org.sonar.batch.issue.ignore.pattern;
-import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.config.Settings;
+import org.sonar.core.config.IssueExclusionProperties;
import java.util.List;
@@ -45,7 +44,7 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer
@Override
protected String getMulticriteriaConfigurationKey() {
- return IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY;
+ return "sonar.issue.ignore" + ".multicriteria";
}
public PatternMatcher getPatternMatcher() {
@@ -54,7 +53,7 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer
@Override
public void initializePatternsForPath(String relativePath, String componentKey) {
- for (IssuePattern pattern: getMulticriteriaPatterns()) {
+ for (IssuePattern pattern : getMulticriteriaPatterns()) {
if (pattern.matchResource(relativePath)) {
getPatternMatcher().addPatternForComponent(componentKey, pattern);
}
@@ -70,12 +69,12 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer
protected final void loadFileContentPatterns() {
// Patterns Block
blockPatterns = Lists.newArrayList();
- String patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY), "");
+ String patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionProperties.PATTERNS_BLOCK_KEY), "");
for (String id : StringUtils.split(patternConf, ',')) {
- String propPrefix = IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + "." + id + ".";
- String beginBlockRegexp = getSettings().getString(propPrefix + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP);
- String endBlockRegexp = getSettings().getString(propPrefix + IssueExclusionsConfiguration.END_BLOCK_REGEXP);
- String[] fields = new String[] { beginBlockRegexp, endBlockRegexp };
+ String propPrefix = IssueExclusionProperties.PATTERNS_BLOCK_KEY + "." + id + ".";
+ String beginBlockRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.BEGIN_BLOCK_REGEXP);
+ String endBlockRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.END_BLOCK_REGEXP);
+ String[] fields = new String[]{beginBlockRegexp, endBlockRegexp};
PatternDecoder.checkDoubleRegexpLineConstraints(StringUtils.join(fields, ","), fields);
IssuePattern pattern = new IssuePattern().setBeginBlockRegexp(nullToEmpty(beginBlockRegexp)).setEndBlockRegexp(nullToEmpty(endBlockRegexp));
blockPatterns.add(pattern);
@@ -83,10 +82,10 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer
// Patterns All File
allFilePatterns = Lists.newArrayList();
- patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY), "");
+ patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionProperties.PATTERNS_ALLFILE_KEY), "");
for (String id : StringUtils.split(patternConf, ',')) {
- String propPrefix = IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + "." + id + ".";
- String allFileRegexp = getSettings().getString(propPrefix + IssueExclusionsConfiguration.FILE_REGEXP);
+ String propPrefix = IssueExclusionProperties.PATTERNS_ALLFILE_KEY + "." + id + ".";
+ String allFileRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.FILE_REGEXP);
PatternDecoder.checkWholeFileRegexp(allFileRegexp);
IssuePattern pattern = new IssuePattern().setAllFileRegexp(nullToEmpty(allFileRegexp));
allFilePatterns.add(pattern);
@@ -102,6 +101,6 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer
}
public boolean hasFileContentPattern() {
- return ! (blockPatterns.isEmpty() && allFilePatterns.isEmpty());
+ return !(blockPatterns.isEmpty() && allFilePatterns.isEmpty());
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java
index 5ce390af0be..bf87ad85313 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java
@@ -20,8 +20,6 @@
package org.sonar.batch.issue.ignore.pattern;
-import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
-
import com.google.common.collect.Maps;
import org.sonar.api.config.Settings;
@@ -38,7 +36,7 @@ public class IssueInclusionPatternInitializer extends AbstractPatternInitializer
@Override
protected String getMulticriteriaConfigurationKey() {
- return IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY;
+ return "sonar.issue.enforce" + ".multicriteria";
}
@Override
diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/DefaultMavenPluginExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/maven/DefaultMavenPluginExecutor.java
new file mode 100644
index 00000000000..fb0a1a56a20
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/maven/DefaultMavenPluginExecutor.java
@@ -0,0 +1,149 @@
+/*
+ * 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.maven;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.ReactorManager;
+import org.apache.maven.lifecycle.LifecycleExecutor;
+import org.apache.maven.project.MavenProject;
+import org.sonar.api.batch.SupportedEnvironment;
+import org.sonar.api.batch.maven.MavenPlugin;
+import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.resources.Project;
+import org.sonar.api.utils.SonarException;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
+import org.sonar.batch.scan.maven.MavenPluginExecutor;
+
+import javax.annotation.Nullable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+@SupportedEnvironment("maven")
+public class DefaultMavenPluginExecutor implements MavenPluginExecutor {
+
+ private final MavenProjectConverter pomConverter;
+ private LifecycleExecutor lifecycleExecutor;
+ private MavenSession mavenSession;
+
+ public DefaultMavenPluginExecutor(LifecycleExecutor le, MavenSession mavenSession, MavenProjectConverter pomConverter) {
+ this.lifecycleExecutor = le;
+ this.mavenSession = mavenSession;
+ this.pomConverter = pomConverter;
+ }
+
+ @Override
+ public final MavenPluginHandler execute(Project project, DefaultModuleFileSystem fs, MavenPluginHandler handler) {
+ for (String goal : handler.getGoals()) {
+ if (goal == null) {
+ throw new IllegalStateException("Maven goal can't be null");
+ }
+ MavenPlugin plugin = MavenPlugin.getPlugin(project.getPom(), handler.getGroupId(), handler.getArtifactId());
+ execute(project,
+ fs,
+ getGoal(handler.getGroupId(), handler.getArtifactId(), plugin != null && plugin.getPlugin() != null ? plugin.getPlugin().getVersion() : null, goal));
+ }
+ return handler;
+ }
+
+ @Override
+ public final void execute(Project project, DefaultModuleFileSystem fs, String goal) {
+ if (project.getPom() != null) {
+ TimeProfiler profiler = new TimeProfiler().start("Execute " + goal);
+ ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ concreteExecute(project.getPom(), goal);
+ } catch (Exception e) {
+ throw new SonarException("Unable to execute maven plugin", e);
+ } finally {
+ // Reset original ClassLoader that may have been changed during Maven Execution (see SONAR-1800)
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ profiler.stop();
+ }
+ if (!fs.isInitialized()) {
+ pomConverter.synchronizeFileSystem(project.getPom(), fs);
+ }
+ }
+ }
+
+ static String getGoal(String groupId, String artifactId, @Nullable String version, String goal) {
+ String defaultVersion = version == null ? "" : version;
+ return new StringBuilder()
+ .append(groupId).append(":")
+ .append(artifactId).append(":")
+ .append(defaultVersion)
+ .append(":")
+ .append(goal)
+ .toString();
+ }
+
+ public void concreteExecute(MavenProject pom, String goal) {
+ Method executeMethod = null;
+ for (Method m : lifecycleExecutor.getClass().getMethods()) {
+ if ("execute".equals(m.getName())) {
+ executeMethod = m;
+ break;
+ }
+ }
+ if (executeMethod == null) {
+ throw new SonarException("Unable to find execute method on Maven LifecycleExecutor. Please check your Maven version.");
+ }
+ if (executeMethod.getParameterTypes().length == 1) {
+ concreteExecuteMaven3(pom, goal);
+ } else if (executeMethod.getParameterTypes().length == 3) {
+ concreteExecuteMaven2(executeMethod, pom, goal);
+ } else {
+ throw new SonarException("Unexpected parameter count on Maven LifecycleExecutor#execute method. Please check your Maven version.");
+ }
+ }
+
+ public void concreteExecuteMaven3(MavenProject pom, String goal) {
+ MavenSession projectSession = mavenSession.clone();
+ projectSession.setCurrentProject(pom);
+ projectSession.setProjects(Arrays.asList(pom));
+ projectSession.getRequest().setRecursive(false);
+ projectSession.getRequest().setPom(pom.getFile());
+ projectSession.getRequest().setGoals(Arrays.asList(goal));
+ projectSession.getRequest().setInteractiveMode(false);
+ lifecycleExecutor.execute(projectSession);
+ if (projectSession.getResult().hasExceptions()) {
+ throw new SonarException("Exception during execution of " + goal);
+ }
+ }
+
+ public void concreteExecuteMaven2(Method executeMethod, MavenProject pom, String goal) {
+ try {
+ ReactorManager reactor = new ReactorManager(Arrays.asList(pom));
+ MavenSession clonedSession = new MavenSession(mavenSession.getContainer(),
+ mavenSession.getSettings(),
+ mavenSession.getLocalRepository(),
+ mavenSession.getEventDispatcher(),
+ reactor,
+ Arrays.asList(goal),
+ mavenSession.getExecutionRootDirectory(),
+ mavenSession.getExecutionProperties(),
+ mavenSession.getStartTime());
+ executeMethod.invoke(lifecycleExecutor, clonedSession, reactor, clonedSession.getEventDispatcher());
+ } catch (Exception e) {
+ throw new SonarException("Unable to execute Maven 2 plugin", e);
+ }
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBootstrapper.java b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBootstrapper.java
new file mode 100644
index 00000000000..3cd1f9d5c7f
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBootstrapper.java
@@ -0,0 +1,62 @@
+/*
+ * 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.maven;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.sonar.api.batch.SupportedEnvironment;
+import org.sonar.api.batch.bootstrap.ProjectBootstrapper;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+
+import java.util.List;
+
+/**
+ * @deprecated since 4.3 kept only to support old version of SonarQube Mojo
+ */
+@Deprecated
+@SupportedEnvironment("maven")
+public class MavenProjectBootstrapper implements ProjectBootstrapper {
+
+ private MavenSession session;
+ private MavenProjectConverter mavenProjectConverter;
+
+ public MavenProjectBootstrapper(MavenSession session, MavenProjectConverter mavenProjectConverter) {
+ this.session = session;
+ this.mavenProjectConverter = mavenProjectConverter;
+ }
+
+ @Override
+ public ProjectReactor bootstrap() {
+ // Don't use session.getTopLevelProject or session.getProjects to keep compatibility with Maven 2
+ List<MavenProject> sortedProjects = session.getSortedProjects();
+ MavenProject topLevelProject = null;
+ for (MavenProject project : sortedProjects) {
+ if (project.isExecutionRoot()) {
+ topLevelProject = project;
+ break;
+ }
+ }
+ if (topLevelProject == null) {
+ throw new IllegalStateException("Maven session does not declare a top level project");
+ }
+ return new ProjectReactor(mavenProjectConverter.configure(sortedProjects, topLevelProject));
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBuilder.java
new file mode 100644
index 00000000000..7cbf3efc5f2
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBuilder.java
@@ -0,0 +1,63 @@
+/*
+ * 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.maven;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.sonar.api.batch.SupportedEnvironment;
+import org.sonar.api.batch.bootstrap.ProjectBuilder;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+
+import java.util.List;
+
+/**
+ * Class that inject MavenProject in each module container
+ */
+@SupportedEnvironment("maven")
+public class MavenProjectBuilder extends ProjectBuilder {
+
+ private final MavenSession mavenSession;
+
+ public MavenProjectBuilder(MavenSession mavenSession) {
+ this.mavenSession = mavenSession;
+ }
+
+ @Override
+ public void build(Context context) {
+ ProjectReactor reactor = context.projectReactor();
+ for (ProjectDefinition moduleDef : reactor.getProjects()) {
+ setMavenProjectIfApplicable(moduleDef);
+ }
+ }
+
+ private void setMavenProjectIfApplicable(ProjectDefinition definition) {
+ if (mavenSession != null) {
+ String moduleKey = definition.getKey();
+ for (MavenProject mavenModule : (List<MavenProject>) mavenSession.getSortedProjects()) {
+ String mavenModuleKey = mavenModule.getGroupId() + ":" + mavenModule.getArtifactId();
+ if (mavenModuleKey.equals(moduleKey) && !definition.getContainerExtensions().contains(mavenModule)) {
+ definition.addContainerExtension(mavenModule);
+ }
+ }
+ }
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectConverter.java b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectConverter.java
new file mode 100644
index 00000000000..2ef3fe6fa54
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectConverter.java
@@ -0,0 +1,310 @@
+/*
+ * 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.maven;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.model.CiManagement;
+import org.apache.maven.model.IssueManagement;
+import org.apache.maven.model.Scm;
+import org.apache.maven.project.MavenProject;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.SupportedEnvironment;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.maven.MavenUtils;
+import org.sonar.api.task.TaskExtension;
+import org.sonar.api.utils.MessageException;
+import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
+import org.sonar.java.api.JavaUtils;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @deprecated since 4.3 kept only to support old version of SonarQube Mojo
+ */
+@Deprecated
+@SupportedEnvironment("maven")
+public class MavenProjectConverter implements TaskExtension {
+
+ private static final String UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE = "Unable to determine structure of project." +
+ " Probably you use Maven Advanced Reactor Options, which is not supported by SonarQube and should not be used.";
+
+ public ProjectDefinition configure(List<MavenProject> poms, MavenProject root) {
+ // projects by canonical path to pom.xml
+ Map<String, MavenProject> paths = Maps.newHashMap();
+ Map<MavenProject, ProjectDefinition> defs = Maps.newHashMap();
+
+ try {
+ configureModules(poms, paths, defs);
+
+ rebuildModuleHierarchy(paths, defs);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot configure project", e);
+ }
+
+ ProjectDefinition rootProject = defs.get(root);
+ if (rootProject == null) {
+ throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
+ }
+ return rootProject;
+ }
+
+ private void rebuildModuleHierarchy(Map<String, MavenProject> paths, Map<MavenProject, ProjectDefinition> defs) throws IOException {
+ for (Map.Entry<String, MavenProject> entry : paths.entrySet()) {
+ MavenProject pom = entry.getValue();
+ for (Object m : pom.getModules()) {
+ String moduleId = (String) m;
+ File modulePath = new File(pom.getBasedir(), moduleId);
+ MavenProject module = findMavenProject(modulePath, paths);
+
+ ProjectDefinition parentProject = defs.get(pom);
+ if (parentProject == null) {
+ throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
+ }
+ ProjectDefinition subProject = defs.get(module);
+ if (subProject == null) {
+ throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
+ }
+ parentProject.addSubProject(subProject);
+ }
+ }
+ }
+
+ private void configureModules(List<MavenProject> poms, Map<String, MavenProject> paths, Map<MavenProject, ProjectDefinition> defs) throws IOException {
+ for (MavenProject pom : poms) {
+ paths.put(pom.getFile().getCanonicalPath(), pom);
+ ProjectDefinition def = ProjectDefinition.create();
+ merge(pom, def);
+ defs.put(pom, def);
+ }
+ }
+
+ private static MavenProject findMavenProject(final File modulePath, Map<String, MavenProject> paths) throws IOException {
+ if (modulePath.exists() && modulePath.isDirectory()) {
+ for (Map.Entry<String, MavenProject> entry : paths.entrySet()) {
+ String pomFileParentDir = new File(entry.getKey()).getParent();
+ if (pomFileParentDir.equals(modulePath.getCanonicalPath())) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+ return paths.get(modulePath.getCanonicalPath());
+ }
+
+ @VisibleForTesting
+ void merge(MavenProject pom, ProjectDefinition definition) {
+ String key = getSonarKey(pom);
+ // IMPORTANT NOTE : reference on properties from POM model must not be saved,
+ // instead they should be copied explicitly - see SONAR-2896
+ definition
+ .setProperties(pom.getModel().getProperties())
+ .setKey(key)
+ .setVersion(pom.getVersion())
+ .setName(pom.getName())
+ .setDescription(pom.getDescription())
+ .addContainerExtension(pom);
+ guessJavaVersion(pom, definition);
+ guessEncoding(pom, definition);
+ convertMavenLinksToProperties(definition, pom);
+ synchronizeFileSystem(pom, definition);
+ }
+
+ private static String getSonarKey(MavenProject pom) {
+ return new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString();
+ }
+
+ private static void guessEncoding(MavenProject pom, ProjectDefinition definition) {
+ // See http://jira.codehaus.org/browse/SONAR-2151
+ String encoding = MavenUtils.getSourceEncoding(pom);
+ if (encoding != null) {
+ definition.setProperty(CoreProperties.ENCODING_PROPERTY, encoding);
+ }
+ }
+
+ private static void guessJavaVersion(MavenProject pom, ProjectDefinition definition) {
+ // See http://jira.codehaus.org/browse/SONAR-2148
+ // Get Java source and target versions from maven-compiler-plugin.
+ String version = MavenUtils.getJavaSourceVersion(pom);
+ if (version != null) {
+ definition.setProperty(JavaUtils.JAVA_SOURCE_PROPERTY, version);
+ }
+ version = MavenUtils.getJavaVersion(pom);
+ if (version != null) {
+ definition.setProperty(JavaUtils.JAVA_TARGET_PROPERTY, version);
+ }
+ }
+
+ /**
+ * For SONAR-3676
+ */
+ private static void convertMavenLinksToProperties(ProjectDefinition definition, MavenProject pom) {
+ setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_HOME_PAGE, pom.getUrl());
+
+ Scm scm = pom.getScm();
+ if (scm == null) {
+ scm = new Scm();
+ }
+ setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES, scm.getUrl());
+ setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES_DEV, scm.getDeveloperConnection());
+
+ CiManagement ci = pom.getCiManagement();
+ if (ci == null) {
+ ci = new CiManagement();
+ }
+ setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_CI, ci.getUrl());
+
+ IssueManagement issues = pom.getIssueManagement();
+ if (issues == null) {
+ issues = new IssueManagement();
+ }
+ setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_ISSUE_TRACKER, issues.getUrl());
+ }
+
+ private static void setPropertyIfNotAlreadyExists(ProjectDefinition definition, String propertyKey, String propertyValue) {
+ if (StringUtils.isBlank(definition.getProperties().getProperty(propertyKey))) {
+ definition.setProperty(propertyKey, StringUtils.defaultString(propertyValue));
+ }
+ }
+
+ public void synchronizeFileSystem(MavenProject pom, ProjectDefinition into) {
+ into.setBaseDir(pom.getBasedir());
+ File buildDir = getBuildDir(pom);
+ if (buildDir != null) {
+ into.setBuildDir(buildDir);
+ into.setWorkDir(getSonarWorkDir(pom));
+ }
+ into.setSourceDirs(toPaths(mainDirs(pom)));
+ into.setTestDirs(toPaths(testDirs(pom)));
+ File binaryDir = resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir());
+ if (binaryDir != null) {
+ into.addBinaryDir(binaryDir);
+ }
+ }
+
+ public static File getSonarWorkDir(MavenProject pom) {
+ return new File(getBuildDir(pom), "sonar");
+ }
+
+ private static File getBuildDir(MavenProject pom) {
+ return resolvePath(pom.getBuild().getDirectory(), pom.getBasedir());
+ }
+
+ public void synchronizeFileSystem(MavenProject pom, DefaultModuleFileSystem into) {
+ into.resetDirs(
+ pom.getBasedir(),
+ getBuildDir(pom),
+ mainDirs(pom),
+ testDirs(pom),
+ Arrays.asList(resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir())));
+ }
+
+ static File resolvePath(@Nullable String path, File basedir) {
+ if (path != null) {
+ File file = new File(StringUtils.trim(path));
+ if (!file.isAbsolute()) {
+ try {
+ file = new File(basedir, path).getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to resolve path '" + path + "'", e);
+ }
+ }
+ return file;
+ }
+ return null;
+ }
+
+ static List<File> resolvePaths(List<String> paths, File basedir) {
+ List<File> result = Lists.newArrayList();
+ for (String path : paths) {
+ File dir = resolvePath(path, basedir);
+ if (dir != null) {
+ result.add(dir);
+ }
+ }
+ return result;
+ }
+
+ private List<File> mainDirs(MavenProject pom) {
+ return sourceDirs(pom, ProjectDefinition.SOURCE_DIRS_PROPERTY, pom.getCompileSourceRoots());
+ }
+
+ private List<File> testDirs(MavenProject pom) {
+ return sourceDirs(pom, ProjectDefinition.TEST_DIRS_PROPERTY, pom.getTestCompileSourceRoots());
+ }
+
+ private List<File> sourceDirs(MavenProject pom, String propertyKey, List mavenDirs) {
+ List<String> paths;
+ String prop = pom.getProperties().getProperty(propertyKey);
+ if (prop != null) {
+ paths = Arrays.asList(StringUtils.split(prop, ","));
+ // do not remove dirs that do not exist. They must be kept in order to
+ // notify users that value of sonar.sources has a typo.
+ return existingDirsOrFail(resolvePaths(paths, pom.getBasedir()), pom, propertyKey);
+ }
+
+ List<File> dirs = resolvePaths(mavenDirs, pom.getBasedir());
+
+ // Maven provides some directories that do not exist. They
+ // should be removed
+ return keepExistingDirs(dirs);
+ }
+
+ private List<File> existingDirsOrFail(List<File> dirs, MavenProject pom, String propertyKey) {
+ for (File dir : dirs) {
+ if (!dir.isDirectory() || !dir.exists()) {
+ throw MessageException.of(String.format(
+ "The directory '%s' does not exist for Maven module %s. Please check the property %s",
+ dir.getAbsolutePath(), pom.getId(), propertyKey));
+ }
+ }
+ return dirs;
+ }
+
+ private static List<File> keepExistingDirs(List<File> files) {
+ return Lists.newArrayList(Collections2.filter(files, new Predicate<File>() {
+ @Override
+ public boolean apply(File dir) {
+ return dir != null && dir.exists() && dir.isDirectory();
+ }
+ }));
+ }
+
+ private static String[] toPaths(Collection<File> dirs) {
+ Collection<String> paths = Collections2.transform(dirs, new Function<File, String>() {
+ @Override
+ public String apply(File dir) {
+ return dir.getAbsolutePath();
+ }
+ });
+ return paths.toArray(new String[paths.size()]);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/IssueExclusionsConfigurationTest.java b/sonar-batch/src/main/java/org/sonar/batch/maven/package-info.java
index 79849ba1358..b595cf0548f 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/IssueExclusionsConfigurationTest.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/maven/package-info.java
@@ -18,16 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.issue.ignore;
-
-import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
+/**
+ * This package is a part of bootstrap process, so we should take care about backward compatibility.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.maven;
-import org.junit.Test;
-import static org.fest.assertions.Assertions.assertThat;
+import javax.annotation.ParametersAreNonnullByDefault;
-public class IssueExclusionsConfigurationTest {
- @Test
- public void justForCoverage() {
- assertThat(IssueExclusionsConfiguration.getPropertyDefinitions()).hasSize(4);
- }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
index 835aa9be91f..5180a498752 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
@@ -19,6 +19,7 @@
*/
package org.sonar.batch.scan;
+import org.sonar.api.BatchComponent;
import org.sonar.batch.qualitygate.GenerateQualityGateEvents;
import org.slf4j.Logger;
@@ -158,7 +159,7 @@ public class ModuleScanContainer extends ComponentContainer {
ExtensionInstaller installer = getComponentByType(ExtensionInstaller.class);
installer.install(this, new ExtensionMatcher() {
public boolean accept(Object extension) {
- if (ExtensionUtils.isType(extension, BatchExtension.class) && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_PROJECT)) {
+ if (ExtensionUtils.isType(extension, BatchComponent.class) && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_PROJECT)) {
// Special use-case: the extension point ProjectBuilder is used in a Maven environment to define some
// new sub-projects without pom.
// Example : C# plugin adds sub-projects at runtime, even if they are not defined in root pom.
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
index dee73ecd6c7..e4eacf6aa8d 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
@@ -20,6 +20,7 @@
package org.sonar.batch.scan;
import com.google.common.annotations.VisibleForTesting;
+import org.sonar.api.BatchComponent;
import org.sonar.api.BatchExtension;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.InstantiationStrategy;
@@ -201,7 +202,7 @@ public class ProjectScanContainer extends ComponentContainer {
static class BatchExtensionFilter implements ExtensionMatcher {
public boolean accept(Object extension) {
- return ExtensionUtils.isType(extension, BatchExtension.class)
+ return ExtensionUtils.isType(extension, BatchComponent.class)
&& ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_BATCH);
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java
new file mode 100644
index 00000000000..fc82ece746c
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java
@@ -0,0 +1,355 @@
+/*
+ * 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.debt;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.hamcrest.Description;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.rule.Rules;
+import org.sonar.api.batch.rule.internal.RulesBuilder;
+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.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.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.technicaldebt.batch.Characteristic;
+import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.test.IsMeasure;
+import org.sonar.api.utils.Duration;
+
+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.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.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DebtDecoratorTest {
+
+ static final int HOURS_IN_DAY = 8;
+
+ static final Long ONE_DAY_IN_MINUTES = 1L * HOURS_IN_DAY * 60;
+
+ @Mock
+ DecoratorContext context;
+
+ @Mock
+ Resource resource;
+
+ @Mock
+ TechnicalDebtModel debtModel;
+
+ @Mock
+ Issuable issuable;
+
+ @Mock
+ ResourcePerspectives perspectives;
+
+ @Mock
+ RuleFinder ruleFinder;
+
+ RuleKey ruleKey1 = RuleKey.of("repo1", "rule1");
+ RuleKey ruleKey2 = RuleKey.of("repo2", "rule2");
+ Rules rules;
+
+ DefaultCharacteristic efficiency = new DefaultCharacteristic().setKey("EFFICIENCY");
+ DefaultCharacteristic memoryEfficiency = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(efficiency);
+
+ DefaultCharacteristic reusability = new DefaultCharacteristic().setKey("REUSABILITY");
+ DefaultCharacteristic modularity = new DefaultCharacteristic().setKey("MODULARITY").setParent(reusability);
+
+ DebtDecorator decorator;
+
+ @Before
+ public void before() throws Exception {
+ when(perspectives.as(Issuable.class, resource)).thenReturn(issuable);
+ RulesBuilder rulesBuilder = new RulesBuilder();
+ rulesBuilder.add(ruleKey1).setName("rule1").setDebtSubCharacteristic("MEMORY_EFFICIENCY");
+ rulesBuilder.add(ruleKey2).setName("rule2").setDebtSubCharacteristic("MODULARITY");
+ rules = rulesBuilder.build();
+
+ when(ruleFinder.findByKey(ruleKey1)).thenReturn(org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule()));
+ when(ruleFinder.findByKey(ruleKey2)).thenReturn(org.sonar.api.rules.Rule.create(ruleKey2.repository(), ruleKey2.rule()));
+
+ when(debtModel.characteristics()).thenReturn(newArrayList(efficiency, memoryEfficiency, reusability, modularity));
+ when(debtModel.characteristicByKey("EFFICIENCY")).thenReturn(efficiency);
+ when(debtModel.characteristicByKey("MEMORY_EFFICIENCY")).thenReturn(memoryEfficiency);
+ when(debtModel.characteristicByKey("REUSABILITY")).thenReturn(reusability);
+ when(debtModel.characteristicByKey("MODULARITY")).thenReturn(modularity);
+
+ decorator = new DebtDecorator(perspectives, debtModel, rules, ruleFinder);
+ }
+
+ @Test
+ public void generates_metrics() throws Exception {
+ assertThat(decorator.generatesMetrics()).hasSize(1);
+ }
+
+ @Test
+ public void execute_on_project() throws Exception {
+ assertThat(decorator.shouldExecuteOnProject(null)).isTrue();
+ }
+
+ @Test
+ public void not_save_if_measure_already_computed() {
+ when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure());
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.TECHNICAL_DEBT)));
+ }
+
+ @Test
+ public void add_technical_debt_from_one_issue_and_no_parent() throws Exception {
+ Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES));
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue());
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue())));
+ }
+
+ @Test
+ public void add_technical_debt_from_one_issue_without_debt() throws Exception {
+ Issue issue = createIssue("rule1", "repo1").setDebt(null);
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 0.0);
+ }
+
+ @Test
+ public void add_technical_debt_from_one_issue_and_propagate_to_parents() throws Exception {
+ Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES));
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue());
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue())));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, ONE_DAY_IN_MINUTES.doubleValue())));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, ONE_DAY_IN_MINUTES.doubleValue())));
+ }
+
+ @Test
+ public void add_technical_debt_from_issues() throws Exception {
+ Long technicalDebt1 = ONE_DAY_IN_MINUTES;
+ Long technicalDebt2 = 2 * ONE_DAY_IN_MINUTES;
+
+ Issue issue1 = createIssue("rule1", "repo1").setDebt(Duration.create(technicalDebt1));
+ Issue issue2 = createIssue("rule1", "repo1").setDebt(Duration.create(technicalDebt1));
+ Issue issue3 = createIssue("rule2", "repo2").setDebt(Duration.create(technicalDebt2));
+ Issue issue4 = createIssue("rule2", "repo2").setDebt(Duration.create(technicalDebt2));
+ when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4));
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 6d * ONE_DAY_IN_MINUTES);
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 2d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey2, 4d * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void add_technical_debt_from_current_and_children_measures() throws Exception {
+ Issue issue1 = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES));
+ Issue issue2 = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES));
+ when(issuable.issues()).thenReturn(newArrayList(issue1, issue2));
+
+ when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.<Measure>newArrayList(
+ 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);
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 7d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, 7d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, 7d * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void add_technical_debt_only_from_children_measures() throws Exception {
+ when(issuable.issues()).thenReturn(Collections.<Issue>emptyList());
+
+ when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.<Measure>newArrayList(
+ new RuleMeasure(CoreMetrics.TECHNICAL_DEBT,
+ org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule())
+ , null, null).setValue(5d * ONE_DAY_IN_MINUTES),
+
+ 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);
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 5d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey2, 10d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, 5d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, 5d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, reusability, 10d * ONE_DAY_IN_MINUTES)));
+ verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, modularity, 10d * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void always_save_technical_debt_for_positive_values() throws Exception {
+ // for a project
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getResource()).thenReturn(new Project("foo"));
+ decorator.saveCharacteristicMeasure(context, (Characteristic) null, 12.0, false);
+ verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT));
+
+ // or for a file
+ context = mock(DecoratorContext.class);
+ when(context.getResource()).thenReturn(new File("foo"));
+ decorator.saveCharacteristicMeasure(context, (Characteristic) null, 12.0, false);
+ verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT));
+ }
+
+ @Test
+ public void always_save_technical_debt_for_project_if_top_characteristic() throws Exception {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getResource()).thenReturn(new Project("foo"));
+
+ // this is a top characteristic
+ DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("root");
+
+ decorator.saveCharacteristicMeasure(context, rootCharacteristic, 0.0, true);
+ verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT).setCharacteristic(rootCharacteristic));
+ }
+
+ /**
+ * SQALE-147
+ */
+ @Test
+ public void never_save_technical_debt_for_project_if_not_top_characteristic() throws Exception {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getResource()).thenReturn(new Project("foo"));
+
+ DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("EFFICIENCY");
+ DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(rootCharacteristic);
+
+ decorator.saveCharacteristicMeasure(context, characteristic, 0.0, true);
+ verify(context, never()).saveMeasure(any(Measure.class));
+ }
+
+ @Test
+ public void not_save_technical_debt_for_file_if_zero() throws Exception {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getResource()).thenReturn(new File("foo"));
+
+ decorator.saveCharacteristicMeasure(context, null, 0.0, true);
+ verify(context, never()).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT));
+ }
+
+ private DefaultIssue createIssue(String ruleKey, String repositoryKey) {
+ return new DefaultIssue().setRuleKey(RuleKey.of(repositoryKey, ruleKey));
+ }
+
+ class IsCharacteristicMeasure extends ArgumentMatcher<Measure> {
+ Metric metric = null;
+ Characteristic characteristic = null;
+ Double value = null;
+
+ public IsCharacteristicMeasure(Metric metric, Characteristic characteristic, Double value) {
+ this.metric = metric;
+ this.characteristic = characteristic;
+ this.value = value;
+ }
+
+ @Override
+ public boolean matches(Object o) {
+ if (!(o instanceof Measure)) {
+ return false;
+ }
+ Measure m = (Measure) o;
+ return ObjectUtils.equals(metric, m.getMetric()) &&
+ ObjectUtils.equals(characteristic, m.getCharacteristic()) &&
+ ObjectUtils.equals(value, m.getValue());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(new StringBuilder()
+ .append("value=").append(value).append(",")
+ .append("characteristic=").append(characteristic.key()).append(",")
+ .append("metric=").append(metric.getKey()).toString())
+ ;
+ }
+ }
+
+ class IsRuleMeasure extends ArgumentMatcher<RuleMeasure> {
+ Metric metric = null;
+ RuleKey ruleKey = null;
+ Double value = null;
+
+ public IsRuleMeasure(Metric metric, RuleKey ruleKey, Double value) {
+ this.metric = metric;
+ this.ruleKey = ruleKey;
+ this.value = value;
+ }
+
+ @Override
+ public boolean matches(Object o) {
+ if (!(o instanceof RuleMeasure)) {
+ return false;
+ }
+ 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(value, m.getValue());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE));
+ }
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/NewDebtDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/NewDebtDecoratorTest.java
new file mode 100644
index 00000000000..a80a19feac1
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/debt/NewDebtDecoratorTest.java
@@ -0,0 +1,375 @@
+/*
+ * 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.debt;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.config.Settings;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.FieldDiffs;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.test.IsMeasure;
+import org.sonar.api.utils.Duration;
+import org.sonar.batch.components.Period;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.debt.IssueChangelogDebtCalculator;
+import org.sonar.batch.debt.NewDebtDecorator;
+
+import java.util.Date;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NewDebtDecoratorTest {
+
+ NewDebtDecorator decorator;
+
+ @Mock
+ TimeMachineConfiguration timeMachineConfiguration;
+
+ @Mock
+ Resource resource;
+
+ @Mock
+ Issuable issuable;
+
+ @Mock
+ DecoratorContext context;
+
+ Date rightNow;
+ Date elevenDaysAgo;
+ Date tenDaysAgo;
+ Date nineDaysAgo;
+ Date fiveDaysAgo;
+ Date fourDaysAgo;
+
+ static final int HOURS_IN_DAY = 8;
+
+ static final Long ONE_DAY_IN_MINUTES = 1L * HOURS_IN_DAY * 60;
+ static final Long TWO_DAYS_IN_MINUTES = 2L * HOURS_IN_DAY * 60;
+ static final Long FIVE_DAYS_IN_MINUTES = 5L * HOURS_IN_DAY * 60;
+
+ @Before
+ public void setup() {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY);
+
+ ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+ when(perspectives.as(Issuable.class, resource)).thenReturn(issuable);
+
+ rightNow = new Date();
+ elevenDaysAgo = DateUtils.addDays(rightNow, -11);
+ tenDaysAgo = DateUtils.addDays(rightNow, -10);
+ nineDaysAgo = DateUtils.addDays(rightNow, -9);
+ fiveDaysAgo = DateUtils.addDays(rightNow, -5);
+ fourDaysAgo = DateUtils.addDays(rightNow, -4);
+
+ when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, fiveDaysAgo), new Period(2, tenDaysAgo)));
+
+ decorator = new NewDebtDecorator(perspectives, timeMachineConfiguration, new IssueChangelogDebtCalculator());
+ }
+
+ @Test
+ public void generates_metrics() throws Exception {
+ assertThat(decorator.generatesMetrics()).hasSize(1);
+ }
+
+ @Test
+ public void execute_on_project() throws Exception {
+ assertThat(decorator.shouldExecuteOnProject(null)).isTrue();
+ }
+
+ @Test
+ public void save_on_one_issue_with_one_new_changelog() {
+ Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ // changelog created at is null because it has just been created on the current analysis
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(null)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 1.0 * ONE_DAY_IN_MINUTES, 1.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_one_issue_with_changelog() {
+ Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(null),
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_one_issue_with_changelog_only_in_the_past() {
+ Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges(
+ newArrayList(
+ // Change before all periods
+ new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(elevenDaysAgo)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0)));
+ }
+
+ @Test
+ public void save_on_one_issue_with_changelog_having_null_value() {
+ Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null),
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo),
+ new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_one_issue_with_changelog_and_periods_have_no_dates() {
+ when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null)));
+
+ Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null),
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo),
+ new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_one_issue_with_changelog_having_not_only_technical_debt_changes() {
+ Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs()
+ .setDiff("actionPlan", "1.0", "1.1").setCreationDate(fourDaysAgo)
+ .setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_issues_with_changelog() {
+ Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow),
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo),
+ new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
+ )
+ );
+ Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow),
+ new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue1, issue2));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 7.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_one_issue_without_changelog() {
+ when(issuable.issues()).thenReturn(newArrayList(
+ (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)))
+ );
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 5.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_one_issue_without_technical_debt_and_without_changelog() {
+ when(issuable.issues()).thenReturn(newArrayList(
+ (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(null))
+ );
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0)));
+ }
+
+ @Test
+ public void save_on_one_issue_without_changelog_and_periods_have_no_dates() {
+ when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null)));
+
+ when(issuable.issues()).thenReturn(newArrayList(
+ (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)))
+ );
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is null, period2 is null
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_issues_without_changelog() {
+ when(issuable.issues()).thenReturn(newArrayList(
+ (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)),
+ new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES))
+ ));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 7.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void save_on_issues_with_changelog_and_issues_without_changelog() {
+ // issue1 and issue2 have changelog
+ Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow),
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo),
+ new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
+ )
+ );
+ Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges(
+ newArrayList(
+ new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow),
+ new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo)
+ )
+ );
+
+ // issue3 and issue4 have no changelog
+ Issue issue3 = new DefaultIssue().setKey("C").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES));
+ Issue issue4 = new DefaultIssue().setKey("D").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES));
+ when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 14.0 * ONE_DAY_IN_MINUTES)));
+ }
+
+ @Test
+ public void not_save_if_measure_already_computed() {
+ when(context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT)).thenReturn(new Measure());
+ when(issuable.issues()).thenReturn(newArrayList(
+ (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)),
+ new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES))
+ ));
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.NEW_TECHNICAL_DEBT)));
+ }
+
+ /**
+ * SONAR-5059
+ */
+ @Test
+ public void not_return_negative_debt() {
+ Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges(
+ newArrayList(
+ // changelog created at is null because it has just been created on the current analysis
+ new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, ONE_DAY_IN_MINUTES).setCreationDate(null)
+ )
+ );
+ when(issuable.issues()).thenReturn(newArrayList(issue));
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0)));
+ }
+
+
+ class IsVariationMeasure extends ArgumentMatcher<Measure> {
+ Metric metric = null;
+ Double var1 = null;
+ Double var2 = null;
+
+ public IsVariationMeasure(Metric metric, Double var1, Double var2) {
+ this.metric = metric;
+ this.var1 = var1;
+ this.var2 = var2;
+ }
+
+ public boolean matches(Object o) {
+ if (!(o instanceof Measure)) {
+ return false;
+ }
+ Measure m = (Measure) o;
+ return ObjectUtils.equals(metric, m.getMetric()) &&
+ ObjectUtils.equals(var1, m.getVariation1()) &&
+ ObjectUtils.equals(var2, m.getVariation2());
+ }
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java
index f478c284184..a0707867019 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java
@@ -20,14 +20,14 @@
package org.sonar.batch.issue.ignore.pattern;
-import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
-import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.SonarException;
+import org.sonar.core.config.IssueExclusionProperties;
+
import static org.fest.assertions.Assertions.assertThat;
public class IssueExclusionPatternInitializerTest {
@@ -38,7 +38,7 @@ public class IssueExclusionPatternInitializerTest {
@Before
public void init() {
- settings = new Settings(new PropertyDefinitions(IssueExclusionsConfiguration.getPropertyDefinitions()));
+ settings = new Settings(new PropertyDefinitions(IssueExclusionProperties.all()));
patternsInitializer = new IssueExclusionPatternInitializer(settings);
}
@@ -51,11 +51,11 @@ public class IssueExclusionPatternInitializerTest {
@Test
public void shouldHavePatternsBasedOnMulticriteriaPattern() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY, "1,2");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Bar.java");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, "*");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Hello.java");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RULE_KEY, "checkstyle:MagicNumber");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1,2");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", "org/foo/Bar.java");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", "*");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".2." + "resourceKey", "org/foo/Hello.java");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".2." + "ruleKey", "checkstyle:MagicNumber");
patternsInitializer.initPatterns();
assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue();
@@ -77,29 +77,29 @@ public class IssueExclusionPatternInitializerTest {
@Test(expected = SonarException.class)
public void shouldLogInvalidResourceKey() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY, "1");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, "");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, "*");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", "");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", "*");
patternsInitializer.initPatterns();
}
@Test(expected = SonarException.class)
public void shouldLogInvalidRuleKey() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY, "1");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, "*");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, "");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", "*");
+ settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", "");
patternsInitializer.initPatterns();
}
@Test
public void shouldReturnBlockPattern() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY, "1,2,3");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, "// SONAR-OFF");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, "// SONAR-ON");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, "// FOO-OFF");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, "// FOO-ON");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, "// IGNORE-TO-EOF");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, "");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY, "1,2,3");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// SONAR-OFF");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.END_BLOCK_REGEXP, "// SONAR-ON");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// FOO-OFF");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionProperties.END_BLOCK_REGEXP, "// FOO-ON");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// IGNORE-TO-EOF");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionProperties.END_BLOCK_REGEXP, "");
patternsInitializer.loadFileContentPatterns();
assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue();
@@ -112,17 +112,17 @@ public class IssueExclusionPatternInitializerTest {
@Test(expected = SonarException.class)
public void shouldLogInvalidStartBlockPattern() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY, "1");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, "");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, "// SONAR-ON");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY, "1");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.END_BLOCK_REGEXP, "// SONAR-ON");
patternsInitializer.loadFileContentPatterns();
}
@Test
public void shouldReturnAllFilePattern() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY, "1,2");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionsConfiguration.FILE_REGEXP, "@SONAR-IGNORE-ALL");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + ".2." + IssueExclusionsConfiguration.FILE_REGEXP, "//FOO-IGNORE-ALL");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY, "1,2");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionProperties.FILE_REGEXP, "@SONAR-IGNORE-ALL");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".2." + IssueExclusionProperties.FILE_REGEXP, "//FOO-IGNORE-ALL");
patternsInitializer.loadFileContentPatterns();
assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue();
@@ -135,8 +135,8 @@ public class IssueExclusionPatternInitializerTest {
@Test(expected = SonarException.class)
public void shouldLogInvalidAllFilePattern() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY, "1");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionsConfiguration.FILE_REGEXP, "");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY, "1");
+ settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionProperties.FILE_REGEXP, "");
patternsInitializer.loadFileContentPatterns();
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java
index ed93e89c8e7..eae28d70c05 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java
@@ -20,13 +20,12 @@
package org.sonar.batch.issue.ignore.pattern;
-import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
-import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer;
-
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
+import org.sonar.core.config.IssueExclusionProperties;
+
import static org.fest.assertions.Assertions.assertThat;
public class IssueInclusionPatternInitializerTest {
@@ -37,7 +36,7 @@ public class IssueInclusionPatternInitializerTest {
@Before
public void init() {
- settings = new Settings(new PropertyDefinitions(IssueExclusionsConfiguration.getPropertyDefinitions()));
+ settings = new Settings(new PropertyDefinitions(IssueExclusionProperties.all()));
patternsInitializer = new IssueInclusionPatternInitializer(settings);
}
@@ -49,11 +48,11 @@ public class IssueInclusionPatternInitializerTest {
@Test
public void shouldHavePatternsBasedOnMulticriteriaPattern() {
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY, "1,2");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Bar.java");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, "*");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Hello.java");
- settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RULE_KEY, "checkstyle:MagicNumber");
+ settings.setProperty("sonar.issue.enforce" + ".multicriteria", "1,2");
+ settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".1." + "resourceKey", "org/foo/Bar.java");
+ settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".1." + "ruleKey", "*");
+ settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".2." + "resourceKey", "org/foo/Hello.java");
+ settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".2." + "ruleKey", "checkstyle:MagicNumber");
patternsInitializer.initPatterns();
assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue();
diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/DefaultMavenPluginExecutorTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/DefaultMavenPluginExecutorTest.java
new file mode 100644
index 00000000000..75897a2abb3
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/maven/DefaultMavenPluginExecutorTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.maven;
+
+import org.apache.maven.project.MavenProject;
+import org.junit.Test;
+import org.sonar.api.batch.maven.MavenPlugin;
+import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.resources.Project;
+import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class DefaultMavenPluginExecutorTest {
+
+ @Test
+ public void plugin_version_should_be_optional() {
+ assertThat(DefaultMavenPluginExecutor.getGoal("group", "artifact", null, "goal"), is("group:artifact::goal"));
+ }
+
+ @Test
+ public void test_plugin_version() {
+ assertThat(DefaultMavenPluginExecutor.getGoal("group", "artifact", "3.54", "goal"), is("group:artifact:3.54:goal"));
+ }
+
+ @Test
+ public void should_ignore_non_maven_projects() {
+ DefaultMavenPluginExecutor executor = new DefaultMavenPluginExecutor(null, null, mock(MavenProjectConverter.class)) {
+ @Override
+ public void concreteExecute(MavenProject pom, String goal) {
+ pom.addCompileSourceRoot("src/java");
+ }
+ };
+ Project foo = new Project("foo");
+ DefaultModuleFileSystem fs = mock(DefaultModuleFileSystem.class);
+ executor.execute(foo, fs, new AddSourceMavenPluginHandler());
+
+ verify(fs, never()).resetDirs(any(File.class), any(File.class), anyList(), anyList(), anyList());
+ }
+
+ static class AddSourceMavenPluginHandler implements MavenPluginHandler {
+ public String getGroupId() {
+ return "fake";
+ }
+
+ public String getArtifactId() {
+ return "fake";
+ }
+
+ public String getVersion() {
+ return "2.2";
+ }
+
+ public boolean isFixedVersion() {
+ return false;
+ }
+
+ public String[] getGoals() {
+ return new String[]{"fake"};
+ }
+
+ public void configure(Project project, MavenPlugin plugin) {
+ }
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectBootstrapperTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectBootstrapperTest.java
new file mode 100644
index 00000000000..f4f7ea66051
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectBootstrapperTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.maven;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class MavenProjectBootstrapperTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void bootstrap() throws Exception {
+ ProjectDefinition def = mock(ProjectDefinition.class);
+ MavenSession session = mock(MavenSession.class);
+ MavenProject rootProject = new MavenProject();
+ rootProject.setExecutionRoot(true);
+ List<MavenProject> projects = Arrays.asList(rootProject);
+ when(session.getSortedProjects()).thenReturn(projects);
+
+ MavenProjectConverter pomConverter = mock(MavenProjectConverter.class);
+ when(pomConverter.configure(projects, rootProject)).thenReturn(def);
+ MavenProjectBootstrapper bootstrapper = new MavenProjectBootstrapper(session, pomConverter);
+
+ ProjectReactor reactor = bootstrapper.bootstrap();
+
+ assertThat(reactor).isNotNull();
+ verify(pomConverter).configure(projects, rootProject);
+ }
+
+ @Test
+ public void should_fail_if_no_top_level_project() throws Exception {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Maven session does not declare a top level project");
+
+ MavenSession session = mock(MavenSession.class);
+ MavenProjectConverter pomConverter = new MavenProjectConverter();
+ MavenProjectBootstrapper bootstrapper = new MavenProjectBootstrapper(session, pomConverter);
+
+ bootstrapper.bootstrap();
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectConverterTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectConverterTest.java
new file mode 100644
index 00000000000..be1fd2cfe3e
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectConverterTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.maven;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.hamcrest.core.Is;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.test.TestUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Properties;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class MavenProjectConverterTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ MavenProjectConverter converter = new MavenProjectConverter();
+
+ /**
+ * See SONAR-2681
+ */
+ @Test
+ public void shouldThrowExceptionWhenUnableToDetermineProjectStructure() {
+ MavenProject root = new MavenProject();
+ root.setFile(new File("/foo/pom.xml"));
+ root.getBuild().setDirectory("target");
+ root.getModules().add("module/pom.xml");
+
+ try {
+ converter.configure(Arrays.asList(root), root);
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage(), containsString("Advanced Reactor Options"));
+ }
+ }
+
+ @Test
+ public void shouldConvertModules() throws IOException {
+ File basedir = temp.newFolder();
+
+ MavenProject root = newMavenProject("com.foo", "parent", "1.0-SNAPSHOT");
+ root.setFile(new File(basedir, "pom.xml"));
+ root.getBuild().setDirectory("target");
+ root.getBuild().setOutputDirectory("target/classes");
+ root.getModules().add("module/pom.xml");
+ MavenProject module = newMavenProject("com.foo", "moduleA", "1.0-SNAPSHOT");
+ module.setFile(new File(basedir, "module/pom.xml"));
+ module.getBuild().setDirectory("target");
+ module.getBuild().setOutputDirectory("target/classes");
+ ProjectDefinition project = converter.configure(Arrays.asList(root, module), root);
+
+ assertThat(project.getSubProjects().size(), is(1));
+ }
+
+ private MavenProject newMavenProject(String groupId, String artifactId, String version) {
+ Model model = new Model();
+ model.setGroupId(groupId);
+ model.setArtifactId(artifactId);
+ model.setVersion(version);
+ return new MavenProject(model);
+ }
+
+ @Test
+ public void shouldConvertProperties() {
+ MavenProject pom = new MavenProject();
+ pom.setGroupId("foo");
+ pom.setArtifactId("bar");
+ pom.setVersion("1.0.1");
+ pom.setName("Test");
+ pom.setDescription("just test");
+ pom.setFile(new File("/foo/pom.xml"));
+ pom.getBuild().setDirectory("target");
+ ProjectDefinition project = ProjectDefinition.create();
+ converter.merge(pom, project);
+
+ Properties properties = project.getProperties();
+ assertThat(properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY), is("foo:bar"));
+ assertThat(properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY), is("1.0.1"));
+ assertThat(properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY), is("Test"));
+ assertThat(properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY), is("just test"));
+ }
+
+ @Test
+ public void moduleNameShouldEqualArtifactId() throws Exception {
+ File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/");
+ MavenProject parent = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml", true);
+ MavenProject module1 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml", false);
+ MavenProject module2 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml", false);
+
+ ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module1, module2), parent);
+
+ assertThat(rootDef.getSubProjects().size(), Is.is(2));
+ assertThat(rootDef.getKey(), Is.is("org.test:parent"));
+ assertNull(rootDef.getParent());
+ assertThat(rootDef.getBaseDir(), is(rootDir));
+
+ ProjectDefinition module1Def = rootDef.getSubProjects().get(0);
+ assertThat(module1Def.getKey(), Is.is("org.test:module1"));
+ assertThat(module1Def.getParent(), Is.is(rootDef));
+ assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "module1")));
+ assertThat(module1Def.getSubProjects().size(), Is.is(0));
+ }
+
+ @Test
+ public void moduleNameDifferentThanArtifactId() throws Exception {
+ File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/");
+ MavenProject parent = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml", true);
+ MavenProject module1 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml", false);
+ MavenProject module2 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml", false);
+
+ ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module1, module2), parent);
+
+ assertThat(rootDef.getSubProjects().size(), is(2));
+ assertThat(rootDef.getKey(), is("org.test:parent"));
+ assertNull(rootDef.getParent());
+ assertThat(rootDef.getBaseDir(), is(rootDir));
+
+ ProjectDefinition module1Def = rootDef.getSubProjects().get(0);
+ assertThat(module1Def.getKey(), Is.is("org.test:module1"));
+ assertThat(module1Def.getParent(), Is.is(rootDef));
+ assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "path1")));
+ assertThat(module1Def.getSubProjects().size(), Is.is(0));
+ }
+
+ @Test
+ public void should_find_module_with_maven_project_file_naming_different_from_pom_xml() throws Exception {
+ File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/");
+ MavenProject parent = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml", true);
+ MavenProject module = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml", false);
+
+ ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module), parent);
+
+ assertThat(rootDef.getSubProjects().size(), Is.is(1));
+ assertThat(rootDef.getKey(), Is.is("org.test:parent"));
+ assertNull(rootDef.getParent());
+ assertThat(rootDef.getBaseDir(), is(rootDir));
+
+ ProjectDefinition module1Def = rootDef.getSubProjects().get(0);
+ assertThat(module1Def.getKey(), Is.is("org.test:module"));
+ assertThat(module1Def.getParent(), Is.is(rootDef));
+ assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "module")));
+ assertThat(module1Def.getSubProjects().size(), Is.is(0));
+ }
+
+ @Test
+ public void testSingleProjectWithoutModules() throws Exception {
+ File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/");
+ MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml", true);
+
+ ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom);
+
+ assertThat(rootDef.getKey(), is("org.test:parent"));
+ assertThat(rootDef.getSubProjects().size(), is(0));
+ assertNull(rootDef.getParent());
+ assertThat(rootDef.getBaseDir(), is(rootDir));
+ }
+
+ @Test
+ public void shouldConvertLinksToProperties() throws Exception {
+ MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml", true);
+
+ ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom);
+
+ Properties props = rootDef.getProperties();
+ assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com");
+ assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com");
+ assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com");
+ assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com");
+ assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com");
+ }
+
+ @Test
+ public void shouldNotConvertLinksToPropertiesIfPropertyAlreadyDefined() throws Exception {
+ MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml", true);
+
+ ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom);
+
+ Properties props = rootDef.getProperties();
+
+ // Those properties have been fed by the POM elements <ciManagement>, <issueManagement>, ...
+ assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com");
+ assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com");
+ assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com");
+
+ // ... but those ones have been overridden by <properties> in the POM
+ assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com-OVERRIDEN-BY-PROPS");
+ assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com-OVERRIDEN-BY-PROPS");
+ }
+
+ @Test
+ public void shouldLoadSourceEncoding() throws Exception {
+ MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml", true);
+
+ ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom);
+
+ assertThat(rootDef.getProperties().getProperty(CoreProperties.ENCODING_PROPERTY)).isEqualTo("Shift_JIS");
+ }
+
+ private MavenProject loadPom(String pomPath, boolean isRoot) throws URISyntaxException, IOException, XmlPullParserException {
+ File pomFile = new File(getClass().getResource(pomPath).toURI());
+ Model model = new MavenXpp3Reader().read(new StringReader(FileUtils.readFileToString(pomFile)));
+ MavenProject pom = new MavenProject(model);
+ pom.setFile(pomFile);
+ pom.getBuild().setDirectory("target");
+ pom.setExecutionRoot(isRoot);
+ return pom;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/SonarMavenProjectBuilderTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/SonarMavenProjectBuilderTest.java
new file mode 100644
index 00000000000..16cbd06bc1e
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/maven/SonarMavenProjectBuilderTest.java
@@ -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.maven;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SonarMavenProjectBuilderTest {
+
+ @Test
+ public void testSimpleProject() {
+ MavenSession session = mock(MavenSession.class);
+ MavenProject rootProject = mock(MavenProject.class);
+ when(rootProject.isExecutionRoot()).thenReturn(true);
+ when(session.getSortedProjects()).thenReturn(Arrays.asList(rootProject));
+
+ MavenProjectConverter mavenProjectConverter = mock(MavenProjectConverter.class);
+ ProjectDefinition projectDefinition = ProjectDefinition.create();
+ when(mavenProjectConverter.configure(any(List.class), any(MavenProject.class))).thenReturn(projectDefinition);
+ MavenProjectBootstrapper builder = new MavenProjectBootstrapper(session, mavenProjectConverter);
+
+ assertThat(builder.bootstrap().getRoot()).isEqualTo(projectDefinition);
+
+ ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
+ verify(mavenProjectConverter).configure(argument.capture(), eq(rootProject));
+ assertThat(argument.getValue()).contains(rootProject);
+ }
+
+ @Test
+ public void testMultimoduleProject() {
+ MavenSession session = mock(MavenSession.class);
+ MavenProject rootProject = mock(MavenProject.class);
+ MavenProject module1 = mock(MavenProject.class);
+ MavenProject module2 = mock(MavenProject.class);
+ when(rootProject.isExecutionRoot()).thenReturn(true);
+ when(module1.isExecutionRoot()).thenReturn(false);
+ when(module2.isExecutionRoot()).thenReturn(false);
+ when(session.getSortedProjects()).thenReturn(Arrays.asList(module1, module2, rootProject));
+
+ MavenProjectConverter mavenProjectConverter = mock(MavenProjectConverter.class);
+ ProjectDefinition projectDefinition = ProjectDefinition.create();
+ when(mavenProjectConverter.configure(any(List.class), any(MavenProject.class))).thenReturn(projectDefinition);
+ MavenProjectBootstrapper builder = new MavenProjectBootstrapper(session, mavenProjectConverter);
+
+ assertThat(builder.bootstrap().getRoot()).isEqualTo(projectDefinition);
+
+ ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
+ verify(mavenProjectConverter).configure(argument.capture(), eq(rootProject));
+ assertThat(argument.getValue()).contains(module1, module2, rootProject);
+ }
+
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml
new file mode 100644
index 00000000000..7b49562c2e0
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml
new file mode 100644
index 00000000000..7b49562c2e0
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml
new file mode 100644
index 00000000000..9d2244eb148
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <modules>
+ <module>module</module>
+ </modules>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml
new file mode 100644
index 00000000000..470f2d1f6e5
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module1</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml
new file mode 100644
index 00000000000..88101678e0f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module2</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml
new file mode 100644
index 00000000000..afd92c0dbee
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <modules>
+ <module>path1</module>
+ <module>path2</module>
+ </modules>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml
new file mode 100644
index 00000000000..470f2d1f6e5
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module1</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml
new file mode 100644
index 00000000000..88101678e0f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module2</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml
new file mode 100644
index 00000000000..cc73a43ec08
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <modules>
+ <module>module1</module>
+ <module>module2</module>
+ </modules>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml
new file mode 100644
index 00000000000..460e8967e5c
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml
@@ -0,0 +1,18 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <url>http://home.com</url>
+ <ciManagement>
+ <url>http://ci.com</url>
+ </ciManagement>
+ <issueManagement>
+ <url>http://issues.com</url>
+ </issueManagement>
+ <scm>
+ <url>http://sources.com</url>
+ <developerConnection>http://sources-dev.com</developerConnection>
+ </scm>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml
new file mode 100644
index 00000000000..5b024e5c4a7
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml
@@ -0,0 +1,27 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <url>http://home.com</url>
+ <ciManagement>
+ <url>http://ci.com</url>
+ </ciManagement>
+ <issueManagement>
+ <url>http://issues.com</url>
+ </issueManagement>
+ <scm>
+ <url>http://sources.com</url>
+ <developerConnection>http://sources-dev.com</developerConnection>
+ </scm>
+
+
+
+ <properties>
+ <sonar.links.homepage>http://home.com-OVERRIDEN-BY-PROPS</sonar.links.homepage>
+ <sonar.links.scm>http://sources.com-OVERRIDEN-BY-PROPS</sonar.links.scm>
+ </properties>
+
+
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml
new file mode 100644
index 00000000000..ffd40530c5d
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml
@@ -0,0 +1,8 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml
new file mode 100644
index 00000000000..01bf73cfa9e
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <url>http://home.com</url>
+ <properties>
+ <project.build.sourceEncoding>Shift_JIS</project.build.sourceEncoding>
+ </properties>
+</project> \ No newline at end of file