]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9672 Store the link between a rule and the plugin declaring it
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 3 Aug 2017 11:15:32 +0000 (13:15 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Thu, 7 Sep 2017 06:33:31 +0000 (08:33 +0200)
21 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDefinitionDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRules.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRulesTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66Test.java
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRulesTest/rules_6_5.sql [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/rule/DeprecatedRulesDefinitionLoader.java
server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java
server/sonar-server/src/main/java/org/sonar/server/rule/RuleCreator.java
server/sonar-server/src/main/java/org/sonar/server/rule/RuleDefinitionsLoader.java
server/sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionLoaderTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/RuleCreatorTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/RuleDefinitionsLoaderTest.java
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java

index 2a775ea8298efc138e654f873483a68c1a621b25..130b892622b02e05524d4077dc8d24969f9960c6 100644 (file)
@@ -115,7 +115,6 @@ public class ComputeEngineContainerImplTest {
 
     underTest
       .start(new Props(properties));
-
     MutablePicoContainer picoContainer = underTest.getComponentContainer().getPicoContainer();
     assertThat(picoContainer.getComponentAdapters())
       .hasSize(
index 8cbd7afb00142d93755ea615aafc577c48e991c2..aa131c20e116996f1b2af7f399369ddfbe3f4f30 100644 (file)
@@ -151,6 +151,7 @@ CREATE TABLE "RULE_REPOSITORIES" (
 
 CREATE TABLE "RULES" (
   "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "PLUGIN_KEY" VARCHAR(200),
   "PLUGIN_RULE_KEY" VARCHAR(200) NOT NULL,
   "PLUGIN_NAME" VARCHAR(255) NOT NULL,
   "DESCRIPTION" VARCHAR(16777215),
index 31c60553853773cd8d103127932a5e0e4abfac4a..13c687305c1b64d5d7672582f02aa7f5a4ef357a 100644 (file)
@@ -57,6 +57,8 @@ public class RuleDefinitionDto {
 
   private RuleKey key;
 
+  private String pluginKey;
+
   private long createdAt;
   private long updatedAt;
 
@@ -293,6 +295,15 @@ public class RuleDefinitionDto {
     return this;
   }
 
+  public String getPluginKey() {
+    return pluginKey;
+  }
+
+  public RuleDefinitionDto setPluginKey(String pluginKey) {
+    this.pluginKey = pluginKey;
+    return this;
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (!(obj instanceof RuleDto)) {
index 64d22aa8daca12014946f3023f69451e51c3d089..e5861debe31d6c20a45c5eec5e38889b5bab109f 100644 (file)
@@ -89,6 +89,15 @@ public class RuleDto {
     return this;
   }
 
+  public String getPluginKey() {
+    return definition.getPluginKey();
+  }
+
+  public RuleDto setPluginKey(String s) {
+    definition.setPluginKey(s);
+    return this;
+  }
+
   public String getDescription() {
     return definition.getDescription();
   }
index 5178e00e2da5353d03ce7f9caf4cf0ecdaabd332..d1a161504b437dd57e3043247c7b8413ba3b43f9 100644 (file)
@@ -22,6 +22,7 @@
     r.gap_description as "gapDescription",
     r.system_tags as "systemTagsField",
     r.rule_type as "type",
+    r.plugin_key as "pluginKey",
   </sql>
 
   <sql id="selectRuleTableColumns">
 
   <insert id="insertDefinition" parameterType="org.sonar.db.rule.RuleDefinitionDto" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
     insert into rules (
+      plugin_key,
       plugin_rule_key,
       plugin_name,
       description,
       updated_at
     )
     values (
+      #{pluginKey,jdbcType=VARCHAR},
       #{ruleKey,jdbcType=VARCHAR},
       #{repositoryKey,jdbcType=VARCHAR},
       #{description,jdbcType=VARCHAR},
 
   <update id="updateDefinition" parameterType="org.sonar.db.rule.RuleDefinitionDto">
     update rules set
+      plugin_key=#{pluginKey,jdbcType=VARCHAR},
       plugin_rule_key=#{ruleKey,jdbcType=VARCHAR},
       plugin_name=#{repositoryKey,jdbcType=VARCHAR},
       description=#{description,jdbcType=VARCHAR},
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRules.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRules.java
new file mode 100644 (file)
index 0000000..4fa05dc
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.platform.db.migration.version.v66;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddPluginKeyToRules extends DdlChange {
+
+  public AddPluginKeyToRules(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDialect(), "rules")
+      .addColumn(newVarcharColumnDefBuilder()
+        .setColumnName("plugin_key")
+        .setLimit(200)
+        .setIsNullable(true)
+        .build())
+      .build());
+  }
+}
index c8df2d866856592d267049123a6d782acbb1a2c8..3ac1c79b7fa9ee4e6a12e2c2ea921e67ac85dd0e 100644 (file)
@@ -31,6 +31,6 @@ public class DbVersion66 implements DbVersion {
       .add(1801, "Create table CE task characteristics", CreateTableCeTaskCharacteristics.class)
       .add(1802, "Delete leak settings on views", DeleteLeakSettingsOnViews.class)
       .add(1803, "Fix empty USERS.EXTERNAL_IDENTITY and USERS.EXTERNAL_IDENTITY_PROVIDER", FixEmptyIdentityProviderInUsers.class)
-    ;
+      .add(1804, "Add rules.plugin_key", AddPluginKeyToRules.class);
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRulesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRulesTest.java
new file mode 100644 (file)
index 0000000..d4a0cce
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.platform.db.migration.version.v66;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+public class AddPluginKeyToRulesTest {
+  @Rule
+  public final CoreDbTester dbTester = CoreDbTester.createForSchema(AddPluginKeyToRulesTest.class, "rules_6_5.sql");
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddPluginKeyToRules underTest = new AddPluginKeyToRules(dbTester.database());
+
+  @Test
+  public void column_is_added_to_table() throws SQLException {
+    underTest.execute();
+
+    dbTester.assertColumnDefinition("rules", "plugin_key", java.sql.Types.VARCHAR, 200, true);
+  }
+
+  @Test
+  public void migration_is_not_reentrant() throws SQLException {
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+
+    underTest.execute();
+  }
+
+}
index 4a97a611e52eef28c1bd38a396848804821a2f0a..591df1a3dd4eb6890143e927d5be4e53b9367486 100644 (file)
@@ -17,6 +17,7 @@
  * 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.platform.db.migration.version.v66;
 
 import org.junit.Test;
@@ -34,6 +35,6 @@ public class DbVersion66Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 4);
+    verifyMigrationCount(underTest, 5);
   }
 }
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRulesTest/rules_6_5.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v66/AddPluginKeyToRulesTest/rules_6_5.sql
new file mode 100644 (file)
index 0000000..375442e
--- /dev/null
@@ -0,0 +1,23 @@
+CREATE TABLE "RULES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "PLUGIN_RULE_KEY" VARCHAR(200) NOT NULL,
+  "PLUGIN_NAME" VARCHAR(255) NOT NULL,
+  "DESCRIPTION" VARCHAR(16777215),
+  "DESCRIPTION_FORMAT" VARCHAR(20),
+  "PRIORITY" INTEGER,
+  "IS_TEMPLATE" BOOLEAN DEFAULT FALSE,
+  "TEMPLATE_ID" INTEGER,
+  "PLUGIN_CONFIG_KEY" VARCHAR(200),
+  "NAME" VARCHAR(200),
+  "STATUS" VARCHAR(40),
+  "LANGUAGE" VARCHAR(20),
+  "DEF_REMEDIATION_FUNCTION" VARCHAR(20),
+  "DEF_REMEDIATION_GAP_MULT" VARCHAR(20),
+  "DEF_REMEDIATION_BASE_EFFORT" VARCHAR(20),
+  "GAP_DESCRIPTION" VARCHAR(4000),
+  "SYSTEM_TAGS" VARCHAR(4000),
+  "RULE_TYPE" TINYINT,
+  "CREATED_AT" BIGINT,
+  "UPDATED_AT" BIGINT
+);
+CREATE UNIQUE INDEX "RULES_REPO_KEY" ON "RULES" ("PLUGIN_NAME", "PLUGIN_RULE_KEY");
\ No newline at end of file
index 07ef2553697c450f04cfb50295700866285a8712..4edc0f7768c35389050cc07ddff7c28693d7a45e 100644 (file)
@@ -36,6 +36,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import org.apache.commons.io.FileUtils;
 import org.picocontainer.Startable;
@@ -93,6 +94,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   // following fields are available after startup
   private final Map<String, PluginInfo> pluginInfosByKeys = new HashMap<>();
   private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>();
+  private final Map<ClassLoader, String> keysByClassLoader = new HashMap<>();
 
   public ServerPluginRepository(SonarRuntime runtime, ServerUpgradeStatus upgradeStatus,
     ServerFileSystem fs, PluginLoader loader) {
@@ -124,9 +126,18 @@ public class ServerPluginRepository implements PluginRepository, Startable {
     loader.unload(pluginInstancesByKeys.values());
     pluginInstancesByKeys.clear();
     pluginInfosByKeys.clear();
+    keysByClassLoader.clear();
     started.set(true);
   }
 
+  /**
+   * Return the key of the plugin the extension (in the sense of {@link Plugin.Context#addExtension(Object)} is coming from.
+   */
+  @CheckForNull
+  public String getPluginKey(Object extension) {
+    return keysByClassLoader.get(extension.getClass().getClassLoader());
+  }
+
   /**
    * Load the plugins that are located in extensions/plugins. Blacklisted plugins are
    * deleted.
@@ -286,6 +297,10 @@ public class ServerPluginRepository implements PluginRepository, Startable {
 
   private void loadInstances() {
     pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys));
+
+    for (Map.Entry<String, Plugin> e : pluginInstancesByKeys.entrySet()) {
+      keysByClassLoader.put(e.getValue().getClass().getClassLoader(), e.getKey());
+    }
   }
 
   /**
index b4511c6d5ad2d9abe5675a4f9c1fee572d21d24a..2e09fec93a979185acb696dfe4ab922b1c999d23 100644 (file)
@@ -29,11 +29,11 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
+import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rules.RuleParam;
 import org.sonar.api.rules.RuleRepository;
-import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.server.debt.DebtRemediationFunction;
 import org.sonar.api.server.rule.RuleParamType;
@@ -46,6 +46,7 @@ import org.sonar.server.debt.DebtModelPluginRepository;
 import org.sonar.server.debt.DebtModelXMLExporter;
 import org.sonar.server.debt.DebtModelXMLExporter.RuleDebt;
 import org.sonar.server.debt.DebtRulesXMLImporter;
+import org.sonar.server.plugins.ServerPluginRepository;
 
 import static com.google.common.collect.Lists.newArrayList;
 
@@ -63,9 +64,12 @@ public class DeprecatedRulesDefinitionLoader {
 
   private final DebtModelPluginRepository languageModelFinder;
   private final DebtRulesXMLImporter importer;
+  private final ServerPluginRepository serverPluginRepository;
 
-  public DeprecatedRulesDefinitionLoader(RuleI18nManager i18n, DebtModelPluginRepository languageModelFinder, DebtRulesXMLImporter importer, RuleRepository[] repositories) {
+  public DeprecatedRulesDefinitionLoader(RuleI18nManager i18n, DebtModelPluginRepository languageModelFinder, DebtRulesXMLImporter importer,
+    ServerPluginRepository serverPluginRepository, RuleRepository[] repositories) {
     this.i18n = i18n;
+    this.serverPluginRepository = serverPluginRepository;
     this.repositories = repositories;
     this.languageModelFinder = languageModelFinder;
     this.importer = importer;
@@ -74,8 +78,9 @@ public class DeprecatedRulesDefinitionLoader {
   /**
    * Used when no deprecated repositories
    */
-  public DeprecatedRulesDefinitionLoader(RuleI18nManager i18n, DebtModelPluginRepository languageModelFinder, DebtRulesXMLImporter importer) {
-    this(i18n, languageModelFinder, importer, new RuleRepository[0]);
+  public DeprecatedRulesDefinitionLoader(RuleI18nManager i18n, DebtModelPluginRepository languageModelFinder, DebtRulesXMLImporter importer,
+    ServerPluginRepository serverPluginRepository) {
+    this(i18n, languageModelFinder, importer, serverPluginRepository, new RuleRepository[0]);
   }
 
   void complete(RulesDefinition.Context context) {
@@ -83,6 +88,7 @@ public class DeprecatedRulesDefinitionLoader {
     List<RuleDebt> ruleDebts = loadRuleDebtList();
 
     for (RuleRepository repository : repositories) {
+      context.setCurrentPluginKey(serverPluginRepository.getPluginKey(repository));
       // RuleRepository API does not handle difference between new and extended repositories,
       RulesDefinition.NewRepository newRepository;
       if (context.repository(repository.getKey()) == null) {
@@ -119,8 +125,7 @@ public class DeprecatedRulesDefinitionLoader {
         ruleDebt.coefficient(),
         ruleDebt.offset(),
         newRule.debtRemediationFunctions(),
-        repoKey, ruleKey
-        ));
+        repoKey, ruleKey));
     }
   }
 
@@ -159,8 +164,7 @@ public class DeprecatedRulesDefinitionLoader {
   private String paramDescription(String repositoryKey, String ruleKey, RuleParam param) {
     String desc = StringUtils.defaultIfEmpty(
       i18n.getParamDescription(repositoryKey, ruleKey, param.getKey()),
-      param.getDescription()
-      );
+      param.getDescription());
     return StringUtils.defaultIfBlank(desc, null);
   }
 
index a3750b27789cd9d339000a748f3d7ff58732508d..70e2eacbeceed42b3df599d421bec71395af3377 100644 (file)
@@ -214,6 +214,7 @@ public class RegisterRules implements Startable {
   private RuleDefinitionDto createRuleDto(RulesDefinition.Rule ruleDef, DbSession session) {
     RuleDefinitionDto ruleDto = new RuleDefinitionDto()
       .setRuleKey(RuleKey.of(ruleDef.repository().key(), ruleDef.key()))
+      .setPluginKey(ruleDef.pluginKey())
       .setIsTemplate(ruleDef.template())
       .setConfigKey(ruleDef.internalKey())
       .setLanguage(ruleDef.repository().language())
@@ -246,6 +247,10 @@ public class RegisterRules implements Startable {
     if (mergeDescription(def, dto)) {
       changed = true;
     }
+    if (!StringUtils.equals(dto.getPluginKey(), def.pluginKey())) {
+      dto.setPluginKey(def.pluginKey());
+      changed = true;
+    }
     if (!StringUtils.equals(dto.getConfigKey(), def.internalKey())) {
       dto.setConfigKey(def.internalKey());
       changed = true;
@@ -459,6 +464,10 @@ public class RegisterRules implements Startable {
       customRule.setConfigKey(templateRule.getConfigKey());
       changed = true;
     }
+    if (!StringUtils.equals(customRule.getPluginKey(), templateRule.getPluginKey())) {
+      customRule.setPluginKey(templateRule.getPluginKey());
+      changed = true;
+    }
     if (!StringUtils.equals(customRule.getDefRemediationFunction(), templateRule.getDefRemediationFunction())) {
       customRule.setDefRemediationFunction(templateRule.getDefRemediationFunction());
       changed = true;
index 941cc2835bfdc79dd5d5e70ea0c2205c26e13609..f9ad9c6ce6ab9ffe5734d9cd96b88de3df5cd736 100644 (file)
@@ -158,6 +158,7 @@ public class RuleCreator {
   private RuleKey createCustomRule(RuleKey ruleKey, NewCustomRule newRule, RuleDto templateRuleDto, DbSession dbSession) {
     RuleDefinitionDto ruleDefinition = new RuleDefinitionDto()
       .setRuleKey(ruleKey)
+      .setPluginKey(templateRuleDto.getPluginKey())
       .setTemplateId(templateRuleDto.getId())
       .setConfigKey(templateRuleDto.getConfigKey())
       .setName(newRule.name())
index 9652e4fc910d8c564e7da67527cee5a4cda4ad81..fdd5d565cc4035662aa332f93b26cff6277bfe6b 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.rule;
 
 import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.server.plugins.ServerPluginRepository;
 
 /**
  * Loads all instances of {@link RulesDefinition}. Used during server startup
@@ -30,11 +31,13 @@ public class RuleDefinitionsLoader {
   private final DeprecatedRulesDefinitionLoader deprecatedDefConverter;
   private final CommonRuleDefinitions coreCommonDefs;
   private final RulesDefinition[] pluginDefs;
+  private final ServerPluginRepository serverPluginRepository;
 
   public RuleDefinitionsLoader(DeprecatedRulesDefinitionLoader deprecatedDefConverter,
-    CommonRuleDefinitions coreCommonDefs, RulesDefinition[] pluginDefs) {
+    CommonRuleDefinitions coreCommonDefs, ServerPluginRepository serverPluginRepository, RulesDefinition[] pluginDefs) {
     this.deprecatedDefConverter = deprecatedDefConverter;
     this.coreCommonDefs = coreCommonDefs;
+    this.serverPluginRepository = serverPluginRepository;
     this.pluginDefs = pluginDefs;
   }
 
@@ -42,16 +45,18 @@ public class RuleDefinitionsLoader {
    * Used when no definitions at all.
    */
   public RuleDefinitionsLoader(DeprecatedRulesDefinitionLoader converter,
-    CommonRuleDefinitions coreCommonDefs) {
-    this(converter, coreCommonDefs, new RulesDefinition[0]);
+    CommonRuleDefinitions coreCommonDefs, ServerPluginRepository serverPluginRepository) {
+    this(converter, coreCommonDefs, serverPluginRepository, new RulesDefinition[0]);
   }
 
   public RulesDefinition.Context load() {
     RulesDefinition.Context context = new RulesDefinition.Context();
     for (RulesDefinition pluginDefinition : pluginDefs) {
+      context.setCurrentPluginKey(serverPluginRepository.getPluginKey(pluginDefinition));
       pluginDefinition.define(context);
     }
     deprecatedDefConverter.complete(context);
+    context.setCurrentPluginKey(null);
     coreCommonDefs.define(context);
     return context;
   }
index 6ec467354dd5f2726cc1cfd5055d1ef802693de8..7ec4f6e11bc50ae63f2329968c4a02ca272ab48a 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.core.i18n.RuleI18nManager;
 import org.sonar.server.debt.DebtModelPluginRepository;
 import org.sonar.server.debt.DebtModelXMLExporter;
 import org.sonar.server.debt.DebtRulesXMLImporter;
+import org.sonar.server.plugins.ServerPluginRepository;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -60,6 +61,9 @@ public class DeprecatedRulesDefinitionLoaderTest {
   @Mock
   DebtRulesXMLImporter importer;
 
+  @Mock
+  ServerPluginRepository pluginRepository;
+
   static class CheckstyleRules extends RuleRepository {
     public CheckstyleRules() {
       super("checkstyle", "java");
@@ -96,7 +100,9 @@ public class DeprecatedRulesDefinitionLoaderTest {
   @Test
   public void wrap_deprecated_rule_repositories() {
     RulesDefinition.Context context = new RulesDefinition.Context();
-    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, new RuleRepository[] {new CheckstyleRules()}).complete(context);
+    CheckstyleRules checkstyleRules = new CheckstyleRules();
+    when(pluginRepository.getPluginKey(checkstyleRules)).thenReturn("unittest");
+    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository, new RuleRepository[] {checkstyleRules}).complete(context);
 
     assertThat(context.repositories()).hasSize(1);
     RulesDefinition.Repository checkstyle = context.repository("checkstyle");
@@ -108,6 +114,7 @@ public class DeprecatedRulesDefinitionLoaderTest {
     RulesDefinition.Rule rule = checkstyle.rule("ConstantName");
     assertThat(rule).isNotNull();
     assertThat(rule.key()).isEqualTo("ConstantName");
+    assertThat(rule.pluginKey()).isEqualTo("unittest");
     assertThat(rule.name()).isEqualTo("Constant Name");
     assertThat(rule.htmlDescription()).isEqualTo("Checks that constant names conform to the specified format");
     assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
@@ -128,7 +135,7 @@ public class DeprecatedRulesDefinitionLoaderTest {
     RulesDefinition.Context context = new RulesDefinition.Context();
 
     // no more RuleRepository !
-    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer);
+    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository);
 
     assertThat(context.repositories()).isEmpty();
   }
@@ -140,7 +147,7 @@ public class DeprecatedRulesDefinitionLoaderTest {
     when(i18n.getDescription("checkstyle", "ConstantName")).thenReturn("Checks that constant names conform to the specified format");
     when(i18n.getParamDescription("checkstyle", "ConstantName", "format")).thenReturn("Regular expression");
 
-    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, new RuleRepository[] {new UseBundles()}).complete(context);
+    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository, new RuleRepository[] {new UseBundles()}).complete(context);
 
     RulesDefinition.Repository checkstyle = context.repository("checkstyle");
     RulesDefinition.Rule rule = checkstyle.rule("ConstantName");
@@ -162,15 +169,14 @@ public class DeprecatedRulesDefinitionLoaderTest {
         .setRuleKey(RuleKey.of("checkstyle", "ConstantName"))
         .setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
         .setCoefficient("1d")
-        .setOffset("10min")
-      );
+        .setOffset("10min"));
 
     Reader javaModelReader = mock(Reader.class);
     when(debtModelRepository.createReaderForXMLFile("java")).thenReturn(javaModelReader);
     when(debtModelRepository.getContributingPluginList()).thenReturn(newArrayList("java"));
     when(importer.importXML(eq(javaModelReader), any(ValidationMessages.class))).thenReturn(ruleDebts);
 
-    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, new RuleRepository[] {new CheckstyleRules()}).complete(context);
+    new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository, new RuleRepository[] {new CheckstyleRules()}).complete(context);
 
     assertThat(context.repositories()).hasSize(1);
     RulesDefinition.Repository checkstyle = context.repository("checkstyle");
@@ -192,8 +198,7 @@ public class DeprecatedRulesDefinitionLoaderTest {
       new DebtModelXMLExporter.RuleDebt()
         .setRuleKey(RuleKey.of("checkstyle", "ConstantName"))
         .setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
-        .setCoefficient("1d")
-      );
+        .setCoefficient("1d"));
 
     Reader javaModelReader = mock(Reader.class);
     when(debtModelRepository.createReaderForXMLFile("java")).thenReturn(javaModelReader);
@@ -201,7 +206,7 @@ public class DeprecatedRulesDefinitionLoaderTest {
     when(importer.importXML(eq(javaModelReader), any(ValidationMessages.class))).thenReturn(ruleDebts);
 
     try {
-      new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, new RuleRepository[] {new CheckstyleRules()}).complete(context);
+      new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository, new RuleRepository[] {new CheckstyleRules()}).complete(context);
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalArgumentException.class);
index 50db488c3a3bc4c445a36fbccde0349939777816..04274707801995628cad08dc4c61d6b42638c34e 100644 (file)
@@ -47,6 +47,7 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.organization.OrganizationFlags;
 import org.sonar.server.organization.TestOrganizationFlags;
+import org.sonar.server.plugins.ServerPluginRepository;
 import org.sonar.server.qualityprofile.RuleActivator;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
 import org.sonar.server.rule.index.RuleIndex;
@@ -58,6 +59,7 @@ import static com.google.common.collect.Sets.newHashSet;
 import static java.util.Collections.singletonList;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -67,6 +69,7 @@ import static org.sonar.api.rule.Severity.INFO;
 
 public class RegisterRulesTest {
 
+  private static final String FAKE_PLUGIN_KEY = "unittest";
   private static final Date DATE1 = DateUtils.parseDateTime("2014-01-01T19:10:03+0100");
   private static final Date DATE2 = DateUtils.parseDateTime("2014-02-01T12:10:03+0100");
   private static final Date DATE3 = DateUtils.parseDateTime("2014-03-01T12:10:03+0100");
@@ -122,6 +125,7 @@ public class RegisterRulesTest {
     assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d");
     assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h");
     assertThat(rule1.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
+    assertThat(rule1.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
 
     List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(dbTester.getSession(), RULE_KEY1);
     assertThat(params).hasSize(2);
@@ -481,7 +485,10 @@ public class RegisterRulesTest {
   }
 
   private void execute(RulesDefinition... defs) {
-    RuleDefinitionsLoader loader = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), mock(CommonRuleDefinitionsImpl.class), defs);
+    ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+    when(pluginRepository.getPluginKey(any(RulesDefinition.class))).thenReturn(FAKE_PLUGIN_KEY);
+    RuleDefinitionsLoader loader = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), mock(CommonRuleDefinitionsImpl.class), pluginRepository,
+      defs);
     Languages languages = mock(Languages.class);
     when(languages.get("java")).thenReturn(mock(Language.class));
     reset(webServerRuleFinder);
index 9dbda10be8d5dea949b722d88df9a4774863b2b2..3e0c0cb7cdbffdcc3e091b2eeffd405f36b5d659 100644 (file)
@@ -91,6 +91,7 @@ public class RuleCreatorTest {
     RuleDto rule = dbTester.getDbClient().ruleDao().selectOrFailByKey(dbSession, dbTester.getDefaultOrganization(), customRuleKey);
     assertThat(rule).isNotNull();
     assertThat(rule.getKey()).isEqualTo(RuleKey.of("java", "CUSTOM_RULE"));
+    assertThat(rule.getPluginKey()).isEqualTo("sonarjava");
     assertThat(rule.getTemplateId()).isEqualTo(templateRule.getId());
     assertThat(rule.getName()).isEqualTo("My custom");
     assertThat(rule.getDescription()).isEqualTo("Some description");
@@ -461,6 +462,7 @@ public class RuleCreatorTest {
     RuleDto templateRule = RuleTesting.newDto(RuleKey.of("java", "S001"), dbTester.getDefaultOrganization())
       .setIsTemplate(true)
       .setLanguage("java")
+      .setPluginKey("sonarjava")
       .setConfigKey("S001")
       .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
       .setDefRemediationGapMultiplier("1h")
@@ -489,7 +491,8 @@ public class RuleCreatorTest {
       .setCreatedAt(new Date().getTime())
       .setUpdatedAt(new Date().getTime());
     dbTester.rules().insert(templateRule);
-    dbTester.rules().insertRuleParam(templateRule, param -> param.setName("myIntegers").setType("INTEGER,multiple=true,values=1;2;3").setDescription("My Integers").setDefaultValue("1"));
+    dbTester.rules().insertRuleParam(templateRule,
+      param -> param.setName("myIntegers").setType("INTEGER,multiple=true,values=1;2;3").setDescription("My Integers").setDefaultValue("1"));
     ruleIndexer.commitAndIndex(dbTester.getSession(), templateRule.getKey());
     return templateRule;
   }
index 57f205c5d2afcea095a117ec0a2e7037357bc96b..1b04d4b1876b22f4be56376d5dea7d3ae0450732 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.rule;
 
 import org.junit.Test;
 import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.server.plugins.ServerPluginRepository;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -30,7 +31,7 @@ public class RuleDefinitionsLoaderTest {
   @Test
   public void no_definitions() {
     CommonRuleDefinitions commonRulesDefinitions = mock(CommonRuleDefinitions.class);
-    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions).load();
+    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions, mock(ServerPluginRepository.class)).load();
 
     assertThat(context.repositories()).isEmpty();
   }
@@ -38,9 +39,10 @@ public class RuleDefinitionsLoaderTest {
   @Test
   public void load_definitions() {
     CommonRuleDefinitions commonRulesDefinitions = mock(CommonRuleDefinitions.class);
-    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions, new RulesDefinition[] {
-      new FindbugsDefinitions(), new SquidDefinitions()
-    }).load();
+    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions, mock(ServerPluginRepository.class),
+      new RulesDefinition[] {
+        new FindbugsDefinitions(), new SquidDefinitions()
+      }).load();
 
     assertThat(context.repositories()).hasSize(2);
     assertThat(context.repository("findbugs")).isNotNull();
@@ -50,9 +52,10 @@ public class RuleDefinitionsLoaderTest {
   @Test
   public void define_common_rules() throws Exception {
     CommonRuleDefinitions commonRulesDefinitions = new FakeCommonRuleDefinitions();
-    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions, new RulesDefinition[] {
-      new SquidDefinitions()
-    }).load();
+    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions, mock(ServerPluginRepository.class),
+      new RulesDefinition[] {
+        new SquidDefinitions()
+      }).load();
 
     assertThat(context.repositories()).extracting("key").containsOnly("squid", "common-java");
     assertThat(context.repository("common-java").rules()).extracting("key").containsOnly("InsufficientBranchCoverage");
@@ -65,9 +68,10 @@ public class RuleDefinitionsLoaderTest {
   @Test
   public void plugin_common_rules_are_overridden() throws Exception {
     CommonRuleDefinitions commonRulesDefinitions = new FakeCommonRuleDefinitions();
-    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions, new RulesDefinition[] {
-      new PluginCommonRuleDefinitions()
-    }).load();
+    RulesDefinition.Context context = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), commonRulesDefinitions, mock(ServerPluginRepository.class),
+      new RulesDefinition[] {
+        new PluginCommonRuleDefinitions()
+      }).load();
 
     assertThat(context.repositories()).extracting("key").containsOnly("common-java");
     assertThat(context.repository("common-java").rules()).extracting("key").containsOnly("InsufficientBranchCoverage");
index 0e9b90fbc3cfcd09cffdf0ee67fa1deab774f5bf..c9690b1536cf2f709543bc88a8b613310b2e1411 100644 (file)
@@ -385,6 +385,7 @@ public interface RulesDefinition {
    */
   class Context {
     private final Map<String, Repository> repositoriesByKey = new HashMap<>();
+    private String currentPluginKey;
 
     /**
      * New builder for {@link org.sonar.api.server.rule.RulesDefinition.Repository}.
@@ -442,6 +443,10 @@ public interface RulesDefinition {
       }
       repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository, existing));
     }
+
+    public void setCurrentPluginKey(@Nullable String pluginKey) {
+      this.currentPluginKey = pluginKey;
+    }
   }
 
   interface NewExtendedRepository {
@@ -496,7 +501,7 @@ public interface RulesDefinition {
     @Override
     public NewRule createRule(String ruleKey) {
       checkArgument(!newRules.containsKey(ruleKey), "The rule '%s' of repository '%s' is declared several times", ruleKey, key);
-      NewRule newRule = new NewRule(key, ruleKey);
+      NewRule newRule = new NewRule(context.currentPluginKey, key, ruleKey);
       newRules.put(ruleKey, newRule);
       return newRule;
     }
@@ -671,6 +676,7 @@ public interface RulesDefinition {
   }
 
   class NewRule {
+    private final String pluginKey;
     private final String repoKey;
     private final String key;
     private RuleType type;
@@ -688,7 +694,8 @@ public interface RulesDefinition {
     private final DebtRemediationFunctions functions;
     private boolean activatedByDefault;
 
-    private NewRule(String repoKey, String key) {
+    private NewRule(@Nullable String pluginKey, String repoKey, String key) {
+      this.pluginKey = pluginKey;
       this.repoKey = repoKey;
       this.key = key;
       this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
@@ -926,6 +933,7 @@ public interface RulesDefinition {
 
   @Immutable
   class Rule {
+    private final String pluginKey;
     private final Repository repository;
     private final String repoKey;
     private final String key;
@@ -944,6 +952,7 @@ public interface RulesDefinition {
     private final boolean activatedByDefault;
 
     private Rule(Repository repository, NewRule newRule) {
+      this.pluginKey = newRule.pluginKey;
       this.repository = repository;
       this.repoKey = newRule.repoKey;
       this.key = newRule.key;
@@ -970,6 +979,14 @@ public interface RulesDefinition {
       return repository;
     }
 
+    /**
+     * @since 6.6 the plugin the rule was declared in
+     */
+    @CheckForNull
+    public String pluginKey() {
+      return pluginKey;
+    }
+
     public String key() {
       return key;
     }
index 06b6482500c9dfa3a22b0f309082dd6851ea2ba5..d8d9c6a95369751f1e08eff132a032e84a1132f6 100644 (file)
@@ -94,6 +94,7 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
 
     pluginInstancesByKeys.clear();
     infosByKeys.clear();
+    keysByClassLoader.clear();
   }
 
   @Override