]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15237 Improve startup performance of the web process
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 27 Jul 2021 21:10:33 +0000 (16:10 -0500)
committersonartech <sonartech@sonarsource.com>
Wed, 4 Aug 2021 20:08:14 +0000 (20:08 +0000)
31 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml
server/sonar-server-common/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/ServerRuleFinder.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/DefaultRuleFinderTest.java
server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java [new file with mode: 0644]
server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java [new file with mode: 0644]
server/sonar-webserver-api/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java [new file with mode: 0644]
server/sonar-webserver-api/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java [new file with mode: 0644]
server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java [new file with mode: 0644]
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImpl.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImplTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImplTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/CachingRuleFinder.java [deleted file]
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/RegisterRules.java
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java [deleted file]
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java [deleted file]
server/sonar-webserver-core/src/main/java/org/sonar/server/startup/RegisterMetrics.java
server/sonar-webserver-core/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RegisterRulesTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java

index 59fab5899b4ed746018bcc3fe17b4346f6606f93..8507c5885b9a2164f896ff6c1e171d387af02ceb 100644 (file)
@@ -203,6 +203,10 @@ public class RuleDao implements Dao {
     return executeLargeInputs(ruleKeys, mapper(session)::selectParamsByRuleKeys);
   }
 
+  public List<RuleParamDto> selectAllRuleParams(DbSession session) {
+    return mapper(session).selectAllRuleParams();
+  }
+
   public List<RuleParamDto> selectRuleParamsByRuleUuids(DbSession dbSession, Collection<String> ruleUuids) {
     return executeLargeInputs(ruleUuids, mapper(dbSession)::selectParamsByRuleUuids);
   }
index a49cf2e22a57b56891515f86448c7472777cf048..b4b815ec7a700340911b86d595ff49bf3e756261 100644 (file)
@@ -81,6 +81,8 @@ public interface RuleMapper {
 
   List<RuleParamDto> selectParamsByRuleKeys(@Param("ruleKeys") List<RuleKey> ruleKeys);
 
+  List<RuleParamDto> selectAllRuleParams();
+
   void insertParameter(RuleParamDto param);
 
   void updateParameter(RuleParamDto param);
index 8872334858d41fe379d7811a0ce8437cdb5d4c10..8d05ec5155bd9560a9b2335143c8c11dde4cebe0 100644 (file)
@@ -36,7 +36,7 @@
     inner join rules r on r.uuid = a.rule_uuid
   </sql>
 
-  <insert id="insert" parameterType="ActiveRule">
+  <insert id="insert" parameterType="ActiveRule" useGeneratedKeys="false">
     insert into active_rules (
       uuid,
       profile_uuid,
@@ -56,7 +56,7 @@
     )
   </insert>
 
-  <update id="update" parameterType="ActiveRule">
+  <update id="update" parameterType="ActiveRule" useGeneratedKeys="false">
     update active_rules
     set
       failure_level = #{severity, jdbcType=INTEGER},
index ca8ee02db2e04e523c85cc6c2f14f3d501f62d36..06b8316d728735ab594cca4f55604cd0c415b15c 100644 (file)
   </select>
 
   <select id="selectIndexingRuleExtensionsByIds" parameterType="map" resultType="org.sonar.db.rule.RuleExtensionForIndexingDto">
-    <include refid="sqlSelectIndexingRuleExtensions" />
+    <include refid="sqlSelectIndexingRuleExtensions"/>
     and
     <foreach collection="ruleExtensionIds" index="index" item="ruleExtId" open="" separator=" or " close="">
       ( r.uuid = #{ruleExtId, jdbcType=VARCHAR} )
     order by r.created_at asc
   </select>
 
-  <select id="scrollIndexingRules" resultType="org.sonar.db.rule.RuleForIndexingDto" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
+  <select id="scrollIndexingRules" resultType="org.sonar.db.rule.RuleForIndexingDto" fetchSize="${_scrollFetchSize}"
+          resultSetType="FORWARD_ONLY">
     <include refid="sqlSelectIndexingRules"/>
     order by r.created_at asc
   </select>
     <include refid="outerJoinRulesMetadata"/>
     where
       r.status != 'REMOVED' and r.is_external=${_false} and r.is_template=${_false}
-      and r.rule_type in <foreach collection="types" item="type" separator="," open="(" close=")">#{type, jdbcType=INTEGER}</foreach>
-      and r.language in <foreach collection="languages" item="language" separator="," open="(" close=")">#{language, jdbcType=VARCHAR}</foreach>
+      and r.rule_type in
+    <foreach collection="types" item="type" separator="," open="(" close=")">#{type, jdbcType=INTEGER}</foreach>
+      and r.language in
+    <foreach collection="languages" item="language" separator="," open="(" close=")">#{language, jdbcType=VARCHAR}</foreach>
   </select>
 
   <insert id="insertDefinition" parameterType="org.sonar.db.rule.RuleDefinitionDto" useGeneratedKeys="false">
       </foreach>
   </select>
 
+    <select id="selectAllRuleParams" resultType="RuleParam">
+    select
+      <include refid="paramColumns"/>
+    from
+      rules_parameters p
+  </select>
+
   <select id="selectParamsByRuleKey" resultType="RuleParam" parameterType="org.sonar.api.rule.RuleKey">
     select
       <include refid="paramColumns"/>
     </foreach>
   </delete>
 
-  <insert id="insertDeprecatedRuleKey" parameterType="org.sonar.db.rule.DeprecatedRuleKeyDto" keyColumn="uuid" useGeneratedKeys="false" keyProperty="uuid">
+  <insert id="insertDeprecatedRuleKey" parameterType="org.sonar.db.rule.DeprecatedRuleKeyDto" keyColumn="uuid" useGeneratedKeys="false"
+          keyProperty="uuid">
     INSERT INTO deprecated_rule_keys (
       uuid,
       rule_uuid,
index 36e4b71fe67b0b4d18e668cd267540c5aa59983c..3d025d16620fd64d7a48cd411a727e816cbb0cb0 100644 (file)
@@ -42,9 +42,6 @@ import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleParamDto;
 import org.sonar.markdown.Markdown;
 
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Optional.empty;
-
 /**
  * Will be removed in the future.
  */
@@ -61,12 +58,25 @@ public class DefaultRuleFinder implements ServerRuleFinder {
   @Override
   public Optional<RuleDefinitionDto> findDtoByKey(RuleKey key) {
     try (DbSession dbSession = dbClient.openSession(false)) {
-      Optional<RuleDefinitionDto> rule = ruleDao.selectDefinitionByKey(dbSession, key);
-      if (rule.isPresent() && rule.get().getStatus() != RuleStatus.REMOVED) {
-        return rule;
-      } else {
-        return empty();
-      }
+      return ruleDao.selectDefinitionByKey(dbSession, key)
+        .filter(r -> r.getStatus() != RuleStatus.REMOVED);
+    }
+  }
+
+  @Override
+  public Optional<RuleDefinitionDto> findDtoByUuid(String uuid) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      return ruleDao.selectDefinitionByUuid(uuid, dbSession)
+        .filter(r -> r.getStatus() != RuleStatus.REMOVED);
+    }
+  }
+
+  @Override
+  public Collection<RuleDefinitionDto> findAll() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      List<RuleDefinitionDto> list = new ArrayList<>();
+      ruleDao.selectEnabled(dbSession, r -> list.add(r.getResultObject()));
+      return list;
     }
   }
 
@@ -150,7 +160,7 @@ public class DefaultRuleFinder implements ServerRuleFinder {
       }
     }
 
-    List<org.sonar.api.rules.RuleParam> apiParams = newArrayList();
+    List<org.sonar.api.rules.RuleParam> apiParams = new ArrayList<>();
     for (RuleParamDto param : params) {
       apiParams.add(new org.sonar.api.rules.RuleParam(apiRule, param.getName(), param.getDescription(), param.getType())
         .setDefaultValue(param.getDefaultValue()));
index 9f390ad0f468059163ca3d54f01aea661fd5c2f1..06737ddfcca8d82b96e25f151af8cd767a114513 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.rule;
 
+import java.util.Collection;
 import java.util.Optional;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleFinder;
@@ -26,4 +27,9 @@ import org.sonar.db.rule.RuleDefinitionDto;
 
 public interface ServerRuleFinder extends RuleFinder {
   Optional<RuleDefinitionDto> findDtoByKey(RuleKey key);
+
+  Optional<RuleDefinitionDto> findDtoByUuid(String uuid);
+
+  Collection<RuleDefinitionDto> findAll();
+
 }
index f02e599db11abe11832cfa4d715475eeaa975cf4..9943549f96a63d8f27a2223d2464621b4df8b9fd 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.rule;
 import com.google.common.collect.ImmutableSet;
 import org.junit.Before;
 import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleQuery;
@@ -41,10 +42,10 @@ public class DefaultRuleFinderTest {
   @org.junit.Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession session = dbTester.getSession();
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession session = dbTester.getSession();
 
-  private RuleDto rule1 = new RuleDto()
+  private final RuleDto rule1 = new RuleDto()
     .setName("Check Header")
     .setConfigKey("Checker/Treewalker/HeaderCheck")
     .setRuleKey("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck")
@@ -53,7 +54,7 @@ public class DefaultRuleFinderTest {
     .setScope(Scope.MAIN)
     .setStatus(RuleStatus.READY);
 
-  private RuleDto rule2 = new RuleDto()
+  private final RuleDto rule2 = new RuleDto()
     .setName("Disabled checked")
     .setConfigKey("Checker/Treewalker/DisabledCheck")
     .setRuleKey("DisabledCheck")
@@ -62,7 +63,7 @@ public class DefaultRuleFinderTest {
     .setScope(Scope.MAIN)
     .setStatus(RuleStatus.REMOVED);
 
-  private RuleDto rule3 = new RuleDto()
+  private final RuleDto rule3 = new RuleDto()
     .setName("Check Annotation")
     .setConfigKey("Checker/Treewalker/AnnotationUseStyleCheck")
     .setRuleKey("com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck")
@@ -71,7 +72,7 @@ public class DefaultRuleFinderTest {
     .setScope(Scope.MAIN)
     .setStatus(RuleStatus.READY);
 
-  private RuleDto rule4 = new RuleDto()
+  private final RuleDto rule4 = new RuleDto()
     .setName("Call Super First")
     .setConfigKey("rulesets/android.xml/CallSuperFirst")
     .setRuleKey("CallSuperFirst")
@@ -80,7 +81,7 @@ public class DefaultRuleFinderTest {
     .setScope(Scope.MAIN)
     .setStatus(RuleStatus.READY);
 
-  private DefaultRuleFinder underTest = new DefaultRuleFinder(dbClient);
+  private final DefaultRuleFinder underTest = new DefaultRuleFinder(dbClient);
 
   @Before
   public void setup() {
@@ -108,19 +109,31 @@ public class DefaultRuleFinderTest {
 
     // find_all_enabled
     assertThat(underTest.findAll(RuleQuery.create())).extracting("ruleKey").containsOnly(rule1.getKey(), rule3.getKey(), rule4.getKey());
-    assertThat(underTest.findAll(RuleQuery.create())).hasSize(3);
+
+    // find_all
+    assertThat(underTest.findAll()).extracting("ruleKey").containsOnly(rule1.getKey().rule(), rule3.getKey().rule(), rule4.getKey().rule());
 
     // do_not_find_disabled_rules
     assertThat(underTest.findByKey("checkstyle", "DisabledCheck")).isNull();
 
     // do_not_find_unknown_rules
     assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey("unknown_repository"))).isEmpty();
+
+    assertThat(underTest.findDtoByKey(RuleKey.of("pmd", "CallSuperFirst")).get().getUuid()).isEqualTo(rule4.getUuid());
+    assertThat(underTest.findDtoByUuid(rule4.getUuid())).isPresent();
+  }
+
+  @Test
+  public void should_fail_find() {
+    assertThat(underTest.findDtoByKey(RuleKey.of("pmd", "unknown"))).isEmpty();
+    assertThat(underTest.findDtoByUuid("unknown")).isEmpty();
   }
 
   @Test
   public void find_all_not_include_removed_rule() {
     // rule 3 is REMOVED
     assertThat(underTest.findAll(RuleQuery.create())).extracting("ruleKey").containsOnly(rule1.getKey(), rule3.getKey(), rule4.getKey());
+    assertThat(underTest.findAll()).extracting("ruleKey").containsOnly(rule1.getKey().rule(), rule3.getKey().rule(), rule4.getKey().rule());
   }
 
   @Test
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java
new file mode 100644 (file)
index 0000000..26a4c64
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.plugins;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.sonar.api.Startable;
+import org.sonar.api.utils.Preconditions;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.plugin.PluginDto;
+
+import static java.util.function.Function.identity;
+
+public class DetectPluginChange implements Startable {
+  private static final Logger LOG = Loggers.get(DetectPluginChange.class);
+
+  private final ServerPluginRepository serverPluginRepository;
+  private final DbClient dbClient;
+  private Boolean changesDetected = null;
+
+  public DetectPluginChange(ServerPluginRepository serverPluginRepository, DbClient dbClient) {
+    this.serverPluginRepository = serverPluginRepository;
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public void start() {
+    Preconditions.checkState(changesDetected == null, "Can only call #start() once");
+    Profiler profiler = Profiler.create(LOG).startInfo("Detect plugin changes");
+    changesDetected = anyChange();
+    if (changesDetected) {
+      LOG.info("Plugin changes detected");
+    } else {
+      LOG.debug("No plugin changes detected");
+    }
+    profiler.stopDebug();
+  }
+
+  /**
+   * @throws NullPointerException if {@link #start} hasn't been called
+   */
+  public boolean anyPluginChanged() {
+    return changesDetected;
+  }
+
+  private boolean anyChange() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Map<String, PluginDto> dbPluginsByKey = dbClient.pluginDao().selectAll(dbSession).stream()
+        .collect(Collectors.toMap(PluginDto::getKee, identity()));
+      Map<String, ServerPlugin> filePluginsByKey = serverPluginRepository.getPlugins().stream()
+        .collect(Collectors.toMap(p -> p.getPluginInfo().getKey(), p -> p));
+
+      if (!dbPluginsByKey.keySet().equals(filePluginsByKey.keySet())) {
+        return true;
+      }
+
+      // TODO ideally we would detect plugins that were removed and added again, because we don't delete removed plugins from the plugins table.
+      for (ServerPlugin installed : filePluginsByKey.values()) {
+        PluginDto dbPlugin = dbPluginsByKey.get(installed.getPluginInfo().getKey());
+        if (changed(dbPlugin, installed)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean changed(PluginDto dbPlugin, ServerPlugin filePlugin) {
+    return !dbPlugin.getFileHash().equals(filePlugin.getJar().getMd5()) || !dbPlugin.getType().equals(toTypeDto(filePlugin.getType()));
+  }
+
+  static PluginDto.Type toTypeDto(PluginType type) {
+    switch (type) {
+      case EXTERNAL:
+        return PluginDto.Type.EXTERNAL;
+      case BUNDLED:
+        return PluginDto.Type.BUNDLED;
+      default:
+        throw new IllegalStateException("Unknown type: " + type);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java
new file mode 100644 (file)
index 0000000..7e51da0
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.rule;
+
+import com.google.common.collect.Ordering;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.markdown.Markdown;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableMap;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+/**
+ * A {@link RuleFinder} implementation that retrieves all rule definitions and their parameter when instantiated, cache
+ * them in memory and provide implementation of {@link RuleFinder}'s method which only read from this data in memory.
+ */
+public class CachingRuleFinder implements ServerRuleFinder {
+
+  private static final Ordering<Map.Entry<RuleDefinitionDto, Rule>> FIND_BY_QUERY_ORDER = Ordering.natural().reverse().onResultOf(entry -> entry.getKey().getUpdatedAt());
+
+  private final Map<RuleKey, RuleDefinitionDto> ruleDtosByKey;
+  private final Map<String, RuleDefinitionDto> ruleDtosByUuid;
+  private final Map<RuleDefinitionDto, Rule> rulesByRuleDefinition;
+  private final Map<RuleKey, Rule> rulesByKey;
+
+  public CachingRuleFinder(DbClient dbClient) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      List<RuleDefinitionDto> dtos = dbClient.ruleDao().selectAllDefinitions(dbSession);
+      this.ruleDtosByKey = dtos.stream().collect(Collectors.toMap(RuleDefinitionDto::getKey, d -> d));
+      this.ruleDtosByUuid = dtos.stream().collect(Collectors.toMap(RuleDefinitionDto::getUuid, d -> d));
+      this.rulesByRuleDefinition = buildRulesByRuleDefinitionDto(dbClient, dbSession, dtos);
+      this.rulesByKey = this.rulesByRuleDefinition.entrySet().stream()
+        .collect(uniqueIndex(entry -> entry.getKey().getKey(), Map.Entry::getValue));
+    }
+  }
+
+  private static Map<RuleDefinitionDto, Rule> buildRulesByRuleDefinitionDto(DbClient dbClient, DbSession dbSession, List<RuleDefinitionDto> dtos) {
+    Map<String, List<RuleParamDto>> ruleParamsByRuleUuid = retrieveRuleParameters(dbClient, dbSession);
+    Map<RuleDefinitionDto, Rule> rulesByDefinition = new HashMap<>(dtos.size());
+    for (RuleDefinitionDto definition : dtos) {
+      rulesByDefinition.put(definition, toRule(definition, ruleParamsByRuleUuid.getOrDefault(definition.getUuid(), emptyList())));
+    }
+    return unmodifiableMap(rulesByDefinition);
+  }
+
+  private static Map<String, List<RuleParamDto>> retrieveRuleParameters(DbClient dbClient, DbSession dbSession) {
+    return dbClient.ruleDao().selectAllRuleParams(dbSession).stream()
+      .collect(Collectors.groupingBy(RuleParamDto::getRuleUuid));
+  }
+
+  @Override
+  @CheckForNull
+  public Rule findByKey(@Nullable String repositoryKey, @Nullable String key) {
+    if (repositoryKey == null || key == null) {
+      return null;
+    }
+    return findByKey(RuleKey.of(repositoryKey, key));
+  }
+
+  @Override
+  @CheckForNull
+  public Rule findByKey(RuleKey key) {
+    return rulesByKey.get(key);
+  }
+
+  @Override
+  @CheckForNull
+  public Rule find(@Nullable RuleQuery query) {
+    if (query == null) {
+      return null;
+    }
+
+    return rulesByRuleDefinition.entrySet().stream()
+      .filter(entry -> matchQuery(entry.getKey(), query))
+      .sorted(FIND_BY_QUERY_ORDER)
+      .map(Map.Entry::getValue)
+      .findFirst()
+      .orElse(null);
+  }
+
+  @Override
+  public Collection<Rule> findAll(@Nullable RuleQuery query) {
+    if (query == null) {
+      return Collections.emptyList();
+    }
+    return rulesByRuleDefinition.entrySet().stream()
+      .filter(entry -> matchQuery(entry.getKey(), query))
+      .sorted(FIND_BY_QUERY_ORDER)
+      .map(Map.Entry::getValue)
+      .collect(MoreCollectors.toList());
+  }
+
+  private static boolean matchQuery(RuleDefinitionDto ruleDefinition, RuleQuery ruleQuery) {
+    if (RuleStatus.REMOVED.equals(ruleDefinition.getStatus())) {
+      return false;
+    }
+    String repositoryKey = ruleQuery.getRepositoryKey();
+    if (ruleQuery.getRepositoryKey() != null && !repositoryKey.equals(ruleDefinition.getRepositoryKey())) {
+      return false;
+    }
+    String key = ruleQuery.getKey();
+    if (key != null && !key.equals(ruleDefinition.getRuleKey())) {
+      return false;
+    }
+    String configKey = ruleQuery.getConfigKey();
+    return configKey == null || configKey.equals(ruleDefinition.getConfigKey());
+  }
+
+  private static Rule toRule(RuleDefinitionDto ruleDefinition, List<RuleParamDto> params) {
+    String severity = ruleDefinition.getSeverityString();
+    String description = ruleDefinition.getDescription();
+    RuleDto.Format descriptionFormat = ruleDefinition.getDescriptionFormat();
+
+    Rule apiRule = new Rule();
+    apiRule
+      .setName(ruleDefinition.getName())
+      .setLanguage(ruleDefinition.getLanguage())
+      .setKey(ruleDefinition.getRuleKey())
+      .setConfigKey(ruleDefinition.getConfigKey())
+      .setIsTemplate(ruleDefinition.isTemplate())
+      .setCreatedAt(new Date(ruleDefinition.getCreatedAt()))
+      .setUpdatedAt(new Date(ruleDefinition.getUpdatedAt()))
+      .setRepositoryKey(ruleDefinition.getRepositoryKey())
+      .setSeverity(severity != null ? RulePriority.valueOf(severity) : null)
+      .setStatus(ruleDefinition.getStatus().name())
+      .setSystemTags(ruleDefinition.getSystemTags().toArray(new String[ruleDefinition.getSystemTags().size()]))
+      .setTags(new String[0]);
+    if (description != null && descriptionFormat != null) {
+      if (RuleDto.Format.HTML.equals(descriptionFormat)) {
+        apiRule.setDescription(description);
+      } else {
+        apiRule.setDescription(Markdown.convertToHtml(description));
+      }
+    }
+
+    List<org.sonar.api.rules.RuleParam> apiParams = new ArrayList<>();
+    for (RuleParamDto param : params) {
+      apiParams.add(new org.sonar.api.rules.RuleParam(apiRule, param.getName(), param.getDescription(), param.getType())
+        .setDefaultValue(param.getDefaultValue()));
+    }
+    apiRule.setParams(apiParams);
+
+    return apiRule;
+  }
+
+  @Override
+  public Optional<RuleDefinitionDto> findDtoByKey(RuleKey key) {
+    return Optional.ofNullable(this.ruleDtosByKey.get(key));
+  }
+
+  @Override
+  public Optional<RuleDefinitionDto> findDtoByUuid(String uuid) {
+    return Optional.ofNullable(this.ruleDtosByUuid.get(uuid));
+  }
+
+  @Override
+  public Collection<RuleDefinitionDto> findAll() {
+    return ruleDtosByUuid.values();
+  }
+
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java
new file mode 100644 (file)
index 0000000..3b4b759
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.rule;
+
+/**
+ * {@link ServerRuleFinder} implementation that supports caching used by the Web Server.
+ * <p>
+ *   Caching is enabled right after loading of rules is done (see {@link RegisterRules}) and disabled
+ *   once all startup tasks are done (see {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup}).
+ * </p>
+ */
+public interface WebServerRuleFinder extends ServerRuleFinder {
+  /**
+   * Enable caching.
+   */
+  void startCaching();
+
+  /**
+   * Disable caching.
+   */
+  void stopCaching();
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java
new file mode 100644 (file)
index 0000000..4c044f0
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.rule;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Collection;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.db.DbClient;
+import org.sonar.db.rule.RuleDefinitionDto;
+
+public class WebServerRuleFinderImpl implements WebServerRuleFinder {
+  private final DbClient dbClient;
+  private final ServerRuleFinder defaultFinder;
+  @VisibleForTesting
+  ServerRuleFinder delegate;
+
+  public WebServerRuleFinderImpl(DbClient dbClient) {
+    this.dbClient = dbClient;
+    this.defaultFinder = new DefaultRuleFinder(dbClient);
+    this.delegate = this.defaultFinder;
+  }
+
+  @Override
+  public void startCaching() {
+    this.delegate = new CachingRuleFinder(dbClient);
+  }
+
+  @Override
+  public void stopCaching() {
+    this.delegate = this.defaultFinder;
+  }
+
+  @Override
+  @CheckForNull
+  public Rule findByKey(String repositoryKey, String key) {
+    return delegate.findByKey(repositoryKey, key);
+  }
+
+  @Override
+  @CheckForNull
+  public Rule findByKey(RuleKey key) {
+    return delegate.findByKey(key);
+  }
+
+  @Override
+  @CheckForNull
+  public Rule find(RuleQuery query) {
+    return delegate.find(query);
+  }
+
+  @Override
+  public Collection<Rule> findAll(RuleQuery query) {
+    return delegate.findAll(query);
+  }
+
+  @Override
+  public Optional<RuleDefinitionDto> findDtoByKey(RuleKey key) {
+    return delegate.findDtoByKey(key);
+  }
+
+  @Override
+  public Optional<RuleDefinitionDto> findDtoByUuid(String uuid) {
+    return delegate.findDtoByUuid(uuid);
+  }
+
+  @Override
+  public Collection<RuleDefinitionDto> findAll() {
+    return delegate.findAll();
+  }
+
+}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java
new file mode 100644 (file)
index 0000000..5fcd031
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.plugins;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.Plugin;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.db.DbTester;
+import org.sonar.db.plugin.PluginDto;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DetectPluginChangeTest {
+  @Rule
+  public DbTester dbTester = DbTester.create();
+
+  private final ServerPluginRepository pluginRepository = new ServerPluginRepository();
+  private final DetectPluginChange detectPluginChange = new DetectPluginChange(pluginRepository, dbTester.getDbClient());
+
+  @Test
+  public void detect_changed_plugin() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin1", "hash2", PluginType.BUNDLED);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_changed_type() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin1", "hash1", PluginType.EXTERNAL);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_new_plugin() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin2", "hash1", PluginType.BUNDLED);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Ignore
+  @Test
+  public void detect_missing_plugin() {
+    // TODO
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_no_changes_bundled() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
+    addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL);
+    addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isFalse();
+  }
+
+  @Test
+  public void fail_if_start_twice() {
+    detectPluginChange.start();
+    assertThrows(IllegalStateException.class, detectPluginChange::start);
+  }
+
+  @Test
+  public void fail_if_not_started() {
+    assertThrows(NullPointerException.class, detectPluginChange::anyPluginChanged);
+  }
+
+  private void addPluginToDb(String key, String hash, PluginDto.Type type) {
+    dbTester.pluginDbTester().insertPlugin(p -> p.setKee(key).setFileHash(hash).setType(type));
+  }
+
+  private void addPluginToFs(String key, String hash, PluginType type) {
+    PluginInfo pluginInfo = new PluginInfo(key);
+    Plugin plugin = mock(Plugin.class);
+    FileAndMd5 fileAndMd5 = mock(FileAndMd5.class);
+    when(fileAndMd5.getMd5()).thenReturn(hash);
+    ServerPlugin serverPlugin = new ServerPlugin(pluginInfo, type, plugin, fileAndMd5, null);
+    pluginRepository.addPlugin(serverPlugin);
+  }
+
+}
index b7a9905904adc9d2687711796e6a48f36ca2fea0..b1e11e52d673410ee62f470cdbd60f42315f6051 100644 (file)
@@ -35,7 +35,7 @@ import static org.sonar.server.property.InternalProperties.DEFAULT_ADMIN_CREDENT
 /**
  * Detect usage of an active admin account with default credential in order to ask this account to reset its password during authentication.
  */
-public class DefaultAdminCredentialsVerifierImpl implements Startable, DefaultAdminCredentialsVerifier {
+public class DefaultAdminCredentialsVerifierImpl implements DefaultAdminCredentialsVerifier {
 
   private static final Logger LOGGER = Loggers.get(STARTUP_LOGGER_NAME);
 
@@ -49,8 +49,7 @@ public class DefaultAdminCredentialsVerifierImpl implements Startable, DefaultAd
     this.notificationManager = notificationManager;
   }
 
-  @Override
-  public void start() {
+  public void runAtStart() {
     try (DbSession session = dbClient.openSession(false)) {
       UserDto admin = getAdminUser(session);
       if (admin == null || !isDefaultCredentialUser(session, admin)) {
@@ -63,6 +62,7 @@ public class DefaultAdminCredentialsVerifierImpl implements Startable, DefaultAd
     }
   }
 
+  @Override
   public boolean hasDefaultCredentialUser() {
     try (DbSession session = dbClient.openSession(false)) {
       UserDto admin = getAdminUser(session);
@@ -105,9 +105,4 @@ public class DefaultAdminCredentialsVerifierImpl implements Startable, DefaultAd
     notificationManager.scheduleForSending(new DefaultAdminCredentialsVerifierNotification());
     dbClient.internalPropertiesDao().save(session, DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL, Boolean.TRUE.toString());
   }
-
-  @Override
-  public void stop() {
-    // Nothing to do
-  }
 }
index 8eab984a5fd8a88812c24a33715f77f05dacd4e9..b238609da7993d6514a0e97cd33172cdeb261f68 100644 (file)
 package org.sonar.server.qualityprofile;
 
 import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -51,22 +48,27 @@ import org.sonar.db.qualityprofile.RulesProfileDto;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleParamDto;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.ServerRuleFinder;
 import org.sonar.server.util.TypeValidations;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.emptySet;
 import static java.util.Objects.requireNonNull;
 
 public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
   private final DbClient dbClient;
+  private final ServerRuleFinder ruleFinder;
   private final System2 system2;
   private final UuidFactory uuidFactory;
   private final TypeValidations typeValidations;
   private final ActiveRuleIndexer activeRuleIndexer;
   private RuleRepository ruleRepository;
 
-  public BuiltInQProfileInsertImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory, TypeValidations typeValidations, ActiveRuleIndexer activeRuleIndexer) {
+  public BuiltInQProfileInsertImpl(DbClient dbClient, ServerRuleFinder ruleFinder, System2 system2, UuidFactory uuidFactory,
+    TypeValidations typeValidations, ActiveRuleIndexer activeRuleIndexer) {
     this.dbClient = dbClient;
+    this.ruleFinder = ruleFinder;
     this.system2 = system2;
     this.uuidFactory = uuidFactory;
     this.typeValidations = typeValidations;
@@ -80,9 +82,8 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     Date now = new Date(system2.now());
     RulesProfileDto ruleProfile = insertRulesProfile(dbSession, builtInQProfile, now);
 
-    List<ActiveRuleChange> changes = builtInQProfile.getActiveRules()
-      .stream()
-      .map(activeRule -> insertActiveRule(dbSession, ruleProfile, activeRule, now.getTime()))
+    List<ActiveRuleChange> changes = builtInQProfile.getActiveRules().stream()
+      .map(activeRule -> insertActiveRule(dbSession, batchDbSession, ruleProfile, activeRule, now.getTime()))
       .collect(MoreCollectors.toList());
 
     changes.forEach(change -> dbClient.qProfileChangeDao().insert(batchDbSession, change.toDto(null)));
@@ -95,6 +96,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     activeRuleIndexer.commitAndIndex(dbSession, changes);
   }
 
+
   private void createDefaultAndOrgQProfiles(DbSession dbSession, DbSession batchDbSession, BuiltInQProfile builtIn, RulesProfileDto rulesProfileDto) {
     Optional<String> qProfileUuid = dbClient.defaultQProfileDao().selectDefaultQProfileUuid(dbSession, builtIn.getLanguage());
 
@@ -102,7 +104,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
       .setRulesProfileUuid(rulesProfileDto.getUuid())
       .setUuid(uuidFactory.create());
 
-    if (builtIn.isDefault() && !qProfileUuid.isPresent()) {
+    if (builtIn.isDefault() && qProfileUuid.isEmpty()) {
       DefaultQProfileDto defaultQProfileDto = new DefaultQProfileDto()
         .setQProfileUuid(dto.getUuid())
         .setLanguage(builtIn.getLanguage());
@@ -114,7 +116,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
 
   private void initRuleRepository(DbSession dbSession) {
     if (ruleRepository == null) {
-      ruleRepository = new RuleRepository(dbClient, dbSession);
+      ruleRepository = new RuleRepository(dbClient, dbSession, ruleFinder);
     }
   }
 
@@ -129,7 +131,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     return dto;
   }
 
-  private ActiveRuleChange insertActiveRule(DbSession dbSession, RulesProfileDto rulesProfileDto, BuiltInQProfile.ActiveRule activeRule, long now) {
+  private ActiveRuleChange insertActiveRule(DbSession dbSession, DbSession batchDbSession, RulesProfileDto rulesProfileDto, BuiltInQProfile.ActiveRule activeRule, long now) {
     RuleKey ruleKey = activeRule.getRuleKey();
     RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey)
       .orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey));
@@ -141,7 +143,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     dto.setSeverity(firstNonNull(activeRule.getSeverity(), ruleDefinitionDto.getSeverityString()));
     dto.setUpdatedAt(now);
     dto.setCreatedAt(now);
-    dbClient.activeRuleDao().insert(dbSession, dto);
+    dbClient.activeRuleDao().insert(batchDbSession, dto);
 
     List<ActiveRuleParamDto> paramDtos = insertActiveRuleParams(dbSession, activeRule, dto);
 
@@ -151,13 +153,10 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
     return change;
   }
 
-  private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, BuiltInQProfile.ActiveRule activeRule,
-    ActiveRuleDto activeRuleDto) {
-    Map<String, String> valuesByParamKey = activeRule.getParams()
-      .stream()
+  private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, BuiltInQProfile.ActiveRule activeRule, ActiveRuleDto activeRuleDto) {
+    Map<String, String> valuesByParamKey = activeRule.getParams().stream()
       .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
-    List<ActiveRuleParamDto> rules = ruleRepository.getRuleParams(activeRule.getRuleKey())
-      .stream()
+    List<ActiveRuleParamDto> rules = ruleRepository.getRuleParams(activeRule.getRuleKey()).stream()
       .map(param -> createParamDto(param, Optional.ofNullable(valuesByParamKey.get(param.getName())).orElse(param.getDefaultValue())))
       .filter(Objects::nonNull)
       .collect(Collectors.toList());
@@ -188,35 +187,29 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
   }
 
   private static class RuleRepository {
-    private final Map<RuleKey, RuleDefinitionDto> definitions;
     private final Map<RuleKey, Set<RuleParamDto>> params;
+    private final ServerRuleFinder ruleFinder;
+
+    private RuleRepository(DbClient dbClient, DbSession session, ServerRuleFinder ruleFinder) {
+      this.ruleFinder = ruleFinder;
+      this.params = new HashMap<>();
+
+      for (RuleParamDto ruleParam : dbClient.ruleDao().selectAllRuleParams(session)) {
+        Optional<RuleKey> ruleKey = ruleFinder.findDtoByUuid(ruleParam.getRuleUuid())
+          .map(r -> RuleKey.of(r.getRepositoryKey(), r.getRuleKey()));
 
-    private RuleRepository(DbClient dbClient, DbSession session) {
-      this.definitions = dbClient.ruleDao().selectAllDefinitions(session)
-        .stream()
-        .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity()));
-      Map<String, RuleKey> ruleUuidsByKey = definitions.values()
-        .stream()
-        .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getUuid, RuleDefinitionDto::getKey));
-      this.params = new HashMap<>(ruleUuidsByKey.size());
-      dbClient.ruleDao().selectRuleParamsByRuleKeys(session, definitions.keySet())
-        .forEach(ruleParam -> params.compute(
-          ruleUuidsByKey.get(ruleParam.getRuleUuid()),
-          (key, value) -> {
-            if (value == null) {
-              return ImmutableSet.of(ruleParam);
-            }
-            return ImmutableSet.copyOf(Sets.union(value, Collections.singleton(ruleParam)));
-          }));
+        if (ruleKey.isPresent()) {
+          params.computeIfAbsent(ruleKey.get(), r -> new HashSet<>()).add(ruleParam);
+        }
+      }
     }
 
     private Optional<RuleDefinitionDto> getDefinition(RuleKey ruleKey) {
-      return Optional.ofNullable(definitions.get(requireNonNull(ruleKey, "RuleKey can't be null")));
+      return ruleFinder.findDtoByKey(requireNonNull(ruleKey, "RuleKey can't be null"));
     }
 
     private Set<RuleParamDto> getRuleParams(RuleKey ruleKey) {
-      Set<RuleParamDto> res = params.get(requireNonNull(ruleKey, "RuleKey can't be null"));
-      return res == null ? Collections.emptySet() : res;
+      return params.getOrDefault(requireNonNull(ruleKey, "RuleKey can't be null"), emptySet());
     }
   }
 }
index 21f35bb0f80f395aa03c062b7cab7beb1021e249..7f00a988113ab16d9e076dd7cb9fe48120671776 100644 (file)
@@ -46,6 +46,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.rule.ServerRuleFinder;
 
 import static com.google.common.base.Preconditions.checkState;
 
@@ -54,6 +55,7 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
   private static final String DEFAULT_PROFILE_NAME = "Sonar way";
 
   private final DbClient dbClient;
+  private final ServerRuleFinder ruleFinder;
   private final Languages languages;
   private final List<BuiltInQualityProfilesDefinition> definitions;
   private List<BuiltInQProfile> qProfiles;
@@ -61,12 +63,13 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
   /**
    * Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all
    */
-  public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages) {
-    this(dbClient, languages, new BuiltInQualityProfilesDefinition[0]);
+  public BuiltInQProfileRepositoryImpl(DbClient dbClient, ServerRuleFinder ruleFinder, Languages languages) {
+    this(dbClient, ruleFinder, languages, new BuiltInQualityProfilesDefinition[0]);
   }
 
-  public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages, BuiltInQualityProfilesDefinition... definitions) {
+  public BuiltInQProfileRepositoryImpl(DbClient dbClient, ServerRuleFinder ruleFinder, Languages languages, BuiltInQualityProfilesDefinition... definitions) {
     this.dbClient = dbClient;
+    this.ruleFinder = ruleFinder;
     this.languages = languages;
     this.definitions = ImmutableList.copyOf(definitions);
   }
@@ -141,7 +144,7 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
 
   private Map<RuleKey, RuleDefinitionDto> loadRuleDefinitionsByRuleKey() {
     try (DbSession dbSession = dbClient.openSession(false)) {
-      List<RuleDefinitionDto> ruleDefinitions = dbClient.ruleDao().selectAllDefinitions(dbSession);
+      Collection<RuleDefinitionDto> ruleDefinitions = ruleFinder.findAll();
       Multimap<String, DeprecatedRuleKeyDto> deprecatedRuleKeysByRuleId = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
         .collect(MoreCollectors.index(DeprecatedRuleKeyDto::getRuleUuid));
       Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = new HashMap<>();
index 7b41c2424683e8ae8dcbc9e1be7ad5fe08f02578..5b80f1f64289fd15297b9e80d3c3df4a99fa8071 100644 (file)
@@ -52,11 +52,6 @@ public class DefaultAdminCredentialsVerifierImplTest {
 
   private final DefaultAdminCredentialsVerifierImpl underTest = new DefaultAdminCredentialsVerifierImpl(db.getDbClient(), localAuthentication, notificationManager);
 
-  @After
-  public void after() {
-    underTest.stop();
-  }
-
   @Test
   public void correctly_detect_if_admin_account_is_used_with_default_credential() {
     UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
@@ -77,7 +72,7 @@ public class DefaultAdminCredentialsVerifierImplTest {
     UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
     changePassword(admin, "admin");
 
-    underTest.start();
+    underTest.runAtStart();
 
     assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isTrue();
     assertThat(logTester.logs(LoggerLevel.WARN)).contains("Default Administrator credentials are still being used. Make sure to change the password or deactivate the account.");
@@ -92,7 +87,7 @@ public class DefaultAdminCredentialsVerifierImplTest {
     db.getDbClient().internalPropertiesDao().save(db.getSession(), DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL, "true");
     db.commit();
 
-    underTest.start();
+    underTest.runAtStart();
 
     verifyNoMoreInteractions(notificationManager);
   }
@@ -102,7 +97,7 @@ public class DefaultAdminCredentialsVerifierImplTest {
     UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
     changePassword(admin, "something_else");
 
-    underTest.start();
+    underTest.runAtStart();
 
     assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
     assertThat(logTester.logs()).isEmpty();
@@ -114,7 +109,7 @@ public class DefaultAdminCredentialsVerifierImplTest {
     UserDto otherUser = db.users().insertUser();
     changePassword(otherUser, "admin");
 
-    underTest.start();
+    underTest.runAtStart();
 
     assertThat(db.users().selectUserByLogin(otherUser.getLogin()).get().isResetPassword()).isFalse();
     assertThat(logTester.logs()).isEmpty();
@@ -126,7 +121,7 @@ public class DefaultAdminCredentialsVerifierImplTest {
     UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN).setActive(false));
     changePassword(admin, "admin");
 
-    underTest.start();
+    underTest.runAtStart();
 
     assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
     assertThat(logTester.logs()).isEmpty();
index c2769d97a851a7eb51d93cf254cdcc7cfc2bc418..8d1a7f617b42c1677172005c46759f85c287adba 100644 (file)
  */
 package org.sonar.server.qualityprofile;
 
+import java.util.Arrays;
 import java.util.List;
 import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.PropertyType;
 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
@@ -40,10 +42,14 @@ import org.sonar.db.qualityprofile.QProfileChangeDto;
 import org.sonar.db.qualityprofile.QProfileChangeQuery;
 import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.rule.ServerRuleFinder;
+import org.sonar.server.util.StringTypeValidation;
 import org.sonar.server.util.TypeValidations;
 
-import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 
@@ -56,13 +62,14 @@ public class BuiltInQProfileInsertImplTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private System2 system2 = new AlwaysIncreasingSystem2();
-  private UuidFactory uuidFactory = new SequenceUuidFactory();
-  private TypeValidations typeValidations = new TypeValidations(emptyList());
-  private DbSession dbSession = db.getSession();
-  private DbSession batchDbSession = db.getDbClient().openSession(true);
-  private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
-  private BuiltInQProfileInsertImpl underTest = new BuiltInQProfileInsertImpl(db.getDbClient(), system2, uuidFactory, typeValidations, activeRuleIndexer);
+  private final System2 system2 = new AlwaysIncreasingSystem2();
+  private final UuidFactory uuidFactory = new SequenceUuidFactory();
+  private final TypeValidations typeValidations = new TypeValidations(singletonList(new StringTypeValidation()));
+  private final DbSession dbSession = db.getSession();
+  private final DbSession batchDbSession = db.getDbClient().openSession(true);
+  private final ServerRuleFinder ruleFinder = new DefaultRuleFinder(db.getDbClient());
+  private final ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
+  private final BuiltInQProfileInsertImpl underTest = new BuiltInQProfileInsertImpl(db.getDbClient(), ruleFinder, system2, uuidFactory, typeValidations, activeRuleIndexer);
 
   @After
   public void tearDown() {
@@ -85,15 +92,63 @@ public class BuiltInQProfileInsertImplTest {
     call(builtIn);
 
     verifyTableSize("rules_profiles", 1);
+    verifyTableSize("org_qprofiles", 1);
     verifyTableSize("active_rules", 2);
     verifyTableSize("active_rule_parameters", 0);
     verifyTableSize("qprofile_changes", 2);
+    verifyTableSize("default_qprofiles", 0);
+
 
     QProfileDto profile = verifyProfileInDb(builtIn);
     verifyActiveRuleInDb(profile, rule1, Severity.CRITICAL);
     verifyActiveRuleInDb(profile, rule2, Severity.MAJOR);
   }
 
+  @Test
+  public void insert_default_qp() {
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    context.createBuiltInQualityProfile("the name", "xoo")
+      .setDefault(true)
+      .done();
+
+    BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
+    call(builtIn);
+
+    verifyTableSize("rules_profiles", 1);
+    verifyTableSize("org_qprofiles", 1);
+    verifyTableSize("active_rules", 0);
+    verifyTableSize("active_rule_parameters", 0);
+    verifyTableSize("qprofile_changes", 0);
+    verifyTableSize("default_qprofiles", 1);
+
+    verifyProfileInDb(builtIn);
+  }
+
+  @Test
+  public void insert_active_rules_with_params() {
+    RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
+    RuleParamDto param1 = db.rules().insertRuleParam(rule1, p -> p.setType(PropertyType.STRING.name()));
+    RuleParamDto param2 = db.rules().insertRuleParam(rule1, p -> p.setType(PropertyType.STRING.name()));
+
+    BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+    NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo");
+
+    newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+    newQp.done();
+
+    BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"), rule1);
+    call(builtIn);
+
+    verifyTableSize("rules_profiles", 1);
+    verifyTableSize("org_qprofiles", 1);
+    verifyTableSize("active_rules", 1);
+    verifyTableSize("active_rule_parameters", 2);
+    verifyTableSize("qprofile_changes", 1);
+
+    QProfileDto profile = verifyProfileInDb(builtIn);
+    verifyActiveRuleInDb(profile, rule1, Severity.CRITICAL, param1, param2);
+  }
+
   @Test
   public void flag_profile_as_default_if_declared_as_default_by_api() {
     BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
@@ -123,6 +178,7 @@ public class BuiltInQProfileInsertImplTest {
 
     QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, "xoo");
     assertThat(defaultProfile.getKee()).isEqualTo(currentDefault.getKee());
+    verifyTableSize("rules_profiles", 2);
   }
 
   @Test
@@ -138,10 +194,9 @@ public class BuiltInQProfileInsertImplTest {
     assertThat(defaultProfile).isNull();
   }
 
-  // TODO test params
   // TODO test lot of active_rules, params, orgas
 
-  private void verifyActiveRuleInDb(QProfileDto profile, RuleDefinitionDto rule, String expectedSeverity) {
+  private void verifyActiveRuleInDb(QProfileDto profile, RuleDefinitionDto rule, String expectedSeverity, RuleParamDto... paramDtos) {
     ActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByKey(dbSession, ActiveRuleKey.of(profile, rule.getKey())).get();
     assertThat(activeRule.getUuid()).isNotNull();
     assertThat(activeRule.getInheritance()).isNull();
@@ -153,7 +208,7 @@ public class BuiltInQProfileInsertImplTest {
     assertThat(activeRule.getUpdatedAt()).isPositive();
 
     List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(dbSession, activeRule.getUuid());
-    assertThat(params).isEmpty();
+    assertThat(params).extracting(ActiveRuleParamDto::getKey).containsOnly(Arrays.stream(paramDtos).map(RuleParamDto::getName).toArray(String[]::new));
 
     QProfileChangeQuery changeQuery = new QProfileChangeQuery(profile.getKee());
     QProfileChangeDto change = db.getDbClient().qProfileChangeDao().selectByQuery(dbSession, changeQuery).stream()
index 850a5dcdd1e98020ba9c4d2eb2f92a2e4fc3f431..e5c1b02dc4bbd8c51bce093df7a6f02a9b15e3b9 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.qualityprofile;
 
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Rule;
 import org.junit.Test;
@@ -32,6 +31,8 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.rule.ServerRuleFinder;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
@@ -51,16 +52,16 @@ public class BuiltInQProfileRepositoryImplTest {
   @Rule
   public DbTester db = DbTester.create();
 
-  private DbClient dbClient = db.getDbClient();
+  private final DbClient dbClient = db.getDbClient();
+  private final ServerRuleFinder ruleFinder = new DefaultRuleFinder(dbClient);
 
   @Test
   public void create_qprofile_with_rule() {
     RuleDefinitionDto rule1 = db.rules().insert();
     RuleDefinitionDto rule2 = db.rules().insert();
     db.rules().insert();
-    List<DummyProfileDefinition> definitions = singletonList(new DummyProfileDefinition("foo", "foo", false,
-      asList(rule1.getKey(), rule2.getKey())));
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), definitions.toArray(new BuiltInQualityProfilesDefinition[0]));
+    DummyProfileDefinition definition = new DummyProfileDefinition("foo", "foo", false, asList(rule1.getKey(), rule2.getKey()));
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definition);
 
     underTest.initialize();
 
@@ -76,7 +77,8 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void make_single_profile_of_a_language_default_even_if_not_flagged_as_so() {
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", false));
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
+      new DummyProfileDefinition("foo", "foo1", false));
 
     underTest.initialize();
 
@@ -87,7 +89,8 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void make_single_profile_of_a_language_default_even_if_flagged_as_so() {
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", true));
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
+      new DummyProfileDefinition("foo", "foo1", true));
 
     underTest.initialize();
 
@@ -98,11 +101,10 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void make_first_profile_of_a_language_default_when_none_flagged_as_so() {
-    List<DummyProfileDefinition> definitions = new ArrayList<>(
-      asList(new DummyProfileDefinition("foo", "foo1", false), new DummyProfileDefinition("foo", "foo2", false)));
-    String firstName = definitions.get(0).getName();
-    String secondName = definitions.get(1).getName();
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), definitions.toArray(new BuiltInQualityProfilesDefinition[0]));
+    DummyProfileDefinition[] definitions = new DummyProfileDefinition[] {new DummyProfileDefinition("foo", "foo1", false), new DummyProfileDefinition("foo", "foo2", false)};
+    String firstName = definitions[0].getName();
+    String secondName = definitions[1].getName();
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definitions);
 
     underTest.initialize();
 
@@ -114,7 +116,7 @@ public class BuiltInQProfileRepositoryImplTest {
   @Test
   public void create_profile_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() {
     BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
-      dbClient, new Languages(FOO_LANGUAGE),
+      dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
       new DummyProfileDefinition("foo", "doh", false), new DummyProfileDefinition("foo", "boo", false),
       new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", false));
 
@@ -130,7 +132,7 @@ public class BuiltInQProfileRepositoryImplTest {
   @Test
   public void do_not_create_Sonar_Way_as_default_if_other_profile_is_defined_as_default() {
     BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
-      dbClient, new Languages(FOO_LANGUAGE),
+      dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
       new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", true));
 
     underTest.initialize();
@@ -146,7 +148,7 @@ public class BuiltInQProfileRepositoryImplTest {
   public void match_Sonar_Way_default_with_case_sensitivity() {
     String sonarWayInOtherCase = SONAR_WAY_QP_NAME.toUpperCase();
     BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
-      dbClient, new Languages(FOO_LANGUAGE),
+      dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
       new DummyProfileDefinition("foo", "goo", false), new DummyProfileDefinition("foo", sonarWayInOtherCase, false));
 
     underTest.initialize();
@@ -160,7 +162,8 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void create_no_BuiltInQProfile_when_all_definitions_apply_to_non_defined_languages() {
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages(), new DummyProfileDefinition("foo", "P1", false));
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages(),
+      new DummyProfileDefinition("foo", "P1", false));
 
     underTest.initialize();
 
@@ -172,9 +175,9 @@ public class BuiltInQProfileRepositoryImplTest {
     RuleDefinitionDto rule1 = db.rules().insert();
     db.rules().insertDeprecatedKey(d -> d.setRuleUuid(rule1.getUuid()).setOldRepositoryKey("oldRepo").setOldRuleKey("oldKey"));
     RuleDefinitionDto rule2 = db.rules().insert();
-    List<DummyProfileDefinition> definitions = singletonList(new DummyProfileDefinition("foo", "foo", false,
-      asList(RuleKey.of("oldRepo", "oldKey"), rule2.getKey())));
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), definitions.toArray(new BuiltInQualityProfilesDefinition[0]));
+    DummyProfileDefinition definition = new DummyProfileDefinition("foo", "foo", false,
+      asList(RuleKey.of("oldRepo", "oldKey"), rule2.getKey()));
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definition);
 
     underTest.initialize();
 
@@ -190,8 +193,8 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void fail_with_ISE_when_rule_does_not_exist() {
-    List<DummyProfileDefinition> definitions = singletonList(new DummyProfileDefinition("foo", "foo", false, singletonList(EXTERNAL_XOO)));
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), definitions.toArray(new BuiltInQualityProfilesDefinition[0]));
+    DummyProfileDefinition[] definitions = new DummyProfileDefinition[] {new DummyProfileDefinition("foo", "foo", false, singletonList(EXTERNAL_XOO))};
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definitions);
 
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage(String.format("Rule with key '%s' not found", EXTERNAL_XOO.toString()));
@@ -201,9 +204,8 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void fail_with_ISE_when_two_profiles_with_different_name_are_default_for_the_same_language() {
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE),
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
       new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo2", true));
-
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage("Several Quality profiles are flagged as default for the language foo: [foo1, foo2]");
 
@@ -212,7 +214,7 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void get_throws_ISE_if_called_before_initialize() {
-    BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages());
+    BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages());
 
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage("initialize must be called first");
@@ -222,7 +224,7 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void initialize_throws_ISE_if_called_twice() {
-    BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages());
+    BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages());
     underTest.initialize();
 
     expectedException.expect(IllegalStateException.class);
@@ -233,7 +235,7 @@ public class BuiltInQProfileRepositoryImplTest {
 
   @Test
   public void initialize_throws_ISE_if_language_has_no_builtin_qp() {
-    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages(FOO_LANGUAGE));
+    BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages(FOO_LANGUAGE));
 
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage("The following languages have no built-in quality profiles: foo");
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/CachingRuleFinder.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/CachingRuleFinder.java
deleted file mode 100644 (file)
index f4fe7a0..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.rule;
-
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Ordering;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.markdown.Markdown;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-
-/**
- * A {@link RuleFinder} implementation that retrieves all rule definitions and their parameter when instantiated, cache
- * them in memory and provide implementation of {@link RuleFinder}'s method which only read from this data in memory.
- */
-public class CachingRuleFinder implements ServerRuleFinder {
-
-  private static final Ordering<Map.Entry<RuleDefinitionDto, Rule>> FIND_BY_QUERY_ORDER = Ordering.natural().reverse().onResultOf(entry -> entry.getKey().getUpdatedAt());
-
-  private final Map<RuleKey, RuleDefinitionDto> ruleDtosByKey;
-  private final Map<RuleDefinitionDto, Rule> rulesByRuleDefinition;
-  private final Map<RuleKey, Rule> rulesByKey;
-
-  public CachingRuleFinder(DbClient dbClient) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      List<RuleDefinitionDto> dtos = dbClient.ruleDao().selectAllDefinitions(dbSession);
-      this.ruleDtosByKey = dtos.stream().collect(Collectors.toMap(RuleDefinitionDto::getKey, d -> d));
-      this.rulesByRuleDefinition = buildRulesByRuleDefinitionDto(dbClient, dbSession, dtos);
-      this.rulesByKey = this.rulesByRuleDefinition.entrySet().stream()
-        .collect(uniqueIndex(entry -> entry.getKey().getKey(), Map.Entry::getValue));
-    }
-  }
-
-  private static Map<RuleDefinitionDto, Rule> buildRulesByRuleDefinitionDto(DbClient dbClient, DbSession dbSession, List<RuleDefinitionDto> dtos) {
-    Set<RuleKey> ruleKeys = dtos.stream().map(RuleDefinitionDto::getKey).collect(toSet(dtos.size()));
-    ListMultimap<String, RuleParamDto> ruleParamsByRuleUuid = retrieveRuleParameters(dbClient, dbSession, ruleKeys);
-    Map<RuleDefinitionDto, Rule> rulesByDefinition = new HashMap<>(dtos.size());
-    for (RuleDefinitionDto definition : dtos) {
-      rulesByDefinition.put(definition, toRule(definition, ruleParamsByRuleUuid.get(definition.getUuid())));
-    }
-    return ImmutableMap.copyOf(rulesByDefinition);
-  }
-
-  private static ImmutableListMultimap<String, RuleParamDto> retrieveRuleParameters(DbClient dbClient, DbSession dbSession, Set<RuleKey> ruleKeys) {
-    if (ruleKeys.isEmpty()) {
-      return ImmutableListMultimap.of();
-    }
-    return dbClient.ruleDao().selectRuleParamsByRuleKeys(dbSession, ruleKeys)
-      .stream()
-      .collect(MoreCollectors.index(RuleParamDto::getRuleUuid));
-  }
-
-  @Override
-  @CheckForNull
-  public Rule findByKey(@Nullable String repositoryKey, @Nullable String key) {
-    if (repositoryKey == null || key == null) {
-      return null;
-    }
-    return findByKey(RuleKey.of(repositoryKey, key));
-  }
-
-  @Override
-  @CheckForNull
-  public Rule findByKey(RuleKey key) {
-    return rulesByKey.get(key);
-  }
-
-  @Override
-  @CheckForNull
-  public Rule find(@Nullable RuleQuery query) {
-    if (query == null) {
-      return null;
-    }
-
-    return rulesByRuleDefinition.entrySet().stream()
-      .filter(entry -> matchQuery(entry.getKey(), query))
-      .sorted(FIND_BY_QUERY_ORDER)
-      .map(Map.Entry::getValue)
-      .findFirst()
-      .orElse(null);
-  }
-
-  @Override
-  public Collection<Rule> findAll(@Nullable RuleQuery query) {
-    if (query == null) {
-      return Collections.emptyList();
-    }
-    return rulesByRuleDefinition.entrySet().stream()
-      .filter(entry -> matchQuery(entry.getKey(), query))
-      .sorted(FIND_BY_QUERY_ORDER)
-      .map(Map.Entry::getValue)
-      .collect(MoreCollectors.toList());
-  }
-
-  private static boolean matchQuery(RuleDefinitionDto ruleDefinition, RuleQuery ruleQuery) {
-    if (RuleStatus.REMOVED.equals(ruleDefinition.getStatus())) {
-      return false;
-    }
-    String repositoryKey = ruleQuery.getRepositoryKey();
-    if (ruleQuery.getRepositoryKey() != null && !repositoryKey.equals(ruleDefinition.getRepositoryKey())) {
-      return false;
-    }
-    String key = ruleQuery.getKey();
-    if (key != null && !key.equals(ruleDefinition.getRuleKey())) {
-      return false;
-    }
-    String configKey = ruleQuery.getConfigKey();
-    return configKey == null || configKey.equals(ruleDefinition.getConfigKey());
-  }
-
-  private static Rule toRule(RuleDefinitionDto ruleDefinition, List<RuleParamDto> params) {
-    String severity = ruleDefinition.getSeverityString();
-    String description = ruleDefinition.getDescription();
-    RuleDto.Format descriptionFormat = ruleDefinition.getDescriptionFormat();
-
-    Rule apiRule = new Rule();
-    apiRule
-      .setName(ruleDefinition.getName())
-      .setLanguage(ruleDefinition.getLanguage())
-      .setKey(ruleDefinition.getRuleKey())
-      .setConfigKey(ruleDefinition.getConfigKey())
-      .setIsTemplate(ruleDefinition.isTemplate())
-      .setCreatedAt(new Date(ruleDefinition.getCreatedAt()))
-      .setUpdatedAt(new Date(ruleDefinition.getUpdatedAt()))
-      .setRepositoryKey(ruleDefinition.getRepositoryKey())
-      .setSeverity(severity != null ? RulePriority.valueOf(severity) : null)
-      .setStatus(ruleDefinition.getStatus().name())
-      .setSystemTags(ruleDefinition.getSystemTags().toArray(new String[ruleDefinition.getSystemTags().size()]))
-      .setTags(new String[0]);
-    if (description != null && descriptionFormat != null) {
-      if (RuleDto.Format.HTML.equals(descriptionFormat)) {
-        apiRule.setDescription(description);
-      } else {
-        apiRule.setDescription(Markdown.convertToHtml(description));
-      }
-    }
-
-    List<org.sonar.api.rules.RuleParam> apiParams = newArrayList();
-    for (RuleParamDto param : params) {
-      apiParams.add(new org.sonar.api.rules.RuleParam(apiRule, param.getName(), param.getDescription(), param.getType())
-        .setDefaultValue(param.getDefaultValue()));
-    }
-    apiRule.setParams(apiParams);
-
-    return apiRule;
-  }
-
-  @Override
-  public Optional<RuleDefinitionDto> findDtoByKey(RuleKey key) {
-    return Optional.ofNullable(this.ruleDtosByKey.get(key));
-  }
-}
index be384165d5f5caab1f2b4f027b3e1b6e82c83f5e..85ca920382e0aaad66f8c0dec6a8732e07e03f1b 100644 (file)
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -35,7 +36,6 @@ import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
-import org.apache.commons.lang.ObjectUtils;
 import org.apache.commons.lang.StringUtils;
 import org.picocontainer.Startable;
 import org.sonar.api.resources.Languages;
@@ -72,7 +72,9 @@ import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Sets.difference;
 import static com.google.common.collect.Sets.intersection;
 import static java.lang.String.format;
+import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableMap;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
@@ -113,7 +115,7 @@ public class RegisterRules implements Startable {
   @Override
   public void start() {
     Profiler profiler = Profiler.create(LOG).startInfo("Register rules");
-    try (DbSession dbSession = dbClient.openSession(false)) {
+    try (DbSession dbSession = dbClient.openSession(true)) {
       RulesDefinition.Context ruleDefinitionContext = defLoader.load();
       List<RulesDefinition.Repository> repositories = ruleDefinitionContext.repositories();
       RegisterRulesContext registerRulesContext = createRegisterRulesContext(dbSession);
@@ -122,9 +124,7 @@ public class RegisterRules implements Startable {
 
       for (RulesDefinition.ExtendedRepository repoDef : repositories) {
         if (languages.get(repoDef.language()) != null) {
-          for (RulesDefinition.Rule ruleDef : repoDef.rules()) {
-            registerRule(registerRulesContext, ruleDef, dbSession);
-          }
+          registerRules(registerRulesContext, repoDef.rules(), dbSession);
           dbSession.commit();
         }
       }
@@ -153,7 +153,13 @@ public class RegisterRules implements Startable {
     Map<RuleKey, RuleDefinitionDto> allRules = dbClient.ruleDao().selectAllDefinitions(dbSession).stream()
       .collect(uniqueIndex(RuleDefinitionDto::getKey));
     Map<String, Set<SingleDeprecatedRuleKey>> existingDeprecatedKeysById = loadDeprecatedRuleKeys(dbSession);
-    return new RegisterRulesContext(allRules, existingDeprecatedKeysById);
+    Map<String, List<RuleParamDto>> ruleParamsByRuleUuid = loadAllRuleParameters(dbSession);
+    return new RegisterRulesContext(allRules, existingDeprecatedKeysById, ruleParamsByRuleUuid);
+  }
+
+  private Map<String, List<RuleParamDto>> loadAllRuleParameters(DbSession dbSession) {
+    return dbClient.ruleDao().selectAllRuleParams(dbSession).stream()
+      .collect(Collectors.groupingBy(RuleParamDto::getRuleUuid));
   }
 
   private Map<String, Set<SingleDeprecatedRuleKey>> loadDeprecatedRuleKeys(DbSession dbSession) {
@@ -167,6 +173,7 @@ public class RegisterRules implements Startable {
     private final Map<RuleKey, RuleDefinitionDto> dbRules;
     private final Set<RuleDefinitionDto> known;
     private final Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid;
+    private final Map<String, List<RuleParamDto>> ruleParamsByRuleUuid;
     private final Map<RuleKey, RuleDefinitionDto> dbRulesByDbDeprecatedKey;
     // mutable data
     private final Set<RuleDefinitionDto> created = new HashSet<>();
@@ -175,10 +182,12 @@ public class RegisterRules implements Startable {
     private final Set<RuleDefinitionDto> unchanged = new HashSet<>();
     private final Set<RuleDefinitionDto> removed = new HashSet<>();
 
-    private RegisterRulesContext(Map<RuleKey, RuleDefinitionDto> dbRules, Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid) {
+    private RegisterRulesContext(Map<RuleKey, RuleDefinitionDto> dbRules, Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid,
+      Map<String, List<RuleParamDto>> ruleParamsByRuleUuid) {
       this.dbRules = ImmutableMap.copyOf(dbRules);
       this.known = ImmutableSet.copyOf(dbRules.values());
       this.dbDeprecatedKeysByUuid = dbDeprecatedKeysByUuid;
+      this.ruleParamsByRuleUuid = ruleParamsByRuleUuid;
       this.dbRulesByDbDeprecatedKey = buildDbRulesByDbDeprecatedKey(dbDeprecatedKeysByUuid, dbRules);
     }
 
@@ -187,19 +196,19 @@ public class RegisterRules implements Startable {
       Map<String, RuleDefinitionDto> dbRulesByRuleUuid = dbRules.values().stream()
         .collect(uniqueIndex(RuleDefinitionDto::getUuid));
 
-      ImmutableMap.Builder<RuleKey, RuleDefinitionDto> builder = ImmutableMap.builder();
+      Map<RuleKey, RuleDefinitionDto> rulesByKey = new LinkedHashMap<>();
       for (Map.Entry<String, Set<SingleDeprecatedRuleKey>> entry : dbDeprecatedKeysByUuid.entrySet()) {
         String ruleUuid = entry.getKey();
         RuleDefinitionDto rule = dbRulesByRuleUuid.get(ruleUuid);
         if (rule == null) {
           LOG.warn("Could not retrieve rule with uuid %s referenced by a deprecated rule key. " +
-            "The following deprecated rule keys seem to be referencing a non-existing rule",
+              "The following deprecated rule keys seem to be referencing a non-existing rule",
             ruleUuid, entry.getValue());
         } else {
-          entry.getValue().forEach(d -> builder.put(d.getOldRuleKeyAsRuleKey(), rule));
+          entry.getValue().forEach(d -> rulesByKey.put(d.getOldRuleKeyAsRuleKey(), rule));
         }
       }
-      return builder.build();
+      return unmodifiableMap(rulesByKey);
     }
 
     private boolean hasDbRules() {
@@ -219,7 +228,7 @@ public class RegisterRules implements Startable {
       return res;
     }
 
-    private ImmutableMap<RuleKey, SingleDeprecatedRuleKey> getDbDeprecatedKeysByOldRuleKey() {
+    private Map<RuleKey, SingleDeprecatedRuleKey> getDbDeprecatedKeysByOldRuleKey() {
       return dbDeprecatedKeysByUuid.values().stream()
         .flatMap(Collection::stream)
         .collect(uniqueIndex(SingleDeprecatedRuleKey::getOldRuleKeyAsRuleKey));
@@ -229,6 +238,10 @@ public class RegisterRules implements Startable {
       return dbDeprecatedKeysByUuid.getOrDefault(rule.getUuid(), emptySet());
     }
 
+    private List<RuleParamDto> getRuleParametersFor(String ruleUuid) {
+      return ruleParamsByRuleUuid.getOrDefault(ruleUuid, emptyList());
+    }
+
     private Stream<RuleDefinitionDto> getRemaining() {
       Set<RuleDefinitionDto> res = new HashSet<>(dbRules.values());
       res.removeAll(unchanged);
@@ -298,8 +311,7 @@ public class RegisterRules implements Startable {
   }
 
   private void persistRepositories(DbSession dbSession, List<RulesDefinition.Repository> repositories) {
-    List<RuleRepositoryDto> dtos = repositories
-      .stream()
+    List<RuleRepositoryDto> dtos = repositories.stream()
       .map(r -> new RuleRepositoryDto(r.key(), r.language(), r.name()))
       .collect(toList(repositories.size()));
     List<String> keys = dtos.stream().map(RuleRepositoryDto::getKey).collect(toList(repositories.size()));
@@ -313,46 +325,53 @@ public class RegisterRules implements Startable {
     // nothing
   }
 
-  private void registerRule(RegisterRulesContext context, RulesDefinition.Rule ruleDef, DbSession session) {
-    RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
+  private void registerRules(RegisterRulesContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) {
+    Map<RulesDefinition.Rule, RuleDefinitionDto> dtos = new LinkedHashMap<>(ruleDefs.size());
 
-    RuleDefinitionDto ruleDefinitionDto = context.getDbRuleFor(ruleDef)
-      .orElseGet(() -> {
-        RuleDefinitionDto newRule = createRuleDto(ruleDef, session);
-        context.created(newRule);
-        return newRule;
-      });
+    for (RulesDefinition.Rule ruleDef : ruleDefs) {
+      RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
 
-    // we must detect renaming __before__ we modify the DTO
-    if (!ruleDefinitionDto.getKey().equals(ruleKey)) {
-      context.renamed(ruleDefinitionDto);
-      ruleDefinitionDto.setRuleKey(ruleKey);
-    }
+      RuleDefinitionDto ruleDefinitionDto = context.getDbRuleFor(ruleDef)
+        .orElseGet(() -> {
+          RuleDefinitionDto newRule = createRuleDto(ruleDef, session);
+          context.created(newRule);
+          return newRule;
+        });
+      dtos.put(ruleDef, ruleDefinitionDto);
+
+      // we must detect renaming __before__ we modify the DTO
+      if (!ruleDefinitionDto.getKey().equals(ruleKey)) {
+        context.renamed(ruleDefinitionDto);
+        ruleDefinitionDto.setRuleKey(ruleKey);
+      }
 
-    if (mergeRule(ruleDef, ruleDefinitionDto)) {
-      context.updated(ruleDefinitionDto);
-    }
+      if (mergeRule(ruleDef, ruleDefinitionDto)) {
+        context.updated(ruleDefinitionDto);
+      }
 
-    if (mergeDebtDefinitions(ruleDef, ruleDefinitionDto)) {
-      context.updated(ruleDefinitionDto);
-    }
+      if (mergeDebtDefinitions(ruleDef, ruleDefinitionDto)) {
+        context.updated(ruleDefinitionDto);
+      }
 
-    if (mergeTags(ruleDef, ruleDefinitionDto)) {
-      context.updated(ruleDefinitionDto);
-    }
+      if (mergeTags(ruleDef, ruleDefinitionDto)) {
+        context.updated(ruleDefinitionDto);
+      }
 
-    if (mergeSecurityStandards(ruleDef, ruleDefinitionDto)) {
-      context.updated(ruleDefinitionDto);
-    }
+      if (mergeSecurityStandards(ruleDef, ruleDefinitionDto)) {
+        context.updated(ruleDefinitionDto);
+      }
 
-    if (context.isUpdated(ruleDefinitionDto) || context.isRenamed(ruleDefinitionDto)) {
-      update(session, ruleDefinitionDto);
-    } else if (!context.isCreated(ruleDefinitionDto)) {
-      context.unchanged(ruleDefinitionDto);
+      if (context.isUpdated(ruleDefinitionDto) || context.isRenamed(ruleDefinitionDto)) {
+        update(session, ruleDefinitionDto);
+      } else if (!context.isCreated(ruleDefinitionDto)) {
+        context.unchanged(ruleDefinitionDto);
+      }
     }
 
-    mergeParams(ruleDef, ruleDefinitionDto, session);
-    updateDeprecatedKeys(context, ruleDef, ruleDefinitionDto, session);
+    for (Map.Entry<RulesDefinition.Rule, RuleDefinitionDto> e : dtos.entrySet()) {
+      mergeParams(context, e.getKey(), e.getValue(), session);
+      updateDeprecatedKeys(context, e.getKey(), e.getValue(), session);
+    }
   }
 
   private RuleDefinitionDto createRuleDto(RulesDefinition.Rule ruleDef, DbSession session) {
@@ -409,23 +428,23 @@ public class RegisterRules implements Startable {
 
   private static boolean mergeRule(RulesDefinition.Rule def, RuleDefinitionDto dto) {
     boolean changed = false;
-    if (!StringUtils.equals(dto.getName(), def.name())) {
+    if (!Objects.equals(dto.getName(), def.name())) {
       dto.setName(def.name());
       changed = true;
     }
     if (mergeDescription(def, dto)) {
       changed = true;
     }
-    if (!StringUtils.equals(dto.getPluginKey(), def.pluginKey())) {
+    if (!Objects.equals(dto.getPluginKey(), def.pluginKey())) {
       dto.setPluginKey(def.pluginKey());
       changed = true;
     }
-    if (!StringUtils.equals(dto.getConfigKey(), def.internalKey())) {
+    if (!Objects.equals(dto.getConfigKey(), def.internalKey())) {
       dto.setConfigKey(def.internalKey());
       changed = true;
     }
     String severity = def.severity();
-    if (!ObjectUtils.equals(dto.getSeverityString(), severity)) {
+    if (!Objects.equals(dto.getSeverityString(), severity)) {
       dto.setSeverity(severity);
       changed = true;
     }
@@ -438,16 +457,16 @@ public class RegisterRules implements Startable {
       dto.setStatus(def.status());
       changed = true;
     }
-    if (!StringUtils.equals(dto.getScope().name(), def.scope().name())) {
+    if (!Objects.equals(dto.getScope().name(), def.scope().name())) {
       dto.setScope(toDtoScope(def.scope()));
       changed = true;
     }
-    if (!StringUtils.equals(dto.getLanguage(), def.repository().language())) {
+    if (!Objects.equals(dto.getLanguage(), def.repository().language())) {
       dto.setLanguage(def.repository().language());
       changed = true;
     }
     RuleType type = RuleType.valueOf(def.type().name());
-    if (!ObjectUtils.equals(dto.getType(), type.getDbConstant())) {
+    if (!Objects.equals(dto.getType(), type.getDbConstant())) {
       dto.setType(type);
       changed = true;
     }
@@ -460,11 +479,11 @@ public class RegisterRules implements Startable {
 
   private static boolean mergeDescription(RulesDefinition.Rule def, RuleDefinitionDto dto) {
     boolean changed = false;
-    if (def.htmlDescription() != null && !StringUtils.equals(dto.getDescription(), def.htmlDescription())) {
+    if (def.htmlDescription() != null && !Objects.equals(dto.getDescription(), def.htmlDescription())) {
       dto.setDescription(def.htmlDescription());
       dto.setDescriptionFormat(Format.HTML);
       changed = true;
-    } else if (def.markdownDescription() != null && !StringUtils.equals(dto.getDescription(), def.markdownDescription())) {
+    } else if (def.markdownDescription() != null && !Objects.equals(dto.getDescription(), def.markdownDescription())) {
       dto.setDescription(def.markdownDescription());
       dto.setDescriptionFormat(Format.MARKDOWN);
       changed = true;
@@ -490,27 +509,27 @@ public class RegisterRules implements Startable {
     @Nullable String remediationCoefficient, @Nullable String remediationOffset, @Nullable String effortToFixDescription) {
     boolean changed = false;
 
-    if (!StringUtils.equals(dto.getDefRemediationFunction(), remediationFunction)) {
+    if (!Objects.equals(dto.getDefRemediationFunction(), remediationFunction)) {
       dto.setDefRemediationFunction(remediationFunction);
       changed = true;
     }
-    if (!StringUtils.equals(dto.getDefRemediationGapMultiplier(), remediationCoefficient)) {
+    if (!Objects.equals(dto.getDefRemediationGapMultiplier(), remediationCoefficient)) {
       dto.setDefRemediationGapMultiplier(remediationCoefficient);
       changed = true;
     }
-    if (!StringUtils.equals(dto.getDefRemediationBaseEffort(), remediationOffset)) {
+    if (!Objects.equals(dto.getDefRemediationBaseEffort(), remediationOffset)) {
       dto.setDefRemediationBaseEffort(remediationOffset);
       changed = true;
     }
-    if (!StringUtils.equals(dto.getGapDescription(), effortToFixDescription)) {
+    if (!Objects.equals(dto.getGapDescription(), effortToFixDescription)) {
       dto.setGapDescription(effortToFixDescription);
       changed = true;
     }
     return changed;
   }
 
-  private void mergeParams(RulesDefinition.Rule ruleDef, RuleDefinitionDto rule, DbSession session) {
-    List<RuleParamDto> paramDtos = dbClient.ruleDao().selectRuleParamsByRuleKey(session, rule.getKey());
+  private void mergeParams(RegisterRulesContext context, RulesDefinition.Rule ruleDef, RuleDefinitionDto rule, DbSession session) {
+    List<RuleParamDto> paramDtos = context.getRuleParametersFor(rule.getUuid());
     Map<String, RuleParamDto> existingParamsByName = new HashMap<>();
 
     Profiler profiler = Profiler.create(Loggers.get(getClass()));
@@ -556,23 +575,22 @@ public class RegisterRules implements Startable {
 
   private static boolean mergeParam(RuleParamDto paramDto, RulesDefinition.Param paramDef) {
     boolean changed = false;
-    if (!StringUtils.equals(paramDto.getType(), paramDef.type().toString())) {
+    if (!Objects.equals(paramDto.getType(), paramDef.type().toString())) {
       paramDto.setType(paramDef.type().toString());
       changed = true;
     }
-    if (!StringUtils.equals(paramDto.getDefaultValue(), paramDef.defaultValue())) {
+    if (!Objects.equals(paramDto.getDefaultValue(), paramDef.defaultValue())) {
       paramDto.setDefaultValue(paramDef.defaultValue());
       changed = true;
     }
-    if (!StringUtils.equals(paramDto.getDescription(), paramDef.description())) {
+    if (!Objects.equals(paramDto.getDescription(), paramDef.description())) {
       paramDto.setDescription(paramDef.description());
       changed = true;
     }
     return changed;
   }
 
-  private void updateDeprecatedKeys(RegisterRulesContext context, RulesDefinition.Rule ruleDef, RuleDefinitionDto rule,
-    DbSession dbSession) {
+  private void updateDeprecatedKeys(RegisterRulesContext context, RulesDefinition.Rule ruleDef, RuleDefinitionDto rule, DbSession dbSession) {
 
     Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDefinition = SingleDeprecatedRuleKey.from(ruleDef);
     Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDB = context.getDBDeprecatedKeysFor(rule);
@@ -604,9 +622,9 @@ public class RegisterRules implements Startable {
       changed = true;
     } else if (dto.getSystemTags().size() != ruleDef.tags().size() ||
       !dto.getSystemTags().containsAll(ruleDef.tags())) {
-        dto.setSystemTags(ruleDef.tags());
-        changed = true;
-      }
+      dto.setSystemTags(ruleDef.tags());
+      changed = true;
+    }
     return changed;
   }
 
@@ -618,9 +636,9 @@ public class RegisterRules implements Startable {
       changed = true;
     } else if (dto.getSecurityStandards().size() != ruleDef.securityStandards().size() ||
       !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) {
-        dto.setSecurityStandards(ruleDef.securityStandards());
-        changed = true;
-      }
+      dto.setSecurityStandards(ruleDef.securityStandards());
+      changed = true;
+    }
     return changed;
   }
 
@@ -669,31 +687,31 @@ public class RegisterRules implements Startable {
 
   private static boolean updateCustomRuleFromTemplateRule(RuleDefinitionDto customRule, RuleDefinitionDto templateRule) {
     boolean changed = false;
-    if (!StringUtils.equals(customRule.getLanguage(), templateRule.getLanguage())) {
+    if (!Objects.equals(customRule.getLanguage(), templateRule.getLanguage())) {
       customRule.setLanguage(templateRule.getLanguage());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getConfigKey(), templateRule.getConfigKey())) {
+    if (!Objects.equals(customRule.getConfigKey(), templateRule.getConfigKey())) {
       customRule.setConfigKey(templateRule.getConfigKey());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getPluginKey(), templateRule.getPluginKey())) {
+    if (!Objects.equals(customRule.getPluginKey(), templateRule.getPluginKey())) {
       customRule.setPluginKey(templateRule.getPluginKey());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getDefRemediationFunction(), templateRule.getDefRemediationFunction())) {
+    if (!Objects.equals(customRule.getDefRemediationFunction(), templateRule.getDefRemediationFunction())) {
       customRule.setDefRemediationFunction(templateRule.getDefRemediationFunction());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getDefRemediationGapMultiplier(), templateRule.getDefRemediationGapMultiplier())) {
+    if (!Objects.equals(customRule.getDefRemediationGapMultiplier(), templateRule.getDefRemediationGapMultiplier())) {
       customRule.setDefRemediationGapMultiplier(templateRule.getDefRemediationGapMultiplier());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getDefRemediationBaseEffort(), templateRule.getDefRemediationBaseEffort())) {
+    if (!Objects.equals(customRule.getDefRemediationBaseEffort(), templateRule.getDefRemediationBaseEffort())) {
       customRule.setDefRemediationBaseEffort(templateRule.getDefRemediationBaseEffort());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getGapDescription(), templateRule.getGapDescription())) {
+    if (!Objects.equals(customRule.getGapDescription(), templateRule.getGapDescription())) {
       customRule.setGapDescription(templateRule.getGapDescription());
       changed = true;
     }
@@ -701,11 +719,11 @@ public class RegisterRules implements Startable {
       customRule.setStatus(templateRule.getStatus());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getSeverityString(), templateRule.getSeverityString())) {
+    if (!Objects.equals(customRule.getSeverityString(), templateRule.getSeverityString())) {
       customRule.setSeverity(templateRule.getSeverityString());
       changed = true;
     }
-    if (!StringUtils.equals(customRule.getRepositoryKey(), templateRule.getRepositoryKey())) {
+    if (!Objects.equals(customRule.getRepositoryKey(), templateRule.getRepositoryKey())) {
       customRule.setRepositoryKey(templateRule.getRepositoryKey());
       changed = true;
     }
@@ -771,7 +789,7 @@ public class RegisterRules implements Startable {
       lazyToString(() -> intersection.stream().map(RuleKey::toString).collect(Collectors.joining(","))));
 
     // Find incorrect usage of deprecated keys
-    ImmutableMap<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey = registerRulesContext.getDbDeprecatedKeysByOldRuleKey();
+    Map<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey = registerRulesContext.getDbDeprecatedKeysByOldRuleKey();
 
     Set<String> incorrectRuleKeyMessage = definedRules.stream()
       .flatMap(r -> filterInvalidDeprecatedRuleKeys(dbDeprecatedRuleKeysByOldRuleKey, r))
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java
deleted file mode 100644 (file)
index 3b4b759..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.rule;
-
-/**
- * {@link ServerRuleFinder} implementation that supports caching used by the Web Server.
- * <p>
- *   Caching is enabled right after loading of rules is done (see {@link RegisterRules}) and disabled
- *   once all startup tasks are done (see {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup}).
- * </p>
- */
-public interface WebServerRuleFinder extends ServerRuleFinder {
-  /**
-   * Enable caching.
-   */
-  void startCaching();
-
-  /**
-   * Disable caching.
-   */
-  void stopCaching();
-}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java
deleted file mode 100644 (file)
index deeba34..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.rule;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.util.Collection;
-import java.util.Optional;
-import javax.annotation.CheckForNull;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.db.DbClient;
-import org.sonar.db.rule.RuleDefinitionDto;
-
-public class WebServerRuleFinderImpl implements WebServerRuleFinder {
-  private final DbClient dbClient;
-  private final ServerRuleFinder defaultFinder;
-  @VisibleForTesting
-  ServerRuleFinder delegate;
-
-  public WebServerRuleFinderImpl(DbClient dbClient) {
-    this.dbClient = dbClient;
-    this.defaultFinder = new DefaultRuleFinder(dbClient);
-    this.delegate = this.defaultFinder;
-  }
-
-  @Override
-  public void startCaching() {
-    this.delegate = new CachingRuleFinder(dbClient);
-  }
-
-  @Override
-  public void stopCaching() {
-    this.delegate = this.defaultFinder;
-  }
-
-  @Override
-  @CheckForNull
-  public Rule findByKey(String repositoryKey, String key) {
-    return delegate.findByKey(repositoryKey, key);
-  }
-
-  @Override
-  @CheckForNull
-  public Rule findByKey(RuleKey key) {
-    return delegate.findByKey(key);
-  }
-
-  @Override
-  @CheckForNull
-  public Rule find(RuleQuery query) {
-    return delegate.find(query);
-  }
-
-  @Override
-  public Collection<Rule> findAll(RuleQuery query) {
-    return delegate.findAll(query);
-  }
-
-  @Override
-  public Optional<RuleDefinitionDto> findDtoByKey(RuleKey key) {
-    return delegate.findDtoByKey(key);
-  }
-}
index 6757058ef218976bb161b525e877f81eb99bb83d..1760041827b118482cb4a88b741581cb88fd7e27 100644 (file)
@@ -19,9 +19,6 @@
  */
 package org.sonar.server.startup;
 
-import static com.google.common.collect.FluentIterable.concat;
-import static com.google.common.collect.Lists.newArrayList;
-
 import com.google.common.annotations.VisibleForTesting;
 import java.util.HashMap;
 import java.util.List;
@@ -39,6 +36,9 @@ import org.sonar.db.DbSession;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.server.metric.MetricToDto;
 
+import static com.google.common.collect.FluentIterable.concat;
+import static com.google.common.collect.Lists.newArrayList;
+
 public class RegisterMetrics implements Startable {
 
   private static final Logger LOG = Loggers.get(RegisterMetrics.class);
@@ -72,7 +72,7 @@ public class RegisterMetrics implements Startable {
 
   void register(Iterable<Metric> metrics) {
     Profiler profiler = Profiler.create(LOG).startInfo("Register metrics");
-    try (DbSession session = dbClient.openSession(false)) {
+    try (DbSession session = dbClient.openSession(true)) {
       save(session, metrics);
       sanitizeQualityGates(session);
       session.commit();
index 7d2219b826b7386342793252b376a3df28ecce01..afb59647971f229dc107ccc104b12a0c60d883c4 100644 (file)
@@ -95,6 +95,7 @@ public class CachingRuleFinderTest {
 
     verify(dbClient).openSession(anyBoolean());
     verify(ruleDao).selectAllDefinitions(dbSession);
+    verify(ruleDao).selectAllRuleParams(dbSession);
     verifyNoMoreInteractions(ruleDao);
   }
 
@@ -110,7 +111,7 @@ public class CachingRuleFinderTest {
 
     new CachingRuleFinder(dbClient);
 
-    verify(ruleDao).selectRuleParamsByRuleKeys(dbSession, ImmutableSet.copyOf(ruleKeys));
+    verify(ruleDao).selectAllRuleParams(dbSession);
   }
 
   @Test
index 07b785376a7604d38342af91fa8b785b5301ea61..4c7423de81b9bbfbdb84424108bf92667152b73a 100644 (file)
@@ -102,7 +102,7 @@ public class RegisterRulesTest {
   private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
   private static final RuleKey HOTSPOT_RULE_KEY = RuleKey.of("fake", "hotspot");
 
-  private TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
+  private final TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
 
   @org.junit.Rule
   public ExpectedException expectedException = ExpectedException.none();
@@ -113,14 +113,16 @@ public class RegisterRulesTest {
   @org.junit.Rule
   public LogTester logTester = new LogTester();
 
-  private QProfileRules qProfileRules = mock(QProfileRules.class);
-  private WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
-  private DbClient dbClient = db.getDbClient();
+  private final QProfileRules qProfileRules = mock(QProfileRules.class);
+  private final WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
+  private final DbClient dbClient = db.getDbClient();
+  private final MetadataIndex metadataIndex = mock(MetadataIndex.class);
+  private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+
   private RuleIndexer ruleIndexer;
   private ActiveRuleIndexer activeRuleIndexer;
   private RuleIndex ruleIndex;
-  private MetadataIndex metadataIndex = mock(MetadataIndex.class);
-  private UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+
 
   @Before
   public void before() {
@@ -996,7 +998,7 @@ public class RegisterRulesTest {
   }
 
   @SafeVarargs
-  private final void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) {
+  private void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) {
     NewRepository repo = context.createRepository(repositoryKey, language);
     NewRule newRule = repo.createRule(ruleKey)
       .setName(ruleKey)
index 69682a5d27ee250ef1150f28b731d77a53a16962..aebccfc0a3f3ff41837ef68824f2bfbe7d87dd84 100644 (file)
@@ -45,8 +45,9 @@ public class RegisterMetricsTest {
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  private UuidFactory uuidFactory = new SequenceUuidFactory();
-  private DbClient dbClient = dbTester.getDbClient();
+  private final UuidFactory uuidFactory = new SequenceUuidFactory();
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
 
   /**
    * Insert new metrics, including custom metrics
@@ -65,7 +66,6 @@ public class RegisterMetricsTest {
       .setUserManaged(true)
       .create();
 
-    RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
     register.register(asList(m1, custom));
 
     Map<String, MetricDto> metricsByKey = selectAllMetrics();
@@ -91,7 +91,6 @@ public class RegisterMetricsTest {
       .setDirection(1)
       .setHidden(false));
 
-    RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
     Metric m1 = new Metric.Builder("m1", "New name", Metric.ValueType.FLOAT)
       .setDescription("new description")
       .setDirection(-1)
@@ -115,7 +114,6 @@ public class RegisterMetricsTest {
     IntStream.range(0, count)
       .forEach(t -> dbTester.measures().insertMetric(m -> m.setEnabled(random.nextBoolean())));
 
-    RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
     register.register(Collections.emptyList());
 
     assertThat(selectAllMetrics().values().stream())
@@ -128,7 +126,6 @@ public class RegisterMetricsTest {
     MetricDto enabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(true));
     MetricDto disabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(false));
 
-    RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
     register.register(asList(builderOf(enabledMetric).create(), builderOf(disabledMetric).create()));
 
     assertThat(selectAllMetrics().values())
@@ -138,7 +135,6 @@ public class RegisterMetricsTest {
 
   @Test
   public void insert_core_metrics() {
-    RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
     register.start();
 
     assertThat(dbTester.countRowsOfTable("metrics")).isEqualTo(CoreMetrics.getMetrics().size());
index fa31fa1259a80a7f5034ef44897111522a4124cd..1b6178596099a37686d4af6747caef990b4a4015 100644 (file)
@@ -73,8 +73,7 @@ public class RegisterQualityGates implements Startable {
   private final UuidFactory uuidFactory;
   private final System2 system2;
 
-  public RegisterQualityGates(DbClient dbClient,
-    QualityGateConditionsUpdater qualityGateConditionsUpdater, UuidFactory uuidFactory, System2 system2) {
+  public RegisterQualityGates(DbClient dbClient, QualityGateConditionsUpdater qualityGateConditionsUpdater, UuidFactory uuidFactory, System2 system2) {
     this.dbClient = dbClient;
     this.qualityGateConditionsUpdater = qualityGateConditionsUpdater;
     this.qualityGateDao = dbClient.qualityGateDao();
@@ -212,7 +211,7 @@ public class RegisterQualityGates implements Startable {
         .setQualityGateUuid(qualityGateUuid);
     }
 
-    // id does not belongs to equals to be able to be compared with builtin
+    // id does not belong to equals to be able to be compared with builtin
     @Override
     public boolean equals(Object o) {
       if (this == o) {
@@ -227,7 +226,7 @@ public class RegisterQualityGates implements Startable {
         Objects.equals(errorThreshold, that.errorThreshold);
     }
 
-    // id does not belongs to hashcode to be able to be compared with builtin
+    // id does not belong to hashcode to be able to be compared with builtin
     @Override
     public int hashCode() {
       return Objects.hash(metricKey, operator, errorThreshold);
index 1333b8e5da60c15e350005ef6ff3aab09a555141..61ced1e075f6641979cfd563d151b6116fbea766 100644 (file)
@@ -47,6 +47,8 @@ import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.db.qualityprofile.RulesProfileDto;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.rule.ServerRuleFinder;
 import org.sonar.server.rule.index.RuleIndex;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.util.TypeValidations;
@@ -62,6 +64,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.rules.RulePriority.MAJOR;
@@ -90,7 +93,9 @@ public class RegisterQualityProfilesNotificationTest {
   private DbClient dbClient = db.getDbClient();
   private TypeValidations typeValidations = mock(TypeValidations.class);
   private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
-  private BuiltInQProfileInsert builtInQProfileInsert = new BuiltInQProfileInsertImpl(dbClient, system2, UuidFactoryFast.getInstance(), typeValidations, activeRuleIndexer);
+  private ServerRuleFinder ruleFinder = new DefaultRuleFinder(dbClient);
+  private BuiltInQProfileInsert builtInQProfileInsert = new BuiltInQProfileInsertImpl(dbClient, ruleFinder, system2, UuidFactoryFast.getInstance(),
+    typeValidations, activeRuleIndexer);
   private RuleActivator ruleActivator = new RuleActivator(system2, dbClient, typeValidations, userSessionRule);
   private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, mock(RuleIndex.class), activeRuleIndexer);
   private BuiltInQProfileUpdate builtInQProfileUpdate = new BuiltInQProfileUpdateImpl(dbClient, ruleActivator, activeRuleIndexer);
@@ -120,7 +125,7 @@ public class RegisterQualityProfilesNotificationTest {
 
     underTest.start();
 
-    verifyZeroInteractions(builtInQualityProfilesNotification);
+    verifyNoInteractions(builtInQualityProfilesNotification);
   }
 
   @Test
index effcfb2b076fdcc8eaee10e2cd0fbff2b9edf051..33b1d52589cffa9d994a94adb11bf0c51560f1dd 100644 (file)
@@ -170,15 +170,15 @@ public abstract class PlatformLevel {
     return addIfStandalone;
   }
 
-  private WebServer getWebServer() {
+  protected WebServer getWebServer() {
     return getOptional(WebServer.class)
       .orElseThrow(() -> new IllegalStateException("WebServer not available in Pico yet"));
   }
 
-  private abstract class AddIf {
+  protected abstract class AddIf {
     private final boolean condition;
 
-    private AddIf(boolean condition) {
+    protected AddIf(boolean condition) {
       this.condition = condition;
     }
 
index c8a92a1071909d8131f2e3a8608f245f33a17bde..d4e2992e549fc82894bd8b0cc2fdd41783ce9bc0 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.server.platform.StartupMetadataPersister;
 import org.sonar.server.platform.WebCoreExtensionsInstaller;
 import org.sonar.server.platform.db.migration.NoopDatabaseMigrationImpl;
 import org.sonar.server.platform.serverid.ServerIdModule;
+import org.sonar.server.plugins.DetectPluginChange;
 import org.sonar.server.setting.DatabaseSettingLoader;
 import org.sonar.server.setting.DatabaseSettingsEnabler;
 
@@ -42,7 +43,9 @@ public class PlatformLevel3 extends PlatformLevel {
 
   @Override
   protected void configureLevel() {
-    addIfStartupLeader(StartupMetadataPersister.class);
+    addIfStartupLeader(
+      StartupMetadataPersister.class,
+      DetectPluginChange.class);
     add(
       NoopDatabaseMigrationImpl.class,
       ServerIdModule.class,
index fab705046ab9bd64df4cf8e602a308643e737349..eca4d7740341a6fe99b0fd2baf689a9cbf212dad 100644 (file)
@@ -23,10 +23,12 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.platform.EditionProvider;
 import org.sonar.core.platform.PlatformEditionProvider;
 import org.sonar.server.app.ProcessCommandWrapper;
+import org.sonar.server.authentication.DefaultAdminCredentialsVerifierImpl;
 import org.sonar.server.ce.queue.CeQueueCleaner;
 import org.sonar.server.es.IndexerStartupTask;
 import org.sonar.server.platform.ServerLifecycleNotifier;
 import org.sonar.server.platform.web.RegisterServletFilters;
+import org.sonar.server.plugins.DetectPluginChange;
 import org.sonar.server.plugins.PluginConsentVerifier;
 import org.sonar.server.qualitygate.ProjectsInWarningDaemon;
 import org.sonar.server.qualitygate.RegisterQualityGates;
@@ -47,6 +49,8 @@ import org.sonar.server.user.DoPrivileged;
 import org.sonar.server.user.ThreadLocalUserSession;
 
 public class PlatformLevelStartup extends PlatformLevel {
+  private AddIfStartupLeaderAndPluginsChanged addIfPluginsChanged;
+
   public PlatformLevelStartup(PlatformLevel parent) {
     super("startup tasks", parent);
   }
@@ -54,29 +58,57 @@ public class PlatformLevelStartup extends PlatformLevel {
   @Override
   protected void configureLevel() {
     add(GeneratePluginIndex.class,
-      RegisterPlugins.class,
       ServerLifecycleNotifier.class);
 
     addIfStartupLeader(
-      IndexerStartupTask.class,
+      IndexerStartupTask.class);
+    addIfStartupLeaderAndPluginsChanged(
       RegisterMetrics.class,
       RegisterQualityGates.class,
-      RegisterRules.class);
-    add(BuiltInQProfileLoader.class);
+      RegisterRules.class,
+      BuiltInQProfileLoader.class);
     addIfStartupLeader(
       BuiltInQualityProfilesUpdateListener.class,
+      BuiltInQProfileUpdateImpl.class);
+    addIfStartupLeaderAndPluginsChanged(
       BuiltInQProfileInsertImpl.class,
-      BuiltInQProfileUpdateImpl.class,
-      RegisterQualityProfiles.class,
+      RegisterQualityProfiles.class);
+    addIfStartupLeader(
       RegisterPermissionTemplates.class,
       RenameDeprecatedPropertyKeys.class,
       CeQueueCleaner.class,
       UpgradeSuggestionsCleaner.class,
       PluginConsentVerifier.class);
+    add(RegisterPlugins.class,
+      // RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefore it
+      // must be started after all the other startup tasks
+      RegisterServletFilters.class
+      );
+  }
+
+  /**
+   * Add a component to container only if plugins have changed since last start.
+   *
+   * @throws IllegalStateException if called from PlatformLevel3 or below, plugin info is loaded yet
+   */
+  AddIfStartupLeaderAndPluginsChanged addIfStartupLeaderAndPluginsChanged(Object... objects) {
+    if (addIfPluginsChanged == null) {
+      this.addIfPluginsChanged = new AddIfStartupLeaderAndPluginsChanged(getWebServer().isStartupLeader() && anyPluginChanged());
+    }
+    addIfPluginsChanged.ifAdd(objects);
+    return addIfPluginsChanged;
+  }
+
+  private boolean anyPluginChanged() {
+    return getOptional(DetectPluginChange.class)
+      .map(DetectPluginChange::anyPluginChanged)
+      .orElseThrow(() -> new IllegalStateException("DetectPluginChange not available in Pico yet"));
+  }
 
-    // RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefore it
-    // must be started after all the other startup tasks
-    add(RegisterServletFilters.class);
+  public final class AddIfStartupLeaderAndPluginsChanged extends AddIf {
+    private AddIfStartupLeaderAndPluginsChanged(boolean condition) {
+      super(condition);
+    }
   }
 
   @Override
@@ -93,6 +125,7 @@ public class PlatformLevelStartup extends PlatformLevel {
         get(WebServerRuleFinder.class).stopCaching();
         Loggers.get(PlatformLevelStartup.class)
           .info("Running {} Edition", get(PlatformEditionProvider.class).get().map(EditionProvider.Edition::getLabel).orElse(""));
+        get(DefaultAdminCredentialsVerifierImpl.class).runAtStart();
       }
     });