]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5236 Create new API to register built-in quality profiles
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 12 Sep 2017 16:58:14 +0000 (18:58 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Mon, 25 Sep 2017 13:17:37 +0000 (15:17 +0200)
33 files changed:
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinition.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinitionTest.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImplTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java
sonar-plugin-api/src/main/java/org/sonar/api/profiles/AnnotationProfileParser.java
sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfileDefinition.java
sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java
sonar-plugin-api/src/main/java/org/sonar/api/profiles/XMLProfileParser.java
sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRule.java
sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoader.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinition.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/server/profile/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoaderTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinitionTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionAnnotationLoaderTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/LocalIssueTracking.java
tests/src/test/java/org/sonarqube/tests/issue/CustomRulesTest.java
tests/src/test/java/org/sonarqube/tests/qualityProfile/BuiltInQualityProfilesTest.java

index a61386c233fab73531552ffdf5ea9896845d9d29..922bd8fdda02a840e52d273d1a179020f3136904 100644 (file)
@@ -60,6 +60,7 @@ import org.sonar.xoo.rule.SaveDataTwiceSensor;
 import org.sonar.xoo.rule.Xoo2BasicProfile;
 import org.sonar.xoo.rule.Xoo2SonarWayProfile;
 import org.sonar.xoo.rule.XooBasicProfile;
+import org.sonar.xoo.rule.XooBuiltInQualityProfilesDefinition;
 import org.sonar.xoo.rule.XooEmptyProfile;
 import org.sonar.xoo.rule.XooFakeExporter;
 import org.sonar.xoo.rule.XooFakeImporter;
@@ -161,6 +162,9 @@ public class XooPlugin implements Plugin {
     if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(5, 5))) {
       context.addExtension(CpdTokenizerSensor.class);
     }
+    if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(6,6))) {
+      context.addExtension(XooBuiltInQualityProfilesDefinition.class);
+    }
   }
 
 }
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinition.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinition.java
new file mode 100644 (file)
index 0000000..41ac7e3
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.rule;
+
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.xoo.Xoo;
+
+import static org.sonar.xoo.rule.XooRulesDefinition.XOO_REPOSITORY;
+
+public class XooBuiltInQualityProfilesDefinition implements BuiltInQualityProfilesDefinition {
+  @Override
+  public void define(Context context) {
+    NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile("test BuiltInQualityProfilesDefinition", Xoo.KEY);
+    profile.setDefault(false);
+    NewBuiltInActiveRule rule = profile.activateRule(XOO_REPOSITORY, HasTagSensor.RULE_KEY);
+    rule.overrideSeverity("BLOCKER");
+    rule.overrideParam("tag", "TODO");
+    profile.done();
+  }
+}
index 94fdc279df15d57a6f2de8bab6a52e75957d25da..1e71ad76efca27cef516ec2ca80f10a57d73617f 100644 (file)
@@ -46,4 +46,12 @@ public class XooPluginTest {
     new XooPlugin().define(context);
     assertThat(context.getExtensions()).hasSize(49).contains(CpdTokenizerSensor.class);
   }
+
+  @Test
+  public void provide_extensions_for_6_6() {
+    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.6"), SonarQubeSide.SCANNER);
+    Plugin.Context context = new Plugin.Context(runtime);
+    new XooPlugin().define(context);
+    assertThat(context.getExtensions()).hasSize(50).contains(CpdTokenizerSensor.class);
+  }
 }
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinitionTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinitionTest.java
new file mode 100644 (file)
index 0000000..ce80f0f
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.xoo.rule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class XooBuiltInQualityProfilesDefinitionTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private XooBuiltInQualityProfilesDefinition underTest = new XooBuiltInQualityProfilesDefinition();
+
+  @Test
+  public void test_built_in_quality_profile() {
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+
+    underTest.define(context);
+
+    BuiltInQualityProfile profile = context.profile("xoo", "test BuiltInQualityProfilesDefinition");
+    assertThat(profile.isDefault()).isFalse();
+    assertThat(profile.name()).isEqualTo("test BuiltInQualityProfilesDefinition");
+    assertThat(profile.language()).isEqualTo("xoo");
+    assertThat(profile.rules()).hasSize(1);
+    BuiltInQualityProfilesDefinition.BuiltInActiveRule activeRule = profile.rule(RuleKey.of("xoo", "HasTag"));
+    assertThat(activeRule.overriddenSeverity()).isEqualTo("BLOCKER");
+    assertThat(activeRule.overriddenParams()).hasSize(1);
+    assertThat(activeRule.overriddenParam("tag").overriddenValue()).isEqualTo("TODO");
+  }
+}
index 759a323fa29585f6cf8c752631ff91a7a3986167..ffa45d1cda12e09b3abafbd45a8322e0f18028c5 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.api.resources.Languages;
 import org.sonar.api.resources.ResourceTypes;
 import org.sonar.api.rules.AnnotationRuleParser;
 import org.sonar.api.rules.XMLRuleParser;
+import org.sonar.api.server.profile.BuiltInQualityProfileAnnotationLoader;
 import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
 import org.sonar.api.utils.Durations;
 import org.sonar.api.utils.System2;
@@ -318,6 +319,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       XMLProfileParser.class,
       XMLProfileSerializer.class,
       AnnotationProfileParser.class,
+      BuiltInQualityProfileAnnotationLoader.class,
       Rules.QProfiles.class,
 
       // rule
index ff887bef380585b85fbaa949651cc481912b7eae..467fe263aac68b10309c65cc5e0812848f42985e 100644 (file)
@@ -123,7 +123,7 @@ public class ComputeEngineContainerImplTest {
     assertThat(picoContainer.getComponentAdapters())
       .hasSize(
         CONTAINER_ITSELF
-          + 72 // level 4
+          + 73 // level 4
           + 4 // content of CeConfigurationModule
           + 4 // content of CeQueueModule
           + 4 // content of CeHttpModule
index f6c7a77d64880a2e157d6808359799c240c44cda..0aca6403befc04aa41b32661fc8edafab71c9878 100644 (file)
@@ -152,6 +152,7 @@ import org.sonar.server.projecttag.ws.ProjectTagsWsModule;
 import org.sonar.server.property.InternalPropertiesImpl;
 import org.sonar.server.property.ws.PropertiesWs;
 import org.sonar.server.qualitygate.QualityGateModule;
+import org.sonar.server.qualityprofile.BuiltInQProfileDefinitionsBridge;
 import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryImpl;
 import org.sonar.server.qualityprofile.QProfileBackuperImpl;
 import org.sonar.server.qualityprofile.QProfileComparison;
@@ -276,6 +277,7 @@ public class PlatformLevel4 extends PlatformLevel {
       BillingValidationsProxyImpl.class,
 
       // quality profile
+      BuiltInQProfileDefinitionsBridge.class,
       BuiltInQProfileRepositoryImpl.class,
       ActiveRuleIndexer.class,
       XMLProfileParser.class,
index 2109187bef0dc22077e9d53c7ffc0fddf9878955..32658aa49b4e6641d899d2bf1dd01754a32754c7 100644 (file)
@@ -23,16 +23,16 @@ import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.concurrent.Immutable;
-import org.sonar.api.profiles.ProfileDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
 
 /**
- * Represent a Quality Profile as computed from {@link ProfileDefinition} provided by installed plugins.
+ * Represent a Quality Profile as computed from {@link BuiltInQualityProfilesDefinition} provided by installed plugins.
  */
 @Immutable
 public final class BuiltInQProfile {
   private final QProfileName qProfileName;
   private final boolean isDefault;
-  private final List<org.sonar.api.rules.ActiveRule> activeRules;
+  private final List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> activeRules;
 
   private BuiltInQProfile(Builder builder) {
     this.qProfileName = new QProfileName(builder.language, builder.name);
@@ -56,7 +56,7 @@ public final class BuiltInQProfile {
     return isDefault;
   }
 
-  public List<org.sonar.api.rules.ActiveRule> getActiveRules() {
+  public List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> getActiveRules() {
     return activeRules;
   }
 
@@ -65,7 +65,7 @@ public final class BuiltInQProfile {
     private String name;
     private boolean declaredDefault;
     private boolean computedDefault;
-    private final List<org.sonar.api.rules.ActiveRule> activeRules = new ArrayList<>();
+    private final List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> activeRules = new ArrayList<>();
 
     public Builder setLanguage(String language) {
       this.language = language;
@@ -95,7 +95,7 @@ public final class BuiltInQProfile {
       return this;
     }
 
-    Builder addRules(List<org.sonar.api.rules.ActiveRule> rules) {
+    Builder addRules(List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> rules) {
       this.activeRules.addAll(rules);
       return this;
     }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java
new file mode 100644 (file)
index 0000000..7adb0d8
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.qualityprofile;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.sonar.api.profiles.ProfileDefinition;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.rules.ActiveRuleParam;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.utils.ValidationMessages;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+
+import static java.lang.String.format;
+
+/**
+ * Bridge between deprecated {@link ProfileDefinition} API and new {@link BuiltInQualityProfilesDefinition}
+ */
+public class BuiltInQProfileDefinitionsBridge implements BuiltInQualityProfilesDefinition {
+  private static final Logger LOGGER = Loggers.get(BuiltInQProfileDefinitionsBridge.class);
+
+  private final List<ProfileDefinition> definitions;
+
+  /**
+   * Requires for pico container when no {@link ProfileDefinition} is defined at all
+   */
+  public BuiltInQProfileDefinitionsBridge() {
+    this(new ProfileDefinition[0]);
+  }
+
+  public BuiltInQProfileDefinitionsBridge(ProfileDefinition... definitions) {
+    this.definitions = ImmutableList.copyOf(definitions);
+  }
+
+  @Override
+  public void define(Context context) {
+    Profiler profiler = Profiler.create(Loggers.get(getClass()));
+    for (ProfileDefinition definition : definitions) {
+      profiler.start();
+      ValidationMessages validation = ValidationMessages.create();
+      RulesProfile profile = definition.createProfile(validation);
+      validation.log(LOGGER);
+      if (profile == null) {
+        profiler.stopDebug(format("Loaded definition %s that return no profile", definition));
+      } else {
+        if (!validation.hasErrors()) {
+          define(context, profile);
+        }
+        profiler.stopDebug(format("Loaded deprecated profile definition %s for language %s", profile.getName(), profile.getLanguage()));
+      }
+    }
+  }
+
+  private static void define(Context context, RulesProfile profile) {
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage())
+      .setDefault(profile.getDefaultProfile().booleanValue());
+
+    for (org.sonar.api.rules.ActiveRule ar : profile.getActiveRules()) {
+      NewBuiltInActiveRule newActiveRule = newQp.activateRule(ar.getRepositoryKey(), ar.getRuleKey());
+      RulePriority overriddenSeverity = ar.getOverriddenSeverity();
+      if (overriddenSeverity != null) {
+        newActiveRule.overrideSeverity(overriddenSeverity.name());
+      }
+      for (ActiveRuleParam param : ar.getActiveRuleParams()) {
+        newActiveRule.overrideParam(param.getKey(), param.getValue());
+      }
+    }
+    newQp.done();
+  }
+
+}
index 9698e5f5fde48a87aad9e07416c0596c4141f5db..cb07b2f256294c0409bc67470acc51378a246a70 100644 (file)
@@ -36,7 +36,7 @@ import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.ActiveRuleParam;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
 import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.UuidFactory;
@@ -139,8 +139,8 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     return dto;
   }
 
-  private ActiveRuleChange insertActiveRule(DbSession dbSession, RulesProfileDto rulesProfileDto, org.sonar.api.rules.ActiveRule activeRule, long now) {
-    RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey());
+  private ActiveRuleChange insertActiveRule(DbSession dbSession, RulesProfileDto rulesProfileDto, BuiltInQualityProfilesDefinition.BuiltInActiveRule activeRule, long now) {
+    RuleKey ruleKey = RuleKey.of(activeRule.repoKey(), activeRule.ruleKey());
     RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey)
       .orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey));
 
@@ -148,7 +148,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     dto.setProfileId(rulesProfileDto.getId());
     dto.setRuleId(ruleDefinitionDto.getId());
     dto.setKey(ActiveRuleKey.of(rulesProfileDto, ruleDefinitionDto.getKey()));
-    dto.setSeverity(firstNonNull(activeRule.getSeverity().name(), ruleDefinitionDto.getSeverityString()));
+    dto.setSeverity(firstNonNull(activeRule.overriddenSeverity(), ruleDefinitionDto.getSeverityString()));
     dto.setUpdatedAt(now);
     dto.setCreatedAt(now);
     dbClient.activeRuleDao().insert(dbSession, dto);
@@ -161,10 +161,11 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     return change;
   }
 
-  private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, org.sonar.api.rules.ActiveRule activeRule, RuleKey ruleKey, ActiveRuleDto activeRuleDto) {
-    Map<String, String> valuesByParamKey = activeRule.getActiveRuleParams()
+  private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, BuiltInQualityProfilesDefinition.BuiltInActiveRule activeRule, RuleKey ruleKey,
+    ActiveRuleDto activeRuleDto) {
+    Map<String, String> valuesByParamKey = activeRule.overriddenParams()
       .stream()
-      .collect(MoreCollectors.uniqueIndex(ActiveRuleParam::getParamKey, ActiveRuleParam::getValue));
+      .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
     return ruleRepository.getRuleParams(ruleKey)
       .stream()
       .map(param -> {
index 321c69fb9a8129494a758fa39f8cd863320d8fec..c41bfa866a6a1ac6bdafe2967356f064889adeab 100644 (file)
  */
 package org.sonar.server.qualityprofile;
 
-import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Multimaps;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import javax.annotation.Nullable;
-import org.sonar.api.profiles.ProfileDefinition;
 import org.sonar.api.profiles.RulesProfile;
 import org.sonar.api.resources.Languages;
-import org.sonar.api.utils.ValidationMessages;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
 import org.sonar.core.util.stream.MoreCollectors;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.apache.commons.lang.StringUtils.lowerCase;
 
 public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository {
   private static final Logger LOGGER = Loggers.get(BuiltInQProfileRepositoryImpl.class);
   private static final String DEFAULT_PROFILE_NAME = "Sonar way";
 
   private final Languages languages;
-  private final List<ProfileDefinition> definitions;
+  private final List<BuiltInQualityProfilesDefinition> definitions;
   private List<BuiltInQProfile> qProfiles;
 
   /**
-   * Requires for pico container when no {@link ProfileDefinition} is defined at all
+   * Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all
    */
   public BuiltInQProfileRepositoryImpl(Languages languages) {
-    this(languages, new ProfileDefinition[0]);
+    this(languages, new BuiltInQualityProfilesDefinition[0]);
   }
 
-  public BuiltInQProfileRepositoryImpl(Languages languages, ProfileDefinition... definitions) {
+  public BuiltInQProfileRepositoryImpl(Languages languages, BuiltInQualityProfilesDefinition... definitions) {
     this.languages = languages;
     this.definitions = ImmutableList.copyOf(definitions);
   }
@@ -70,9 +62,12 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
   public void initialize() {
     checkState(qProfiles == null, "initialize must be called only once");
 
-    Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Load quality profiles");
-    ListMultimap<String, RulesProfile> rulesProfilesByLanguage = buildRulesProfilesByLanguage();
-    validateAndClean(rulesProfilesByLanguage);
+    Profiler profiler = Profiler.create(LOGGER).startInfo("Load quality profiles");
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    for (BuiltInQualityProfilesDefinition definition : definitions) {
+      definition.define(context);
+    }
+    Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage = validateAndClean(context);
     this.qProfiles = toFlatList(rulesProfilesByLanguage);
     profiler.stopDebug();
   }
@@ -84,49 +79,27 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
     return qProfiles;
   }
 
-  /**
-   * @return profiles by language
-   */
-  private ListMultimap<String, RulesProfile> buildRulesProfilesByLanguage() {
-    ListMultimap<String, RulesProfile> byLang = ArrayListMultimap.create();
-    Profiler profiler = Profiler.create(Loggers.get(getClass()));
-    for (ProfileDefinition definition : definitions) {
-      profiler.start();
-      ValidationMessages validation = ValidationMessages.create();
-      RulesProfile profile = definition.createProfile(validation);
-      validation.log(LOGGER);
-      if (profile == null) {
-        profiler.stopDebug(format("Loaded definition %s that return no profile", definition));
-      } else {
-        if (!validation.hasErrors()) {
-          checkArgument(isNotEmpty(profile.getName()), "Profile created by Definition %s can't have a blank name", definition);
-          byLang.put(lowerCase(profile.getLanguage(), Locale.ENGLISH), profile);
-        }
-        profiler.stopDebug(format("Loaded definition %s for language %s", profile.getName(), profile.getLanguage()));
-      }
-    }
-    return byLang;
-  }
-
-  private void validateAndClean(ListMultimap<String, RulesProfile> byLang) {
-    byLang.asMap().entrySet()
+  private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) {
+    Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = context.profilesByLanguageAndName();
+    profilesByLanguageAndName.entrySet()
       .removeIf(entry -> {
         String language = entry.getKey();
         if (languages.get(language) == null) {
           LOGGER.info("Language {} is not installed, related Quality profiles are ignored", language);
           return true;
         }
-        Collection<RulesProfile> profiles = entry.getValue();
+        Collection<BuiltInQualityProfile> profiles = entry.getValue().values();
         if (profiles.isEmpty()) {
           LOGGER.warn("No Quality profiles defined for language: {}", language);
           return true;
         }
         return false;
       });
+    return profilesByLanguageAndName;
   }
 
-  private static List<BuiltInQProfile> toFlatList(ListMultimap<String, RulesProfile> rulesProfilesByLanguage) {
-    Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = Multimaps.asMap(rulesProfilesByLanguage)
+  private static List<BuiltInQProfile> toFlatList(Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage) {
+    Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = rulesProfilesByLanguage
       .entrySet()
       .stream()
       .collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, BuiltInQProfileRepositoryImpl::toQualityProfileBuilders));
@@ -151,14 +124,14 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
    *       RulesProfile with a given name</li>
    * </ul>
    */
-  private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, List<RulesProfile>> rulesProfilesByLanguage) {
-    String language = rulesProfilesByLanguage.getKey();
+  private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguageAndName) {
+    String language = rulesProfilesByLanguageAndName.getKey();
     // use a LinkedHashMap to keep order of insertion of RulesProfiles
     Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
-    for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) {
+    for (BuiltInQualityProfile builtInProfile : rulesProfilesByLanguageAndName.getValue().values()) {
       qualityProfileBuildersByName.compute(
-        rulesProfile.getName(),
-        (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile));
+        builtInProfile.name(),
+        (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, builtInProfile));
     }
     return ImmutableList.copyOf(qualityProfileBuildersByName.values());
   }
@@ -175,20 +148,16 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
     return true;
   }
 
-  private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, RulesProfile rulesProfile) {
+  private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, BuiltInQualityProfile builtInProfile) {
     BuiltInQProfile.Builder builder = existingBuilder;
     if (builder == null) {
       builder = new BuiltInQProfile.Builder()
         .setLanguage(language)
-        .setName(rulesProfile.getName());
+        .setName(builtInProfile.name());
     }
-    Boolean defaultProfile = rulesProfile.getDefaultProfile();
-    boolean declaredDefault = defaultProfile != null && defaultProfile;
     return builder
-      // if there is multiple RulesProfiles with the same name, if at least one is declared default,
-      // then QualityProfile is flagged as declared default
-      .setDeclaredDefault(builder.isDeclaredDefault() || declaredDefault)
-      .addRules(rulesProfile.getActiveRules());
+      .setDeclaredDefault(builtInProfile.isDefault())
+      .addRules(builtInProfile.rules());
   }
 
   private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) {
index 1a1d0bd507be59c1c783002d34c80e6d4ec5d0aa..8f1331f91d7c40e8b7aa8f6f7e61ff183365e561 100644 (file)
@@ -24,7 +24,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.ActiveRuleParam;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -59,18 +60,16 @@ public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate {
     });
 
     // these rules are not part of the built-in profile anymore
-    toBeDeactivated.forEach(ruleKey ->
-      changes.addAll(ruleActivator.deactivateOnBuiltInRulesProfile(dbSession, ruleProfile, ruleKey, false)));
+    toBeDeactivated.forEach(ruleKey -> changes.addAll(ruleActivator.deactivateOnBuiltInRulesProfile(dbSession, ruleProfile, ruleKey, false)));
 
     activeRuleIndexer.commitAndIndex(dbSession, changes);
     return changes;
   }
 
-  private static RuleActivation convert(org.sonar.api.rules.ActiveRule ar) {
-    String severity = ar.getSeverity() != null ? ar.getSeverity().name() : null;
-    Map<String, String> params = ar.getActiveRuleParams().stream()
-      .collect(MoreCollectors.uniqueIndex(ActiveRuleParam::getKey, ActiveRuleParam::getValue));
-    return RuleActivation.create(ar.getRule().ruleKey(), severity, params);
+  private static RuleActivation convert(BuiltInActiveRule ar) {
+    Map<String, String> params = ar.overriddenParams().stream()
+      .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
+    return RuleActivation.create(RuleKey.of(ar.repoKey(), ar.ruleKey()), ar.overriddenSeverity(), params);
   }
 
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java
new file mode 100644 (file)
index 0000000..47d3e31
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.qualityprofile;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.sonar.api.profiles.ProfileDefinition;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleParam;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
+import org.sonar.api.utils.ValidationMessages;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+
+public class BuiltInQProfileDefinitionsBridgeTest {
+
+  @Test
+  public void noProfileDefinitions() {
+    BuiltInQProfileDefinitionsBridge bridge = new BuiltInQProfileDefinitionsBridge();
+
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    bridge.define(context);
+
+    assertThat(context.profilesByLanguageAndName()).isEmpty();
+  }
+
+  @Test
+  public void bridgeProfileDefinitions() {
+    BuiltInQProfileDefinitionsBridge bridge = new BuiltInQProfileDefinitionsBridge(new Profile1(), new NullProfile(), new ProfileWithError());
+
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    bridge.define(context);
+
+    assertThat(context.profilesByLanguageAndName()).hasSize(1);
+    assertThat(context.profilesByLanguageAndName().get("xoo")).hasSize(1);
+
+    BuiltInQualityProfile profile1 = context.profile("xoo", "Profile 1");
+    assertThat(profile1).isNotNull();
+    assertThat(profile1.rules()).hasSize(3);
+    BuiltInActiveRule defaultSeverity = profile1.rule(RuleKey.of("repo1", "defaultSeverity"));
+    assertThat(defaultSeverity).isNotNull();
+    assertThat(defaultSeverity.overriddenSeverity()).isNull();
+    assertThat(defaultSeverity.overriddenParams()).isEmpty();
+
+    assertThat(profile1.rule(RuleKey.of("repo1", "overrideSeverity")).overriddenSeverity()).isEqualTo(Severity.CRITICAL);
+
+    assertThat(profile1.rule(RuleKey.of("repo1", "overrideParam")).overriddenParams())
+      .extracting(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue).containsOnly(tuple("param", "value"));
+  }
+
+  private class Profile1 extends ProfileDefinition {
+    @Override
+    public RulesProfile createProfile(ValidationMessages validation) {
+      RulesProfile profile1 = RulesProfile.create("Profile 1", "xoo");
+
+      profile1.activateRule(Rule.create("repo1", "defaultSeverity"), null);
+      profile1.activateRule(Rule.create("repo1", "overrideSeverity"), RulePriority.CRITICAL);
+      Rule ruleWithParam = Rule.create("repo1", "overrideParam");
+      ruleWithParam.setParams(Arrays.asList(new RuleParam(ruleWithParam, "param", "", "")));
+      ActiveRule arWithParam = profile1.activateRule(ruleWithParam, null);
+      arWithParam.setParameter("param", "value");
+
+      return profile1;
+    }
+  }
+
+  private class NullProfile extends ProfileDefinition {
+    @Override
+    public RulesProfile createProfile(ValidationMessages validation) {
+      return null;
+    }
+  }
+
+  private class ProfileWithError extends ProfileDefinition {
+    @Override
+    public RulesProfile createProfile(ValidationMessages validation) {
+      validation.addErrorText("Foo");
+      return RulesProfile.create("Profile with errors", "xoo");
+    }
+  }
+}
index 3d85ade5b77ee59501dc735f972a6deabcb6f1ca..9a66ceefe03340aa2e0707d82146b58d28362c50 100644 (file)
@@ -27,6 +27,8 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.profiles.RulesProfile;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RulePriority;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
 import org.sonar.core.util.SequenceUuidFactory;
@@ -48,8 +50,6 @@ import org.sonar.server.util.TypeValidations;
 import static java.util.Collections.emptyList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
-import static org.sonar.api.rules.RulePriority.CRITICAL;
-import static org.sonar.api.rules.RulePriority.MAJOR;
 
 public class BuiltInQProfileInsertImplTest {
 
@@ -102,11 +102,15 @@ public class BuiltInQProfileInsertImplTest {
     OrganizationDto org = db.organizations().insert();
     RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
     RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
-    RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
-    activeRule(apiProfile, rule1, CRITICAL);
-    activeRule(apiProfile, rule2, MAJOR);
 
-    BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo");
+
+    newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+    newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+    newQp.done();
+
+    BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
     call(builtIn);
 
     verifyTableSize("rules_profiles", 1);
@@ -122,9 +126,11 @@ public class BuiltInQProfileInsertImplTest {
   @Test
   public void flag_profile_as_default_on_organization_if_declared_as_default_by_api() {
     OrganizationDto org = db.organizations().insert();
-    RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
-    apiProfile.setDefaultProfile(true);
-    BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
+    newQp.done();
+
+    BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
 
     call(builtIn);
 
@@ -135,26 +141,28 @@ public class BuiltInQProfileInsertImplTest {
 
   @Test
   public void existing_default_profile_in_organization_must_not_be_changed() {
-    RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
-    apiProfile.setDefaultProfile(true);
-    BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
+    newQp.done();
+    BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
 
     OrganizationDto org = db.organizations().insert();
-    QProfileDto currentDefault = db.qualityProfiles().insert(org, p -> p.setLanguage(apiProfile.getLanguage()));
+    QProfileDto currentDefault = db.qualityProfiles().insert(org, p -> p.setLanguage("xoo"));
     db.qualityProfiles().setAsDefault(currentDefault);
 
     call(builtIn);
 
-    QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, org, apiProfile.getLanguage());
+    QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, org, "xoo");
     assertThat(defaultProfile.getKee()).isEqualTo(currentDefault.getKee());
   }
 
   @Test
   public void dont_flag_profile_as_default_on_organization_if_not_declared_as_default_by_api() {
     OrganizationDto org = db.organizations().insert();
-    RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
-    apiProfile.setDefaultProfile(false);
-    BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(false);
+    newQp.done();
+    BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
 
     call(builtIn);
 
index b541eabe956a15a1e7de00b932d86bc5a433b431..4c838744595f54e41f68b4e3bb9a24c40c533b90 100644 (file)
@@ -22,15 +22,12 @@ package org.sonar.server.qualityprofile;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Random;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.sonar.api.profiles.ProfileDefinition;
-import org.sonar.api.profiles.RulesProfile;
 import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
-import org.sonar.api.utils.ValidationMessages;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
 import org.sonar.server.language.LanguageTesting;
 
 import static java.util.Arrays.asList;
@@ -83,28 +80,6 @@ public class BuiltInQProfileRepositoryImplTest {
     assertThat(underTest.get()).isEmpty();
   }
 
-  @Test
-  public void initialize_throws_IAE_if_profileDefinition_creates_RulesProfile_with_null_name() {
-    DummyProfileDefinition definition = new DummyProfileDefinition("foo", null, false);
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(), definition);
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Profile created by Definition " + definition + " can't have a blank name");
-
-    underTest.initialize();
-  }
-
-  @Test
-  public void initialize_throws_IAE_if_profileDefinition_creates_RulesProfile_with_empty_name() {
-    DummyProfileDefinition definition = new DummyProfileDefinition("foo", "", false);
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(), definition);
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Profile created by Definition " + definition + " can't have a blank name");
-
-    underTest.initialize();
-  }
-
   @Test
   public void initialize_makes_single_profile_of_a_language_default_even_if_not_flagged_as_so() {
     BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", false));
@@ -134,7 +109,7 @@ public class BuiltInQProfileRepositoryImplTest {
     Collections.shuffle(definitions);
     String firstName = definitions.get(0).getName();
     String secondName = definitions.get(1).getName();
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), definitions.toArray(new ProfileDefinition[0]));
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), definitions.toArray(new BuiltInQualityProfilesDefinition[0]));
 
     underTest.initialize();
 
@@ -154,32 +129,6 @@ public class BuiltInQProfileRepositoryImplTest {
     underTest.initialize();
   }
 
-  @Test
-  public void initialize_creates_profile_as_default_even_if_only_one_profile_with_given_name_has_default_flag_true() {
-    String name = "doh";
-    boolean flag = new Random().nextBoolean();
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE),
-      new DummyProfileDefinition("foo", name, flag), new DummyProfileDefinition("foo", name, !flag));
-
-    underTest.initialize();
-
-    assertThat(underTest.get())
-      .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault)
-      .containsExactly(tuple("foo", true));
-  }
-
-  @Test
-  public void initialize_creates_single_profile_if_several_profile_have_the_same_name_for_a_given_language() {
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE),
-      new DummyProfileDefinition("foo", "aName", true), new DummyProfileDefinition("foo", "aName", true));
-
-    underTest.initialize();
-
-    assertThat(underTest.get())
-      .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::getName)
-      .containsExactlyInAnyOrder(tuple(FOO_LANGUAGE.getKey(), "aName"));
-  }
-
   @Test
   public void initialize_creates_profile_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() {
     BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
@@ -227,7 +176,7 @@ public class BuiltInQProfileRepositoryImplTest {
       .containsExactly("goo");
   }
 
-  private static final class DummyProfileDefinition extends ProfileDefinition {
+  private static final class DummyProfileDefinition implements BuiltInQualityProfilesDefinition {
     private final String language;
     private final String name;
     private final boolean defaultProfile;
@@ -239,23 +188,15 @@ public class BuiltInQProfileRepositoryImplTest {
     }
 
     @Override
-    public RulesProfile createProfile(ValidationMessages validation) {
-      RulesProfile res = RulesProfile.create(name, language);
-      res.setDefaultProfile(defaultProfile);
-      return res;
-    }
-
-    String getLanguage() {
-      return language;
+    public void define(Context context) {
+      context.createBuiltInQualityProfile(name, language)
+        .setDefault(defaultProfile).done();
     }
 
     String getName() {
       return name;
     }
 
-    boolean isDefaultProfile() {
-      return defaultProfile;
-    }
   }
 
 }
index 8c93d260da9691783ccf00829ded85cc7b9b1137..bcc6073526e74f41e0fcaa205b76946df3a503a7 100644 (file)
@@ -24,8 +24,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.rules.ExternalResource;
-import org.sonar.api.profiles.RulesProfile;
 import org.sonar.api.resources.Language;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
 
 import static com.google.common.base.Preconditions.checkState;
 
@@ -60,13 +60,13 @@ public class BuiltInQProfileRepositoryRule extends ExternalResource implements B
     return add(language, profileName, false);
   }
 
-  public BuiltInQProfile add(Language language, String profileName, boolean isDefault, org.sonar.api.rules.ActiveRule... rules) {
+  public BuiltInQProfile add(Language language, String profileName, boolean isDefault, BuiltInQualityProfilesDefinition.BuiltInActiveRule... rules) {
     BuiltInQProfile builtIn = create(language, profileName, isDefault, rules);
     profiles.add(builtIn);
     return builtIn;
   }
 
-  public BuiltInQProfile create(Language language, String profileName, boolean isDefault, org.sonar.api.rules.ActiveRule... rules) {
+  public BuiltInQProfile create(Language language, String profileName, boolean isDefault, BuiltInQualityProfilesDefinition.BuiltInActiveRule... rules) {
     return new BuiltInQProfile.Builder()
       .setLanguage(language.getKey())
       .setName(profileName)
@@ -75,12 +75,12 @@ public class BuiltInQProfileRepositoryRule extends ExternalResource implements B
       .build();
   }
 
-  public BuiltInQProfile create(RulesProfile api) {
+  public BuiltInQProfile create(BuiltInQualityProfilesDefinition.BuiltInQualityProfile api) {
     return new BuiltInQProfile.Builder()
-      .setLanguage(api.getLanguage())
-      .setName(api.getName())
-      .setDeclaredDefault(api.getDefaultProfile())
-      .addRules(new ArrayList<>(api.getActiveRules()))
+      .setLanguage(api.language())
+      .setName(api.name())
+      .setDeclaredDefault(api.isDefault())
+      .addRules(new ArrayList<>(api.rules()))
       .build();
   }
 }
index 6a0642b003bed27f72c5c6dfd103327b252f976c..12cdf2fb380a8c2f79e9b90c34e6978fed6f00e5 100644 (file)
@@ -25,7 +25,10 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RulePriority;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.TestSystem2;
 import org.sonar.db.DbTester;
@@ -81,10 +84,12 @@ public class BuiltInQProfileUpdateImplTest {
   public void activate_new_rules() {
     RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
     RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
-    RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
-    activateRuleInDef(apiProfile, rule1, CRITICAL);
-    activateRuleInDef(apiProfile, rule2, MAJOR);
-    BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+    newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+    newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+    newQp.done();
+    BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));
 
     underTest.update(db.getSession(), builtIn, persistedProfile);
 
@@ -98,9 +103,11 @@ public class BuiltInQProfileUpdateImplTest {
   @Test
   public void already_activated_rule_is_updated_in_case_of_differences() {
     RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
-    RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
-    activateRuleInDef(apiProfile, rule, CRITICAL);
-    BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+    newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+    newQp.done();
+    BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));
 
     activateRuleInDb(persistedProfile, rule, BLOCKER);
 
@@ -115,9 +122,11 @@ public class BuiltInQProfileUpdateImplTest {
   @Test
   public void already_activated_rule_is_not_touched_if_no_differences() {
     RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
-    RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
-    activateRuleInDef(apiProfile, rule, CRITICAL);
-    BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+    newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+    newQp.done();
+    BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));
 
     activateRuleInDb(persistedProfile, rule, CRITICAL);
 
@@ -133,9 +142,11 @@ public class BuiltInQProfileUpdateImplTest {
   public void deactivate_rule_that_is_not_in_built_in_definition_anymore() {
     RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
     RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
-    RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
-    activateRuleInDef(apiProfile, rule2, CRITICAL);
-    BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+    newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+    newQp.done();
+    BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));
 
     // built-in definition contains only rule2
     // so rule1 must be deactivated
@@ -154,10 +165,13 @@ public class BuiltInQProfileUpdateImplTest {
     RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
     RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
     RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("xoo"));
-    RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
-    activateRuleInDef(apiProfile, rule1, CRITICAL);
-    activateRuleInDef(apiProfile, rule2, MAJOR);
-    BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
+
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+    newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+    newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+    newQp.done();
+    BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));
 
     // rule1 must be updated (blocker to critical)
     // rule2 must be activated
@@ -175,7 +189,6 @@ public class BuiltInQProfileUpdateImplTest {
     assertThatProfileIsMarkedAsUpdated(persistedProfile);
   }
 
-
   private static void assertThatRuleIsNewlyActivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
     ActiveRuleDto activeRule = findRule(activeRules, rule).get();
 
index 6fbba727fc6521cf8398d04948adc8aa1a8bc022..eac458ab2150949f951477f0a6b22a45818ea406 100644 (file)
@@ -27,10 +27,11 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.mockito.ArgumentCaptor;
-import org.sonar.api.profiles.RulesProfile;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.ActiveRule;
 import org.sonar.api.rules.RulePriority;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.core.util.UuidFactoryFast;
@@ -57,7 +58,6 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
-import static org.sonar.api.rules.Rule.create;
 import static org.sonar.api.rules.RulePriority.MAJOR;
 import static org.sonar.db.qualityprofile.QualityProfileTesting.newRuleProfileDto;
 import static org.sonar.server.language.LanguageTesting.newLanguage;
@@ -296,15 +296,23 @@ public class RegisterQualityProfilesNotificationTest {
   }
 
   private void addPluginProfile(RulesProfileDto dbProfile, RuleDefinitionDto... dbRules) {
-    RulesProfile pluginProfile = RulesProfile.create(dbProfile.getName(), dbProfile.getLanguage());
-    Arrays.stream(dbRules).forEach(dbRule -> pluginProfile.activateRule(create(dbRule.getRepositoryKey(), dbRule.getRuleKey()), null));
-    builtInQProfileRepositoryRule.add(newLanguage(dbProfile.getLanguage()), dbProfile.getName(), false, pluginProfile.getActiveRules().toArray(new ActiveRule[0]));
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(dbProfile.getName(), dbProfile.getLanguage());
+
+    Arrays.stream(dbRules).forEach(dbRule -> newQp.activateRule(dbRule.getRepositoryKey(), dbRule.getRuleKey()).overrideSeverity(Severity.MAJOR));
+    newQp.done();
+    builtInQProfileRepositoryRule.add(newLanguage(dbProfile.getLanguage()), dbProfile.getName(), false,
+      context.profile(dbProfile.getLanguage(), dbProfile.getName()).rules().toArray(new BuiltInActiveRule[0]));
   }
 
   private void addPluginProfile(QProfileDto profile, RuleDefinitionDto... dbRules) {
-    RulesProfile pluginProfile = RulesProfile.create(profile.getName(), profile.getLanguage());
-    Arrays.stream(dbRules).forEach(dbRule -> pluginProfile.activateRule(create(dbRule.getRepositoryKey(), dbRule.getRuleKey()), null));
-    builtInQProfileRepositoryRule.add(newLanguage(profile.getLanguage()), profile.getName(), false, pluginProfile.getActiveRules().toArray(new ActiveRule[0]));
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
+
+    Arrays.stream(dbRules).forEach(dbRule -> newQp.activateRule(dbRule.getRepositoryKey(), dbRule.getRuleKey()).overrideSeverity(Severity.MAJOR));
+    newQp.done();
+    builtInQProfileRepositoryRule.add(newLanguage(profile.getLanguage()), profile.getName(), false,
+      context.profile(profile.getLanguage(), profile.getName()).rules().toArray(new BuiltInActiveRule[0]));
   }
 
   private RulesProfileDto insertBuiltInProfile(String language) {
index e029e1f180a366be0b0dbe76ebdbe48c125a9f91..8e94e17a2f2497f95106dfb67fc7dbacc6a81b03 100644 (file)
@@ -21,20 +21,23 @@ package org.sonar.api.profiles;
 
 import java.util.Collection;
 import org.apache.commons.lang.StringUtils;
+import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleAnnotationUtils;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RulePriority;
-import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.profile.BuiltInQualityProfileAnnotationLoader;
 import org.sonar.api.utils.ValidationMessages;
 import org.sonar.check.BelongsToProfile;
 
 /**
  * @since 2.3
+ * @deprecated since 6.6 use {@link BuiltInQualityProfileAnnotationLoader}
  */
 @ServerSide
 @ComputeEngineSide
+@Deprecated
 public final class AnnotationProfileParser {
 
   private final RuleFinder ruleFinder;
index 88957329396d0b6636dbd05c504d17bb3c846ace..2a00e03c392621768054ccfe10cd4b7beef633a0 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.api.profiles;
 
 import org.sonar.api.ExtensionPoint;
 import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
 import org.sonar.api.utils.ValidationMessages;
 
 /**
@@ -28,9 +29,11 @@ import org.sonar.api.utils.ValidationMessages;
  * The components <code>AnnotationProfileParser</code> and <code>XMLProfileParser</code> can be used to help implementing the method create().
  *
  * @since 2.3
+ * @deprecated since 6.6 use {@link BuiltInQualityProfilesDefinition}
  */
 @ServerSide
 @ExtensionPoint
+@Deprecated
 public abstract class ProfileDefinition {
 
   public abstract RulesProfile createProfile(ValidationMessages validation);
index 6811f3f46835fe41e4e68220be7c8ecb146c4e26..6a5d3c35cf4dfecbe086fb3ff9b926181a85e35f 100644 (file)
@@ -308,10 +308,7 @@ public class RulesProfile implements Cloneable {
         "The definition of the profile '%s' (language '%s') contains multiple occurrences of the '%s:%s' rule. The plugin which declares this profile should fix this.",
         getName(), getLanguage(), rule.getRepositoryKey(), rule.getKey()));
     }
-    ActiveRule activeRule = new ActiveRule();
-    activeRule.setRule(rule);
-    activeRule.setRulesProfile(this);
-    activeRule.setSeverity(optionalSeverity == null ? rule.getSeverity() : optionalSeverity);
+    ActiveRule activeRule = new ActiveRule(this, rule, optionalSeverity);
     activeRules.add(activeRule);
     return activeRule;
   }
index deefa7663fa87f25532028caee4306ed6012ac26..ffd1ca68c7de463eecae2e88cca909c2417fd1a8 100644 (file)
@@ -31,11 +31,11 @@ import org.apache.commons.lang.StringUtils;
 import org.codehaus.staxmate.SMInputFactory;
 import org.codehaus.staxmate.in.SMHierarchicCursor;
 import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.rules.ActiveRule;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RulePriority;
-import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.ValidationMessages;
 
index d4d73b831f0cb90307cc657ccbbfad40a31e25a6..5e3469af78f140c5623aa4432f3419997dff9c74 100644 (file)
@@ -36,6 +36,7 @@ public class ActiveRule implements Cloneable {
   private Integer id;
   private Rule rule;
   private RulePriority severity;
+  private RulePriority overriddenSeverity;
   private RulesProfile rulesProfile;
   private List<ActiveRuleParam> activeRuleParams = new ArrayList<>();
   private String inheritance;
@@ -53,6 +54,7 @@ public class ActiveRule implements Cloneable {
   @Deprecated
   public ActiveRule(RulesProfile profile, Rule rule, RulePriority severity) {
     this.rule = rule;
+    this.overriddenSeverity = severity;
     if (severity == null && rule != null) {
       this.severity = rule.getSeverity();
     } else {
@@ -119,6 +121,16 @@ public class ActiveRule implements Cloneable {
     return severity;
   }
 
+  /**
+   * For internal use
+   * @since 6.6
+   * @deprecated
+   */
+  @Deprecated
+  public RulePriority getOverriddenSeverity() {
+    return overriddenSeverity;
+  }
+
   /**
    * @since 2.5
    */
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoader.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoader.java
new file mode 100644 (file)
index 0000000..869ab39
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.server.profile;
+
+import javax.annotation.CheckForNull;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.rules.RuleAnnotationUtils;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInActiveRule;
+import org.sonar.check.BelongsToProfile;
+
+/**
+ * Read definitions of quality profiles based on the annotation {@link BelongsToProfile} provided by sonar-check-api. It is used
+ * to feed {@link BuiltInQualityProfilesDefinition}.
+ *
+ * @see BuiltInQualityProfilesDefinition
+ * @since 6.6
+ */
+@ServerSide
+public class BuiltInQualityProfileAnnotationLoader {
+
+  public void load(BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile builtInProfile, String repositoryKey, Class... annotatedClasses) {
+    for (Class<?> annotatedClass : annotatedClasses) {
+      loadActiveRule(builtInProfile, repositoryKey, annotatedClass);
+    }
+  }
+
+  @CheckForNull
+  void loadActiveRule(BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile profile, String repositoryKey, Class<?> clazz) {
+    BelongsToProfile belongsToProfile = clazz.getAnnotation(BelongsToProfile.class);
+    if ((belongsToProfile != null) && StringUtils.equals(belongsToProfile.title(), profile.name())) {
+      String ruleKey = RuleAnnotationUtils.getRuleKey(clazz);
+      NewBuiltInActiveRule activeRule = profile.activateRule(repositoryKey, ruleKey);
+      activeRule.overrideSeverity(belongsToProfile.priority().name());
+    }
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinition.java
new file mode 100644 (file)
index 0000000..84b500d
--- /dev/null
@@ -0,0 +1,424 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.server.profile;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ExtensionPoint;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableList;
+import static java.util.Collections.unmodifiableMap;
+import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+
+/**
+ * Define built-in quality profiles which are automatically registered during SonarQube startup.
+ * We no more provide any facility to load profiles from XML file or annotated classes, but it should
+ * be straightforward to implement (adapt code of deprecated {@link org.sonar.api.profiles.AnnotationProfileParser} 
+ * or {@link org.sonar.api.profiles.XMLProfileParser} for example).
+ *
+ * @since 6.6
+ */
+@ServerSide
+@ExtensionPoint
+public interface BuiltInQualityProfilesDefinition {
+
+  /**
+   * Instantiated by core but not by plugins, except for their tests.
+   */
+  class Context {
+
+    private final Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = new HashMap<>();
+
+    /**
+     * New builder for {@link BuiltInQualityProfile}.
+     * <br>
+     * A plugin can activate rules in a built in quality profile that is defined by another plugin.
+     */
+    public NewBuiltInQualityProfile createBuiltInQualityProfile(String name, String language) {
+      return new NewBuiltInQualityProfileImpl(this, name, language);
+    }
+
+    private void registerProfile(NewBuiltInQualityProfileImpl newProfile) {
+      String language = newProfile.language();
+      String name = newProfile.name();
+      Preconditions.checkArgument(!profilesByLanguageAndName.computeIfAbsent(language, l -> new LinkedHashMap<>()).containsKey(name),
+        "There is already a quality profile with name '%s' for language '%s'", name, language);
+      profilesByLanguageAndName.get(language).put(name, new BuiltInQualityProfileImpl(newProfile));
+    }
+
+    public Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName() {
+      return profilesByLanguageAndName;
+    }
+
+    public BuiltInQualityProfile profile(String language, String name) {
+      return profilesByLanguageAndName.computeIfAbsent(language, l -> new LinkedHashMap<>()).get(name);
+    }
+  }
+
+  interface NewBuiltInQualityProfile {
+
+    /**
+     * Set whether this is the default profile for the language. The default profile is used when none is explicitly defined when analyzing a project.
+     */
+    NewBuiltInQualityProfile setDefault(boolean value);
+
+    /**
+     * Activate a rule with specified key.
+     *
+     * @throws IllegalArgumentException if rule is already activated in this profile.
+     */
+    NewBuiltInActiveRule activateRule(String repoKey, String ruleKey);
+
+    Collection<NewBuiltInActiveRule> activeRules();
+
+    String language();
+
+    String name();
+
+    boolean isDefault();
+
+    void done();
+  }
+
+  class NewBuiltInQualityProfileImpl implements NewBuiltInQualityProfile {
+    private final Context context;
+    private final String name;
+    private final String language;
+    private boolean isDefault;
+    private final Map<RuleKey, NewBuiltInActiveRule> newActiveRules = new HashMap<>();
+
+    private NewBuiltInQualityProfileImpl(Context context, String name, String language) {
+      this.context = context;
+      this.name = name;
+      this.language = language;
+    }
+
+    @Override
+    public NewBuiltInQualityProfile setDefault(boolean value) {
+      this.isDefault = value;
+      return this;
+    }
+
+    @Override
+    public NewBuiltInActiveRule activateRule(String repoKey, String ruleKey) {
+      RuleKey ruleKeyObj = RuleKey.of(repoKey, ruleKey);
+      checkArgument(!newActiveRules.containsKey(ruleKeyObj), "The rule '%s' is already activated", ruleKeyObj);
+      NewBuiltInActiveRule newActiveRule = new NewBuiltInActiveRule(repoKey, ruleKey);
+      newActiveRules.put(ruleKeyObj, newActiveRule);
+      return newActiveRule;
+    }
+
+    @Override
+    public String language() {
+      return language;
+    }
+
+    @Override
+    public String name() {
+      return name;
+    }
+
+    @Override
+    public boolean isDefault() {
+      return isDefault;
+    }
+
+    @Override
+    public Collection<NewBuiltInActiveRule> activeRules() {
+      return newActiveRules.values();
+    }
+
+    @Override
+    public void done() {
+      checkArgument(isNotBlank(name), "Built-In Quality Profile can't have a blank name");
+      checkArgument(isNotBlank(language), "Built-In Quality Profile can't have a blank language");
+
+      context.registerProfile(this);
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("NewBuiltInQualityProfile{");
+      sb.append("name='").append(name).append('\'');
+      sb.append(", language='").append(language).append('\'');
+      sb.append(", default='").append(isDefault).append('\'');
+      sb.append('}');
+      return sb.toString();
+    }
+  }
+
+  interface BuiltInQualityProfile {
+    String name();
+
+    String language();
+
+    boolean isDefault();
+
+    @CheckForNull
+    BuiltInActiveRule rule(RuleKey ruleKey);
+
+    List<BuiltInActiveRule> rules();
+  }
+
+  @Immutable
+  class BuiltInQualityProfileImpl implements BuiltInQualityProfile {
+
+    private static final Logger LOG = Loggers.get(BuiltInQualityProfilesDefinition.BuiltInQualityProfileImpl.class);
+    private final String language;
+    private final String name;
+    private final boolean isDefault;
+    private final Map<RuleKey, BuiltInActiveRule> activeRulesByKey;
+
+    private BuiltInQualityProfileImpl(NewBuiltInQualityProfileImpl newProfile) {
+      this.name = newProfile.name();
+      this.language = newProfile.language();
+      this.isDefault = newProfile.isDefault();
+
+      Map<RuleKey, BuiltInActiveRule> ruleBuilder = new HashMap<>();
+      for (NewBuiltInActiveRule newActiveRule : newProfile.activeRules()) {
+        ruleBuilder.put(RuleKey.of(newActiveRule.repoKey, newActiveRule.ruleKey), new BuiltInActiveRule(newActiveRule));
+      }
+      this.activeRulesByKey = unmodifiableMap(ruleBuilder);
+    }
+
+    @Override
+    public String language() {
+      return language;
+    }
+
+    @Override
+    public String name() {
+      return name;
+    }
+
+    @Override
+    public boolean isDefault() {
+      return isDefault;
+    }
+
+    @Override
+    @CheckForNull
+    public BuiltInActiveRule rule(RuleKey ruleKey) {
+      return activeRulesByKey.get(ruleKey);
+    }
+
+    @Override
+    public List<BuiltInActiveRule> rules() {
+      return unmodifiableList(new ArrayList<>(activeRulesByKey.values()));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      BuiltInQualityProfileImpl that = (BuiltInQualityProfileImpl) o;
+      return language.equals(that.language) && name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+      int result = name.hashCode();
+      result = 31 * result + language.hashCode();
+      return result;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("BuiltInQualityProfile{");
+      sb.append("name='").append(name).append('\'');
+      sb.append(", language='").append(language).append('\'');
+      sb.append(", default='").append(isDefault).append('\'');
+      sb.append('}');
+      return sb.toString();
+    }
+  }
+
+  class NewBuiltInActiveRule {
+    private final String repoKey;
+    private final String ruleKey;
+    private String overriddenSeverity = null;
+    private final Map<String, NewOverriddenParam> paramsByKey = new HashMap<>();
+
+    private NewBuiltInActiveRule(String repoKey, String ruleKey) {
+      this.repoKey = repoKey;
+      this.ruleKey = ruleKey;
+    }
+
+    public String repoKey() {
+      return this.repoKey;
+    }
+
+    public String ruleKey() {
+      return this.ruleKey;
+    }
+
+    /**
+     * Override default rule severity in this quality profile. By default the active rule will have the default rule severity.
+     * @param severity See {@link Severity} constants.
+     */
+    public NewBuiltInActiveRule overrideSeverity(String severity) {
+      checkArgument(Severity.ALL.contains(severity), "Severity of rule %s is not correct: %s", RuleKey.of(repoKey, ruleKey), severity);
+      this.overriddenSeverity = severity;
+      return this;
+    }
+
+    /**
+     * Create a parameter with given unique key. Max length of key is 128 characters.
+     */
+    public NewOverriddenParam overrideParam(String paramKey, @Nullable String value) {
+      checkArgument(!paramsByKey.containsKey(paramKey), "The parameter '%s' was already overridden on the built in active rule %s", paramKey, this);
+      NewOverriddenParam param = new NewOverriddenParam(paramKey).setOverriddenValue(value);
+      paramsByKey.put(paramKey, param);
+      return param;
+    }
+
+    @CheckForNull
+    public NewOverriddenParam getOverriddenParam(String paramKey) {
+      return paramsByKey.get(paramKey);
+    }
+
+    public Collection<NewOverriddenParam> getOverriddenParams() {
+      return paramsByKey.values();
+    }
+
+    @Override
+    public String toString() {
+      return format("[repository=%s, key=%s]", repoKey, ruleKey);
+    }
+  }
+
+  /**
+   * A rule activated on a built in quality profile.
+   */
+  @Immutable
+  class BuiltInActiveRule {
+    private final String repoKey;
+    private final String ruleKey;
+    private final String overriddenSeverity;
+    private final Map<String, OverriddenParam> overriddenParams;
+
+    private BuiltInActiveRule(NewBuiltInActiveRule newBuiltInActiveRule) {
+      this.repoKey = newBuiltInActiveRule.repoKey();
+      this.ruleKey = newBuiltInActiveRule.ruleKey();
+      this.overriddenSeverity = newBuiltInActiveRule.overriddenSeverity;
+      Map<String, OverriddenParam> paramsBuilder = new HashMap<>();
+      for (NewOverriddenParam newParam : newBuiltInActiveRule.getOverriddenParams()) {
+        paramsBuilder.put(newParam.key, new OverriddenParam(newParam));
+      }
+      this.overriddenParams = Collections.unmodifiableMap(paramsBuilder);
+    }
+
+    public String repoKey() {
+      return repoKey;
+    }
+
+    public String ruleKey() {
+      return ruleKey;
+    }
+
+    @CheckForNull
+    public String overriddenSeverity() {
+      return overriddenSeverity;
+    }
+
+    @CheckForNull
+    public OverriddenParam overriddenParam(String key) {
+      return overriddenParams.get(key);
+    }
+
+    public List<OverriddenParam> overriddenParams() {
+      return unmodifiableList(new ArrayList<>(overriddenParams.values()));
+    }
+
+    @Override
+    public String toString() {
+      return format("[repository=%s, key=%s]", repoKey, ruleKey);
+    }
+  }
+
+  class NewOverriddenParam {
+    private final String key;
+    private String overriddenValue;
+
+    private NewOverriddenParam(String key) {
+      this.key = key;
+    }
+
+    public String key() {
+      return key;
+    }
+
+    /**
+     * Empty default value will be converted to null. Max length is 4000 characters.
+     */
+    public NewOverriddenParam setOverriddenValue(@Nullable String s) {
+      this.overriddenValue = defaultIfEmpty(s, null);
+      return this;
+    }
+  }
+
+  @Immutable
+  class OverriddenParam {
+    private final String key;
+    private final String overriddenValue;
+
+    private OverriddenParam(NewOverriddenParam newOverriddenParam) {
+      this.key = newOverriddenParam.key();
+      this.overriddenValue = newOverriddenParam.overriddenValue;
+    }
+
+    public String key() {
+      return key;
+    }
+
+    @Nullable
+    public String overriddenValue() {
+      return overriddenValue;
+    }
+
+  }
+
+  /**
+   * This method is executed when server is started.
+   */
+  void define(Context context);
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/profile/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/profile/package-info.java
new file mode 100644 (file)
index 0000000..495f6f2
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.api.server.profile;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoaderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoaderTest.java
new file mode 100644 (file)
index 0000000..de544d5
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.server.profile;
+
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
+import org.sonar.check.BelongsToProfile;
+import org.sonar.check.Priority;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BuiltInQualityProfileAnnotationLoaderTest {
+
+  @Test
+  public void shouldParseAnnotatedClasses() {
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newProfile = context.createBuiltInQualityProfile("Foo way", "java");
+
+    new BuiltInQualityProfileAnnotationLoader().load(newProfile, "squid", FakeRule.class, RuleNoProfile.class);
+    newProfile.done();
+
+    assertThat(context.profile("java", "Foo way").rule(RuleKey.of("squid", "fake")).overriddenSeverity()).isEqualTo(Severity.BLOCKER);
+  }
+
+  @Test
+  public void shouldParseOnlyWantedProfile() {
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newProfile = context.createBuiltInQualityProfile("Foo way", "java");
+
+    new BuiltInQualityProfileAnnotationLoader().load(newProfile, "squid", FakeRule.class, RuleOnOtherProfile.class, RuleNoProfile.class);
+    newProfile.done();
+
+    assertThat(context.profile("java", "Foo way").rule(RuleKey.of("squid", "fake"))).isNotNull();
+    assertThat(context.profile("java", "Foo way").rule(RuleKey.of("squid", "other"))).isNull();
+  }
+}
+
+@BelongsToProfile(title = "Other profile", priority = Priority.BLOCKER)
+@org.sonar.check.Rule(key = "other", priority = Priority.CRITICAL)
+class RuleOnOtherProfile {
+}
+
+@org.sonar.check.Rule(key = "no", priority = Priority.CRITICAL)
+class RuleNoProfile {
+}
+
+@BelongsToProfile(title = "Foo way", priority = Priority.BLOCKER)
+@org.sonar.check.Rule(key = "fake", priority = Priority.CRITICAL)
+class FakeRule {
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinitionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinitionTest.java
new file mode 100644 (file)
index 0000000..53def9e
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.server.profile;
+
+import java.util.Map;
+import java.util.function.Consumer;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInActiveRule;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+
+public class BuiltInQualityProfilesDefinitionTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void coverage() {
+    assertThat(new BuiltInQualityProfilesDefinition.Context().profile("Foo", "xoo")).isNull();
+  }
+
+  @Test
+  public void createEmptyProfile() {
+    Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
+      c.createBuiltInQualityProfile("Foo", "xoo").done();
+    });
+    assertThat(profiles).containsOnlyKeys("xoo");
+    assertThat(profiles.get("xoo")).containsOnlyKeys("Foo");
+    BuiltInQualityProfile profile = profiles.get("xoo").get("Foo");
+    assertThat(profile.name()).isEqualTo("Foo");
+    assertThat(profile.language()).isEqualTo("xoo");
+    assertThat(profile.isDefault()).isFalse();
+  }
+
+  @Test
+  public void sanityCheck() {
+    Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
+      NewBuiltInQualityProfile profile1 = c.createBuiltInQualityProfile("Foo1", "xoo");
+      NewBuiltInActiveRule rule = profile1.activateRule("repo", "rule");
+      profile1.done();
+      NewBuiltInQualityProfile profile2 = c.createBuiltInQualityProfile("Foo2", "xoo");
+      profile2.done();
+      NewBuiltInQualityProfile profile3 = c.createBuiltInQualityProfile("Foo1", "xoo2");
+      profile3.done();
+      assertThat(profile1).isEqualTo(profile1);
+      assertThat(profile1).isNotEqualTo(null);
+      assertThat(profile1).isNotEqualTo("Foo");
+      assertThat(profile1).isNotEqualTo(profile2);
+      assertThat(profile1).isNotEqualTo(profile3);
+      assertThat(profile1.hashCode()).isNotEqualTo(profile2.hashCode());
+      assertThat(profile1.toString()).isEqualTo("NewBuiltInQualityProfile{name='Foo1', language='xoo', default='false'}");
+      assertThat(rule.toString()).isEqualTo("[repository=repo, key=rule]");
+    });
+    BuiltInQualityProfile profile1 = profiles.get("xoo").get("Foo1");
+    BuiltInQualityProfile profile2 = profiles.get("xoo").get("Foo2");
+    BuiltInQualityProfile profile3 = profiles.get("xoo2").get("Foo1");
+    assertThat(profile1).isEqualTo(profile1);
+    assertThat(profile1).isNotEqualTo(null);
+    assertThat(profile1).isNotEqualTo("Foo");
+    assertThat(profile1).isNotEqualTo(profile2);
+    assertThat(profile1).isNotEqualTo(profile3);
+    assertThat(profile1.hashCode()).isNotEqualTo(profile2.hashCode());
+    assertThat(profile1.toString()).isEqualTo("BuiltInQualityProfile{name='Foo1', language='xoo', default='false'}");
+    assertThat(profile1.rule(RuleKey.of("repo", "rule")).toString()).isEqualTo("[repository=repo, key=rule]");
+  }
+
+  @Test
+  public void createDefaultProfile() {
+    Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
+      c.createBuiltInQualityProfile("Foo", "xoo")
+        .setDefault(true)
+        .done();
+    });
+    assertThat(profiles).containsOnlyKeys("xoo");
+    assertThat(profiles.get("xoo")).containsOnlyKeys("Foo");
+    BuiltInQualityProfile profile = profiles.get("xoo").get("Foo");
+    assertThat(profile.name()).isEqualTo("Foo");
+    assertThat(profile.language()).isEqualTo("xoo");
+    assertThat(profile.isDefault()).isTrue();
+  }
+
+  @Test
+  public void duplicateProfile() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("There is already a quality profile with name 'Foo' for language 'xoo'");
+    define(c -> {
+      c.createBuiltInQualityProfile("Foo", "xoo").done();
+      c.createBuiltInQualityProfile("Foo", "xoo").done();
+    });
+  }
+
+  @Test
+  public void createProfileWithRules() {
+    Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
+      NewBuiltInQualityProfile profile = c.createBuiltInQualityProfile("Foo", "xoo");
+      profile.activateRule("repo", "ruleWithoutParam");
+      profile.activateRule("repo", "ruleWithSeverity").overrideSeverity("CRITICAL");
+      profile.activateRule("repo", "ruleWithParam").overrideParam("param", "value");
+      profile.done();
+    });
+    assertThat(profiles).containsOnlyKeys("xoo");
+    assertThat(profiles.get("xoo")).containsOnlyKeys("Foo");
+    BuiltInQualityProfile profile = profiles.get("xoo").get("Foo");
+    assertThat(profile.name()).isEqualTo("Foo");
+    assertThat(profile.language()).isEqualTo("xoo");
+    assertThat(profile.isDefault()).isFalse();
+    assertThat(profile.rules())
+      .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::repoKey, BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey,
+        BuiltInQualityProfilesDefinition.BuiltInActiveRule::overriddenSeverity, r -> r.overriddenParams().size())
+      .containsOnly(
+        tuple("repo", "ruleWithoutParam", null, 0),
+        tuple("repo", "ruleWithSeverity", "CRITICAL", 0),
+        tuple("repo", "ruleWithParam", null, 1));
+    assertThat(profile.rule(RuleKey.of("repo", "ruleWithParam")).overriddenParam("param").key()).isEqualTo("param");
+    assertThat(profile.rule(RuleKey.of("repo", "ruleWithParam")).overriddenParam("param").overriddenValue()).isEqualTo("value");
+  }
+
+  @Test
+  public void createProfileWithDuplicateRules() {
+
+    define(c -> {
+      NewBuiltInQualityProfile profile = c.createBuiltInQualityProfile("Foo", "xoo");
+      profile.activateRule("repo", "rule");
+
+      thrown.expect(IllegalArgumentException.class);
+      thrown.expectMessage("The rule 'repo:rule' is already activated");
+
+      profile.activateRule("repo", "rule");
+    });
+  }
+
+  private Map<String, Map<String, BuiltInQualityProfile>> define(Consumer<BuiltInQualityProfilesDefinition.Context> consumer) {
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    new FakeProfile(consumer).define(context);
+    return context.profilesByLanguageAndName();
+  }
+
+  private class FakeProfile implements BuiltInQualityProfilesDefinition {
+
+    private Consumer<Context> consumer;
+
+    public FakeProfile(Consumer<Context> consumer) {
+      this.consumer = consumer;
+    }
+
+    @Override
+    public void define(Context context) {
+      consumer.accept(context);
+    }
+
+  }
+
+}
index 10b91fafc9e374f0f9c46b78c58e2ab2d2e2528a..8ea8bbab6f0e9f14be54e38b7bd64cd4c8ebabbb 100644 (file)
@@ -60,9 +60,9 @@ public class RulesDefinitionAnnotationLoaderTest {
     RulesDefinition.Context context = new RulesDefinition.Context();
     RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
     NewRule newRule = annotationLoader.loadRule(newRepository, RuleWithProperty.class);
-    newRule.setName("Overriden name");
+    newRule.setName("Overridden name");
     newRule.param("property").setDefaultValue("true");
-    newRule.param("property").setDescription("Overriden");
+    newRule.param("property").setDescription("Overridden");
     newRepository.done();
 
     RulesDefinition.Repository repository = context.repository("squid");
@@ -70,14 +70,14 @@ public class RulesDefinitionAnnotationLoaderTest {
     RulesDefinition.Rule rule = repository.rules().get(0);
     assertThat(rule.key()).isEqualTo("foo");
     assertThat(rule.status()).isEqualTo(RuleStatus.BETA);
-    assertThat(rule.name()).isEqualTo("Overriden name");
+    assertThat(rule.name()).isEqualTo("Overridden name");
     assertThat(rule.htmlDescription()).isEqualTo("Foo Bar");
     assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
     assertThat(rule.params()).hasSize(1);
 
     RulesDefinition.Param prop = rule.param("property");
     assertThat(prop.key()).isEqualTo("property");
-    assertThat(prop.description()).isEqualTo("Overriden");
+    assertThat(prop.description()).isEqualTo("Overridden");
     assertThat(prop.defaultValue()).isEqualTo("true");
     assertThat(prop.type()).isEqualTo(RuleParamType.STRING);
   }
index 5c487b5bf4b128e019df801fcc2287d2fb56da45..515d6f1e5ac44c418c03c4d5ec5095b8e7fde72a 100644 (file)
@@ -66,8 +66,8 @@ public class DeprecatedIssueWrapper implements Issue {
 
   @Override
   public String severity() {
-    Severity overridenSeverity = newIssue.overriddenSeverity();
-    return overridenSeverity != null ? overridenSeverity.name() : null;
+    Severity overriddenSeverity = newIssue.overriddenSeverity();
+    return overriddenSeverity != null ? overriddenSeverity.name() : null;
   }
 
   @Override
index 758f6eebc16ca5dea540db85b18d0cbabafefe5f..f513e5c5ca2e5e51e8f05e5687485954780deec1 100644 (file)
@@ -198,7 +198,7 @@ public class LocalIssueTracking {
       tracked.setCreationDate(new Date(dto.getCreationDate()));
 
       if (dto.getManualSeverity()) {
-        // Severity overriden by user
+        // Severity overridden by user
         tracked.setSeverity(dto.getSeverity().name());
       }
       mergeTo.add(tracked);
index cae6130cea6df62d3d0b0db9fe99167280c168bd..ea4f1dad675a198f7b66bd37ace00fd6580bacc1 100644 (file)
@@ -62,7 +62,7 @@ public class CustomRulesTest extends AbstractIssueTest {
     Issue issue = issues.get(0);
     assertThat(issue.ruleKey()).isEqualTo("xoo:MyCustomRule");
     assertThat(issue.line()).isEqualTo(2);
-    // Overriden in quality profile
+    // Overridden in quality profile
     assertThat(issue.severity()).isEqualTo("CRITICAL");
   }
 }
index 286b412d1fa8184828af8783ad1e49663c7d5bdf..6e9173e042a1560bb756fad4cf87c270bdb2625d 100644 (file)
@@ -63,6 +63,7 @@ public class BuiltInQualityProfilesTest {
         tuple("Basic", "xoo", true, true),
         tuple("Sonar way", "xoo", true, false),
         tuple("empty", "xoo", true, false),
+        tuple("test BuiltInQualityProfilesDefinition", "xoo", true, false),
         tuple("Basic", "xoo2", true, true),
         tuple("Sonar way", "xoo2", true, false));
   }
@@ -77,6 +78,7 @@ public class BuiltInQualityProfilesTest {
         tuple("default-organization", "Basic", "xoo", true, true),
         tuple("default-organization", "Sonar way", "xoo", true, false),
         tuple("default-organization", "empty", "xoo", true, false),
+        tuple("default-organization", "test BuiltInQualityProfilesDefinition", "xoo", true, false),
         tuple("default-organization", "Basic", "xoo2", true, true),
         tuple("default-organization", "Sonar way", "xoo2", true, false));
   }