]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9191 cache rule in RuleFinder during startup
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 6 Jun 2017 15:54:47 +0000 (17:54 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 9 Jun 2017 08:10:53 +0000 (10:10 +0200)
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
server/sonar-server/src/main/java/org/sonar/server/rule/CachingRuleFinder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java
server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java
server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/WebServerRuleFinderImplTest.java [new file with mode: 0644]

index 8805cb297f52aea7e665261ff9df3696f9db509a..d6e90fdd7bbe13036da345eb022f6dab32a32660 100644 (file)
@@ -160,11 +160,11 @@ import org.sonar.server.qualityprofile.ws.QProfilesWsModule;
 import org.sonar.server.qualityprofile.ws.SearchDataLoader;
 import org.sonar.server.root.ws.RootWsModule;
 import org.sonar.server.rule.CommonRuleDefinitionsImpl;
-import org.sonar.server.rule.DefaultRuleFinder;
 import org.sonar.server.rule.DeprecatedRulesDefinitionLoader;
 import org.sonar.server.rule.RuleCreator;
 import org.sonar.server.rule.RuleDefinitionsLoader;
 import org.sonar.server.rule.RuleUpdater;
+import org.sonar.server.rule.WebServerRuleFinderImpl;
 import org.sonar.server.rule.index.RuleIndexDefinition;
 import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.rule.ws.ActiveRuleCompleter;
@@ -286,7 +286,7 @@ public class PlatformLevel4 extends PlatformLevel {
       RuleIndexer.class,
       AnnotationRuleParser.class,
       XMLRuleParser.class,
-      DefaultRuleFinder.class,
+      WebServerRuleFinderImpl.class,
       DeprecatedRulesDefinitionLoader.class,
       RuleDefinitionsLoader.class,
       CommonRuleDefinitionsImpl.class,
index e04676ae3fb79832c1622dd3288a8745cfe05da6..9bbfd549765b16267e86a21d297711df1c8cf96a 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.server.qualityprofile.DefinedQProfileLoader;
 import org.sonar.server.qualityprofile.MassRegisterQualityProfiles;
 import org.sonar.server.qualityprofile.RegisterQualityProfiles;
 import org.sonar.server.rule.RegisterRules;
+import org.sonar.server.rule.WebServerRuleFinder;
 import org.sonar.server.startup.DeleteOldAnalysisReportsFromFs;
 import org.sonar.server.startup.DisplayLogOnDeprecatedProjects;
 import org.sonar.server.startup.GeneratePluginIndex;
@@ -83,6 +84,7 @@ public class PlatformLevelStartup extends PlatformLevel {
         getOptional(IndexerStartupTask.class).ifPresent(IndexerStartupTask::execute);
         get(ServerLifecycleNotifier.class).notifyStart();
         get(ProcessCommandWrapper.class).notifyOperational();
+        get(WebServerRuleFinder.class).stopCaching();
       }
     });
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/CachingRuleFinder.java b/server/sonar-server/src/main/java/org/sonar/server/rule/CachingRuleFinder.java
new file mode 100644 (file)
index 0000000..ff11d30
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.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.Set;
+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 RuleFinder {
+
+  private static final Ordering<Map.Entry<RuleDefinitionDto, Rule>> FIND_BY_QUERY_ORDER = Ordering.natural().reverse().onResultOf(entry -> entry.getKey().getUpdatedAt());
+
+  private final Map<RuleDefinitionDto, Rule> rulesByRuleDefinition;
+  private final Map<Integer, Rule> rulesById;
+  private final Map<RuleKey, Rule> rulesByKey;
+
+  public CachingRuleFinder(DbClient dbClient) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      this.rulesByRuleDefinition = buildRulesByRuleDefinitionDto(dbClient, dbSession);
+      this.rulesById = this.rulesByRuleDefinition.entrySet().stream()
+        .collect(uniqueIndex(entry -> entry.getKey().getId(), Map.Entry::getValue));
+      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 = dbClient.ruleDao().selectAllDefinitions(dbSession);
+    Set<RuleKey> ruleKeys = dtos.stream().map(RuleDefinitionDto::getKey).collect(toSet(dtos.size()));
+    ListMultimap<Integer, RuleParamDto> ruleParamsByRuleId = retrieveRuleParameters(dbClient, dbSession, ruleKeys);
+    Map<RuleDefinitionDto, Rule> rulesByDefinition = new HashMap<>(dtos.size());
+    for (RuleDefinitionDto definition : dtos) {
+      rulesByDefinition.put(definition, toRule(definition, ruleParamsByRuleId.get(definition.getId())));
+    }
+    return ImmutableMap.copyOf(rulesByDefinition);
+  }
+
+  private static ImmutableListMultimap<Integer, 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::getRuleId));
+  }
+
+  @Override
+  @Deprecated
+  @CheckForNull
+  public Rule findById(int ruleId) {
+    return rulesById.get(ruleId);
+  }
+
+  @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();
+
+    org.sonar.api.rules.Rule apiRule = new org.sonar.api.rules.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())
+      .setTags(ruleDefinition.getSystemTags().toArray(new String[ruleDefinition.getSystemTags().size()]))
+      .setId(ruleDefinition.getId());
+    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;
+  }
+}
index ffe7267bc91b4600217d23775435e2fd1a7eeee4..d6a276acc5da1817f742fac5ecc3605a68cc8502 100644 (file)
@@ -46,7 +46,7 @@ import org.sonar.server.organization.DefaultOrganizationProvider;
 import static com.google.common.collect.Lists.newArrayList;
 
 /**
- * Will be removed in the future. Please use {@link org.sonar.server.rule.RuleService}
+ * Will be removed in the future.
  */
 public class DefaultRuleFinder implements RuleFinder {
 
index a9060ea975daf3560851ce053f9f853df20c1c6c..76643f136a3d3e71813924f357c0c65d24652535 100644 (file)
@@ -77,9 +77,11 @@ public class RegisterRules implements Startable {
   private final Languages languages;
   private final System2 system2;
   private final OrganizationFlags organizationFlags;
+  private final WebServerRuleFinder webServerRuleFinder;
 
   public RegisterRules(RuleDefinitionsLoader defLoader, RuleActivator ruleActivator, DbClient dbClient, RuleIndexer ruleIndexer,
-    ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, OrganizationFlags organizationFlags) {
+    ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, OrganizationFlags organizationFlags,
+    WebServerRuleFinder webServerRuleFinder) {
     this.defLoader = defLoader;
     this.ruleActivator = ruleActivator;
     this.dbClient = dbClient;
@@ -88,6 +90,7 @@ public class RegisterRules implements Startable {
     this.languages = languages;
     this.system2 = system2;
     this.organizationFlags = organizationFlags;
+    this.webServerRuleFinder = webServerRuleFinder;
   }
 
   @Override
@@ -105,7 +108,7 @@ public class RegisterRules implements Startable {
             RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
             if (ruleDef.template() && orgsEnabled) {
               RuleDefinitionDto ruleDefinition = allRules.get(ruleKey);
-              if (ruleDefinition != null && ruleDefinition.getStatus() == RuleStatus.REMOVED)  {
+              if (ruleDefinition != null && ruleDefinition.getStatus() == RuleStatus.REMOVED) {
                 LOG.debug("Template rule {} kept removed, because organizations are enabled.", ruleKey);
                 allRules.remove(ruleKey);
               } else {
@@ -130,6 +133,8 @@ public class RegisterRules implements Startable {
       ruleIndexer.indexRuleDefinitions(keysToIndex);
       activeRuleIndexer.index(changes);
       profiler.stopDebug();
+
+      webServerRuleFinder.startCaching();
     }
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java b/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java
new file mode 100644 (file)
index 0000000..8a5ba62
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import org.sonar.api.rules.RuleFinder;
+
+/**
+ * {@link RuleFinder} 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 RuleFinder {
+  /**
+   * Enable caching.
+   */
+  void startCaching();
+
+  /**
+   * Disable caching.
+   */
+  void stopCaching();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java
new file mode 100644 (file)
index 0000000..72b4e4d
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Collection;
+import javax.annotation.CheckForNull;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.db.DbClient;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+
+public class WebServerRuleFinderImpl implements WebServerRuleFinder {
+  private final DbClient dbClient;
+  private final RuleFinder defaultFinder;
+  @VisibleForTesting
+  RuleFinder delegate;
+
+  public WebServerRuleFinderImpl(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider) {
+    this.dbClient = dbClient;
+    this.defaultFinder = new DefaultRuleFinder(dbClient, defaultOrganizationProvider);
+    this.delegate = this.defaultFinder;
+  }
+
+  @Override
+  public void startCaching() {
+    this.delegate = new CachingRuleFinder(dbClient);
+  }
+
+  @Override
+  public void stopCaching() {
+    this.delegate = this.defaultFinder;
+  }
+
+  @Override
+  @CheckForNull
+  @Deprecated
+  public org.sonar.api.rules.Rule findById(int ruleId) {
+    return delegate.findById(ruleId);
+  }
+
+  @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);
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java
new file mode 100644 (file)
index 0000000..7399286
--- /dev/null
@@ -0,0 +1,423 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Consumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDao;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.db.rule.RuleTesting;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class CachingRuleFinderTest {
+  @org.junit.Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+
+  private AlwaysIncreasingSystem2 system2 = new AlwaysIncreasingSystem2();
+  private RuleDefinitionDto[] ruleDefinitions;
+  private RuleParamDto[] ruleParams;
+  private CachingRuleFinder underTest;
+
+  @Before()
+  public void setUp() throws Exception {
+    Consumer<RuleDefinitionDto> setUpdatedAt = rule -> rule.setUpdatedAt(system2.now());
+    this.ruleDefinitions = new RuleDefinitionDto[] {
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt)
+    };
+    this.ruleParams = Arrays.stream(ruleDefinitions)
+      .map(rule -> dbTester.rules().insertRuleParam(rule))
+      .toArray(RuleParamDto[]::new);
+
+    underTest = new CachingRuleFinder(dbClient);
+
+    // delete all data from DB to ensure tests rely on cache exclusively
+    dbTester.executeUpdateSql("delete from rules");
+    dbTester.executeUpdateSql("delete from rules_parameters");
+    assertThat(dbTester.countRowsOfTable("rules")).isZero();
+    assertThat(dbTester.countRowsOfTable("rules_parameters")).isZero();
+  }
+
+  @Test
+  public void constructor_reads_rules_from_DB() {
+    DbClient dbClient = mock(DbClient.class);
+    DbSession dbSession = mock(DbSession.class);
+    RuleDao ruleDao = mock(RuleDao.class);
+    when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
+    when(dbClient.ruleDao()).thenReturn(ruleDao);
+
+    new CachingRuleFinder(dbClient);
+
+    verify(dbClient).openSession(anyBoolean());
+    verify(ruleDao).selectAllDefinitions(dbSession);
+    verifyNoMoreInteractions(ruleDao);
+  }
+
+  @Test
+  public void constructor_reads_parameters_from_DB() {
+    DbClient dbClient = mock(DbClient.class);
+    DbSession dbSession = mock(DbSession.class);
+    RuleDao ruleDao = mock(RuleDao.class);
+    when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
+    when(dbClient.ruleDao()).thenReturn(ruleDao);
+    List<RuleKey> ruleKeys = Arrays.asList(RuleKey.of("A", "B"), RuleKey.of("C", "D"), RuleKey.of("E", "F"));
+    when(ruleDao.selectAllDefinitions(dbSession)).thenReturn(ruleKeys.stream().map(RuleTesting::newRule).collect(toList()));
+
+    new CachingRuleFinder(dbClient);
+
+    verify(ruleDao).selectRuleParamsByRuleKeys(dbSession, ImmutableSet.copyOf(ruleKeys));
+  }
+
+  @Test
+  public void findById_returns_all_loaded_rules_by_id() {
+    for (int i = 0; i < ruleDefinitions.length; i++) {
+      RuleDefinitionDto ruleDefinition = ruleDefinitions[i];
+      RuleParamDto ruleParam = ruleParams[i];
+
+      org.sonar.api.rules.Rule rule = underTest.findById(ruleDefinition.getId());
+      verifyRule(rule, ruleDefinition, ruleParam);
+    }
+  }
+
+  @Test
+  public void findById_returns_null_for_non_existing_id() {
+    assertThat(underTest.findById(new Random().nextInt())).isNull();
+  }
+
+  @Test
+  public void findByKey_returns_all_loaded_rules_by_id() {
+    for (int i = 0; i < ruleDefinitions.length; i++) {
+      RuleDefinitionDto ruleDefinition = ruleDefinitions[i];
+      RuleParamDto ruleParam = ruleParams[i];
+
+      org.sonar.api.rules.Rule rule = underTest.findByKey(ruleDefinition.getKey());
+      verifyRule(rule, ruleDefinition, ruleParam);
+      assertThat(underTest.findByKey(ruleDefinition.getRepositoryKey(), ruleDefinition.getRuleKey()))
+        .isSameAs(rule);
+    }
+  }
+
+  @Test
+  public void findByKey_returns_null_when_RuleKey_is_null() {
+    assertThat(underTest.findByKey(null)).isNull();
+  }
+
+  @Test
+  public void findByKey_returns_null_when_repository_key_is_null() {
+    assertThat(underTest.findByKey(null, randomAlphabetic(2))).isNull();
+  }
+
+  @Test
+  public void findByKey_returns_null_when_key_is_null() {
+    assertThat(underTest.findByKey(randomAlphabetic(2), null)).isNull();
+  }
+
+  @Test
+  public void findByKey_returns_null_when_both_repository_key_and_key_are_null() {
+    assertThat(underTest.findByKey(null, null)).isNull();
+  }
+
+  @Test
+  public void find_returns_null_when_RuleQuery_is_empty() {
+    assertThat(underTest.find(null)).isNull();
+  }
+
+  @Test
+  public void find_returns_most_recent_rule_when_RuleQuery_has_no_non_null_field() {
+    Rule rule = underTest.find(RuleQuery.create());
+
+    assertThat(toRuleKey(rule)).isEqualTo(ruleDefinitions[5].getKey());
+  }
+
+  @Test
+  public void find_searches_by_exact_match_of_repository_key_and_returns_most_recent_rule() {
+    String repoKey = "ABCD";
+    RuleDefinitionDto[] sameRepoKey = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now()))
+    };
+    RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(repoKey))))
+      .isEqualTo(sameRepoKey[1].getKey());
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey()))))
+      .isEqualTo(otherRule.getKey());
+    assertThat(underTest.find(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase())))
+      .isNull();
+    assertThat(underTest.find(RuleQuery.create().withRepositoryKey(randomAlphabetic(3))))
+      .isNull();
+  }
+
+  @Test
+  public void find_searches_by_exact_match_of_ruleKey_and_returns_most_recent_rule() {
+    String ruleKey = "ABCD";
+    RuleDefinitionDto[] sameRuleKey = {
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now()))
+    };
+    RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(ruleKey))))
+      .isEqualTo(sameRuleKey[1].getKey());
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(otherRule.getRuleKey()))))
+      .isEqualTo(otherRule.getKey());
+    assertThat(underTest.find(RuleQuery.create().withKey(ruleKey.toLowerCase())))
+      .isNull();
+    assertThat(underTest.find(RuleQuery.create().withKey(randomAlphabetic(3))))
+      .isNull();
+  }
+
+  @Test
+  public void find_searches_by_exact_match_of_configKey_and_returns_most_recent_rule() {
+    String configKey = "ABCD";
+    RuleDefinitionDto[] sameConfigKey = {
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now()))
+    };
+    RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(configKey))))
+      .isEqualTo(sameConfigKey[1].getKey());
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(otherRule.getConfigKey()))))
+      .isEqualTo(otherRule.getKey());
+    assertThat(underTest.find(RuleQuery.create().withConfigKey(configKey.toLowerCase())))
+      .isNull();
+    assertThat(underTest.find(RuleQuery.create().withConfigKey(randomAlphabetic(3))))
+      .isNull();
+  }
+
+  @Test
+  public void find_searches_by_exact_match_and_match_on_all_criterias_and_returns_most_recent_match() {
+    String repoKey = "ABCD";
+    String ruleKey = "EFGH";
+    String configKey = "IJKL";
+    RuleDefinitionDto[] rules = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()))
+    };
+    RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey);
+    RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey);
+    RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey);
+    RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey);
+    RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey);
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(toRuleKey(underTest.find(allQuery))).isEqualTo(rules[0].getKey());
+    assertThat(toRuleKey(underTest.find(ruleAndConfigKeyQuery))).isEqualTo(rules[1].getKey());
+    assertThat(toRuleKey(underTest.find(repoAndConfigKeyQuery))).isEqualTo(rules[2].getKey());
+    assertThat(toRuleKey(underTest.find(repoAndKeyQuery))).isEqualTo(rules[0].getKey());
+    assertThat(toRuleKey(underTest.find(repoKeyQuery))).isEqualTo(rules[2].getKey());
+    assertThat(toRuleKey(underTest.find(ruleKeyQuery))).isEqualTo(rules[1].getKey());
+    assertThat(toRuleKey(underTest.find(configKeyQuery))).isEqualTo(rules[2].getKey());
+  }
+
+  @Test
+  public void findAll_returns_empty_when_RuleQuery_is_empty() {
+    assertThat(underTest.findAll(null)).isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_when_RuleQuery_has_no_non_null_field() {
+    assertThat(underTest.findAll(RuleQuery.create()))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsOnly(Arrays.stream(ruleDefinitions).map(RuleDefinitionDto::getKey).toArray(RuleKey[]::new));
+  }
+
+  @Test
+  public void findAll_returns_all_rules_with_exact_same_repository_key_and_order_them_most_recent_first() {
+    String repoKey = "ABCD";
+    RuleDefinitionDto[] sameRepoKey = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now()))
+    };
+    RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey)))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(sameRepoKey[1].getKey(), sameRepoKey[0].getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey())))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(otherRule.getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase())))
+      .isEmpty();
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(randomAlphabetic(3))))
+      .isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_with_exact_same_rulekey_and_order_them_most_recent_first() {
+    String ruleKey = "ABCD";
+    RuleDefinitionDto[] sameRuleKey = {
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now()))
+    };
+    RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey)))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(sameRuleKey[1].getKey(), sameRuleKey[0].getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withKey(otherRule.getRuleKey())))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(otherRule.getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey.toLowerCase())))
+      .isEmpty();
+    assertThat(underTest.findAll(RuleQuery.create().withKey(randomAlphabetic(3))))
+      .isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_with_exact_same_configkey_and_order_them_most_recent_first() {
+    String configKey = "ABCD";
+    RuleDefinitionDto[] sameConfigKey = {
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now()))
+    };
+    RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey)))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(sameConfigKey[1].getKey(), sameConfigKey[0].getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(otherRule.getConfigKey())))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(otherRule.getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey.toLowerCase())))
+      .isEmpty();
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(randomAlphabetic(3))))
+      .isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_which_match_exactly_all_criteria_and_order_then_by_most_recent_first() {
+    String repoKey = "ABCD";
+    String ruleKey = "EFGH";
+    String configKey = "IJKL";
+    RuleDefinitionDto[] rules = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()))
+    };
+    RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey);
+    RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey);
+    RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey);
+    RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey);
+    RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey);
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient);
+
+    assertThat(underTest.findAll(allQuery))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(rules[0].getKey());
+    assertThat(underTest.findAll(ruleAndConfigKeyQuery))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(rules[1].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(repoAndConfigKeyQuery))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(rules[2].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(repoAndKeyQuery))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(rules[0].getKey());
+    assertThat(underTest.findAll(repoKeyQuery))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(rules[2].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(ruleKeyQuery))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(rules[1].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(configKeyQuery))
+      .extracting(CachingRuleFinderTest::toRuleKey)
+      .containsExactly(rules[2].getKey(), rules[1].getKey(), rules[0].getKey());
+  }
+
+  private static RuleKey toRuleKey(Rule rule) {
+    return RuleKey.of(rule.getRepositoryKey(), rule.getKey());
+  }
+
+  private void verifyRule(Rule rule, RuleDefinitionDto ruleDefinition, RuleParamDto ruleParam) {
+    assertThat(rule).isNotNull();
+
+    assertThat(rule.getName()).isEqualTo(ruleDefinition.getName());
+    assertThat(rule.getLanguage()).isEqualTo(ruleDefinition.getLanguage());
+    assertThat(rule.getKey()).isEqualTo(ruleDefinition.getRuleKey());
+    assertThat(rule.getConfigKey()).isEqualTo(ruleDefinition.getConfigKey());
+    assertThat(rule.isTemplate()).isEqualTo(ruleDefinition.isTemplate());
+    assertThat(rule.getCreatedAt().getTime()).isEqualTo(ruleDefinition.getCreatedAt());
+    assertThat(rule.getUpdatedAt().getTime()).isEqualTo(ruleDefinition.getUpdatedAt());
+    assertThat(rule.getRepositoryKey()).isEqualTo(ruleDefinition.getRepositoryKey());
+    assertThat(rule.getSeverity().name()).isEqualTo(ruleDefinition.getSeverityString());
+    assertThat(rule.getTags()).isEqualTo(ruleDefinition.getSystemTags().stream().toArray(String[]::new));
+    assertThat(rule.getId()).isEqualTo(ruleDefinition.getId());
+    assertThat(rule.getDescription()).isEqualTo(ruleDefinition.getDescription());
+
+    assertThat(rule.getParams()).hasSize(1);
+    org.sonar.api.rules.RuleParam param = rule.getParams().iterator().next();
+    assertThat(param.getRule()).isSameAs(rule);
+    assertThat(param.getKey()).isEqualTo(ruleParam.getName());
+    assertThat(param.getDescription()).isEqualTo(ruleParam.getDescription());
+    assertThat(param.getType()).isEqualTo(ruleParam.getType());
+    assertThat(param.getDefaultValue()).isEqualTo(ruleParam.getDefaultValue());
+  }
+}
index 47009c005b179b531bba5d0f720af24ceda4778f..b021ccab6a4b9c9c6aeaaa9a62177debb41acb75 100644 (file)
@@ -55,10 +55,12 @@ import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.rule.index.RuleQuery;
 
 import static com.google.common.collect.Sets.newHashSet;
-import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.rule.Severity.BLOCKER;
 import static org.sonar.api.rule.Severity.INFO;
@@ -83,6 +85,7 @@ public class RegisterRulesTest {
   public LogTester logTester = new LogTester();
 
   private RuleActivator ruleActivator = mock(RuleActivator.class);
+  private WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
   private DbClient dbClient = dbTester.getDbClient();
   private RuleIndexer ruleIndexer;
   private ActiveRuleIndexer activeRuleIndexer;
@@ -179,7 +182,7 @@ public class RegisterRulesTest {
   public void delete_repositories_that_have_been_uninstalled() {
     RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs");
     DbSession dbSession = dbTester.getSession();
-    dbTester.getDbClient().ruleRepositoryDao().insert(dbSession, asList(repository));
+    dbTester.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository));
     dbSession.commit();
 
     execute(new FakeRepositoryV1());
@@ -195,7 +198,6 @@ public class RegisterRulesTest {
 
     // user adds tags and sets markdown note
     OrganizationDto defaultOrganization = dbTester.getDefaultOrganization();
-    String organizationUuid = defaultOrganization.getUuid();
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1);
     rule1.setTags(newHashSet("usertag1", "usertag2"));
     rule1.setNoteData("user *note*");
@@ -260,7 +262,6 @@ public class RegisterRulesTest {
     });
 
     OrganizationDto defaultOrganization = dbTester.getDefaultOrganization();
-    String organizationUuid = defaultOrganization.getUuid();
     RuleDto rule = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1);
     assertThat(rule.getSystemTags()).containsOnly("tag1");
 
@@ -360,7 +361,6 @@ public class RegisterRulesTest {
     when(system.now()).thenReturn(DATE2.getTime());
     execute();
 
-    String organizationUuid = dbTester.getDefaultOrganization().getUuid();
     RuleDto rule = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1);
     assertThat(rule.getStatus()).isEqualTo(RuleStatus.REMOVED);
     assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isEqualTo(0);
@@ -484,11 +484,14 @@ public class RegisterRulesTest {
     RuleDefinitionsLoader loader = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), mock(CommonRuleDefinitionsImpl.class), defs);
     Languages languages = mock(Languages.class);
     when(languages.get("java")).thenReturn(mock(Language.class));
+    reset(webServerRuleFinder);
 
-    RegisterRules task = new RegisterRules(loader, ruleActivator, dbClient, ruleIndexer, activeRuleIndexer, languages, system, organizationFlags);
+    RegisterRules task = new RegisterRules(loader, ruleActivator, dbClient, ruleIndexer, activeRuleIndexer, languages, system, organizationFlags, webServerRuleFinder);
     task.start();
     // Execute a commit to refresh session state as the task is using its own session
     dbTester.getSession().commit();
+
+    verify(webServerRuleFinder).startCaching();
   }
 
   private RuleParamDto getParam(List<RuleParamDto> params, String key) {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/WebServerRuleFinderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/WebServerRuleFinderImplTest.java
new file mode 100644 (file)
index 0000000..dd4f957
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.db.DbClient;
+import org.sonar.db.rule.RuleDao;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class WebServerRuleFinderImplTest {
+
+  private DbClient dbClient = mock(DbClient.class);
+  private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.fromUuid("1111");
+  private WebServerRuleFinderImpl underTest = new WebServerRuleFinderImpl(dbClient, defaultOrganizationProvider);
+
+  @Before
+  public void setUp() throws Exception {
+    when(dbClient.ruleDao()).thenReturn(mock(RuleDao.class));
+  }
+
+  @Test
+  public void constructor_initializes_with_non_caching_delegate() {
+    assertThat(underTest.delegate).isInstanceOf(DefaultRuleFinder.class);
+  }
+
+  @Test
+  public void startCaching_sets_caching_delegate() {
+    underTest.startCaching();
+
+    assertThat(underTest.delegate).isInstanceOf(CachingRuleFinder.class);
+  }
+
+  @Test
+  public void stopCaching_restores_non_caching_delegate() {
+    RuleFinder nonCachingDelegate = underTest.delegate;
+
+    underTest.startCaching();
+    underTest.stopCaching();
+
+    assertThat(underTest.delegate).isSameAs(nonCachingDelegate);
+  }
+}