]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7330 WS api/rules/search apply ES+DB pattern
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 25 Feb 2016 11:14:21 +0000 (12:14 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 26 Feb 2016 09:07:22 +0000 (10:07 +0100)
15 files changed:
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/db/ActiveRuleDao.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/ActiveRuleCompleter.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapper.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/search/Facets.java
server/sonar-server/src/main/java/org/sonar/server/search/Result.java
server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionMediumTest.java
sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java
sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java
sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java
sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java
sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml

index 8fe597c2942208cdcdb2e48c38de9a5ebe57afd9..08e83e1d6a15b8f60fdb698f4747924841c116dc 100644 (file)
@@ -257,6 +257,7 @@ import org.sonar.server.rule.RuleService;
 import org.sonar.server.rule.RuleUpdater;
 import org.sonar.server.rule.ws.ActiveRuleCompleter;
 import org.sonar.server.rule.ws.RepositoriesAction;
+import org.sonar.server.rule.ws.RuleMapper;
 import org.sonar.server.rule.ws.RuleMapping;
 import org.sonar.server.rule.ws.RulesWs;
 import org.sonar.server.rule.ws.TagsAction;
@@ -450,6 +451,7 @@ public class PlatformLevel4 extends PlatformLevel {
       org.sonar.server.rule.ws.ListAction.class,
       TagsAction.class,
       RuleMapping.class,
+      RuleMapper.class,
       ActiveRuleCompleter.class,
       RepositoriesAction.class,
       org.sonar.server.rule.ws.AppAction.class,
index ed8190c710bb7f11b27f40e936de252fce5df106..4e331f4c81b1018fdee2f1b5e4741927fe4a2338 100644 (file)
  */
 package org.sonar.server.qualityprofile.db;
 
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.sonar.api.utils.System2;
+import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbSession;
 import org.sonar.db.qualityprofile.ActiveRuleDto;
 import org.sonar.db.qualityprofile.ActiveRuleKey;
@@ -33,9 +40,7 @@ import org.sonar.server.db.BaseDao;
 import org.sonar.server.rule.db.RuleDao;
 import org.sonar.server.search.IndexDefinition;
 
-import javax.annotation.CheckForNull;
-
-import java.util.List;
+import static java.util.Collections.emptyList;
 
 public class ActiveRuleDao extends BaseDao<ActiveRuleMapper, ActiveRuleDto, ActiveRuleKey> {
 
@@ -59,9 +64,6 @@ public class ActiveRuleDao extends BaseDao<ActiveRuleMapper, ActiveRuleDto, Acti
     this.profileDao = profileDao;
   }
 
-  /**
-   * @deprecated do not use ids but keys
-   */
   @CheckForNull
   @Deprecated
   public ActiveRuleDto selectById(DbSession session, int activeRuleId) {
@@ -126,6 +128,23 @@ public class ActiveRuleDao extends BaseDao<ActiveRuleMapper, ActiveRuleDto, Acti
     return mapper(dbSession).selectAllParams();
   }
 
+  public Optional<ActiveRuleDto> selectByActiveRuleKey(DbSession dbSession, ActiveRuleKey key) {
+    return Optional.fromNullable(mapper(dbSession).selectByKey(key.qProfile(), key.ruleKey().repository(), key.ruleKey().rule()));
+  }
+
+  public List<ActiveRuleDto> selectByActiveRuleKeys(final DbSession dbSession, final List<ActiveRuleKey> keys) {
+    if (keys.isEmpty()) {
+      return emptyList();
+    }
+
+    return DatabaseUtils.executeLargeInputs(keys, new Function<List<ActiveRuleKey>, List<ActiveRuleDto>>() {
+      @Override
+      public List<ActiveRuleDto> apply(@Nonnull List<ActiveRuleKey> input) {
+        return mapper(dbSession).selectByKeys(input);
+      }
+    });
+  }
+
   /**
    * Nested DTO ActiveRuleParams
    */
@@ -187,6 +206,23 @@ public class ActiveRuleDao extends BaseDao<ActiveRuleMapper, ActiveRuleDto, Acti
     return mapper(session).selectParamsByActiveRuleId(activeRule.getId());
   }
 
+  public List<ActiveRuleParamDto> selectParamsByActiveRuleId(DbSession dbSession, Integer activeRuleId) {
+    return mapper(dbSession).selectParamsByActiveRuleId(activeRuleId);
+  }
+
+  public List<ActiveRuleParamDto> selectParamsByActiveRuleIds(final DbSession dbSession, List<Integer> activeRuleIds) {
+    if (activeRuleIds.isEmpty()) {
+      return emptyList();
+    }
+
+    return DatabaseUtils.executeLargeInputs(activeRuleIds, new Function<List<Integer>, List<ActiveRuleParamDto>>() {
+      @Override
+      public List<ActiveRuleParamDto> apply(@Nullable List<Integer> input) {
+        return mapper(dbSession).selectParamsByActiveRuleIds(input);
+      }
+    });
+  }
+
   @CheckForNull
   public ActiveRuleParamDto selectParamByKeyAndName(ActiveRuleKey key, String name, DbSession session) {
     Preconditions.checkNotNull(key, ACTIVE_RULE_KEY_CANNOT_BE_NULL);
index ff36057894d490e324f51bbec05a35e42588f567..7b5c496db02463a8c15010e7545e1683c37972b9 100644 (file)
  */
 package org.sonar.server.rule.ws;
 
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
 import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
 import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
 import org.sonar.db.qualityprofile.QualityProfileDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.db.DbClient;
 import org.sonar.server.qualityprofile.ActiveRule;
 import org.sonar.server.qualityprofile.QProfileLoader;
 import org.sonar.server.rule.Rule;
@@ -42,6 +55,8 @@ import org.sonarqube.ws.Rules.ShowResponse;
 
 import static com.google.common.base.Strings.nullToEmpty;
 import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.singletonList;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 
 /**
  * Add details about active rules to api/rules/search and api/rules/show
@@ -52,36 +67,57 @@ public class ActiveRuleCompleter {
 
   private static final Logger LOG = Loggers.get(ActiveRuleCompleter.class);
 
+  private final DbClient dbClient;
   private final QProfileLoader loader;
   private final Languages languages;
 
-  public ActiveRuleCompleter(QProfileLoader loader, Languages languages) {
+  public ActiveRuleCompleter(DbClient dbClient, QProfileLoader loader, Languages languages) {
+    this.dbClient = dbClient;
     this.loader = loader;
     this.languages = languages;
   }
 
-  void completeSearch(RuleQuery query, Collection<Rule> rules, SearchResponse.Builder searchResponse) {
-    Collection<String> harvestedProfileKeys = writeActiveRules(searchResponse, query, rules);
+  void completeSearch(DbSession dbSession, RuleQuery query, List<RuleDto> rules, SearchResponse.Builder searchResponse) {
+    Collection<String> harvestedProfileKeys = writeActiveRules(dbSession, searchResponse, query, rules);
     searchResponse.setQProfiles(buildQProfiles(harvestedProfileKeys));
   }
 
-  private Collection<String> writeActiveRules(SearchResponse.Builder response, RuleQuery query, Collection<Rule> rules) {
+  private Collection<String> writeActiveRules(DbSession dbSession, SearchResponse.Builder response, RuleQuery query, Collection<RuleDto> rules) {
     Collection<String> qProfileKeys = newHashSet();
     Rules.Actives.Builder activesBuilder = response.getActivesBuilder();
 
     String profileKey = query.getQProfileKey();
     if (profileKey != null) {
       // Load details of active rules on the selected profile
-      for (Rule rule : rules) {
-        ActiveRule activeRule = loader.getActiveRule(ActiveRuleKey.of(profileKey, rule.key()));
+      for (RuleDto rule : rules) {
+        ActiveRule activeRule = loader.getActiveRule(ActiveRuleKey.of(profileKey, rule.getKey()));
         if (activeRule != null) {
-          qProfileKeys = writeActiveRules(rule.key(), Arrays.asList(activeRule), activesBuilder);
+          Optional<ActiveRuleDto> activeRuleDto = dbClient.activeRuleDao().selectByActiveRuleKey(dbSession, activeRule.key());
+          checkFoundWithOptional(activeRuleDto, "Active rule with key '%s' not found", activeRule.key().toString());
+          List<ActiveRuleParamDto> activeRuleParamDtos = dbClient.activeRuleDao().selectParamsByActiveRuleId(dbSession, activeRuleDto.get().getId());
+          ListMultimap<ActiveRuleKey, ActiveRuleParamDto> activeRuleParamByActiveRuleKey = ArrayListMultimap.create(1, activeRuleParamDtos.size());
+          activeRuleParamByActiveRuleKey.putAll(activeRule.key(), activeRuleParamDtos);
+          qProfileKeys = writeActiveRules(rule.getKey(), singletonList(activeRule), activeRuleParamByActiveRuleKey, activesBuilder);
         }
       }
     } else {
       // Load details of all active rules
-      for (Rule rule : rules) {
-        qProfileKeys = writeActiveRules(rule.key(), loader.findActiveRulesByRule(rule.key()), activesBuilder);
+      for (RuleDto rule : rules) {
+        List<ActiveRule> activeRules = loader.findActiveRulesByRule(rule.getKey());
+        List<ActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByActiveRuleKeys(dbSession, Lists.transform(activeRules, ActiveRuleToKey.INSTANCE));
+        Map<Integer, ActiveRuleKey> activeRuleIdsByKey = new HashMap<>();
+        for (ActiveRuleDto activeRuleDto : activeRuleDtos) {
+          activeRuleIdsByKey.put(activeRuleDto.getId(), activeRuleDto.getKey());
+        }
+
+        List<ActiveRuleParamDto> activeRuleParamDtos = dbClient.activeRuleDao().selectParamsByActiveRuleIds(dbSession, Lists.transform(activeRuleDtos, ActiveRuleDtoToId.INSTANCE));
+        ListMultimap<ActiveRuleKey, ActiveRuleParamDto> activeRuleParamsByActiveRuleKey = ArrayListMultimap.create(activeRules.size(), 10);
+        for (ActiveRuleParamDto activeRuleParamDto : activeRuleParamDtos) {
+          ActiveRuleKey activeRuleKey = activeRuleIdsByKey.get(activeRuleParamDto.getId());
+          activeRuleParamsByActiveRuleKey.put(activeRuleKey, activeRuleParamDto);
+        }
+
+        qProfileKeys = writeActiveRules(rule.getKey(), activeRules, activeRuleParamsByActiveRuleKey, activesBuilder);
       }
     }
 
@@ -91,15 +127,16 @@ public class ActiveRuleCompleter {
 
   void completeShow(Rule rule, ShowResponse.Builder response) {
     for (ActiveRule activeRule : loader.findActiveRulesByRule(rule.key())) {
-      response.addActives(buildActiveRuleResponse(activeRule));
+      response.addActives(buildActiveRuleResponse(activeRule, Collections.<ActiveRuleParamDto>emptyList()));
     }
   }
 
-  private static Collection<String> writeActiveRules(RuleKey ruleKey, Collection<ActiveRule> activeRules, Rules.Actives.Builder activesBuilder) {
+  private static Collection<String> writeActiveRules(RuleKey ruleKey, Collection<ActiveRule> activeRules,
+    ListMultimap<ActiveRuleKey, ActiveRuleParamDto> activeRuleParamsByActiveRuleKey, Rules.Actives.Builder activesBuilder) {
     Collection<String> qProfileKeys = newHashSet();
     Rules.ActiveList.Builder activeRulesListResponse = Rules.ActiveList.newBuilder();
     for (ActiveRule activeRule : activeRules) {
-      activeRulesListResponse.addActiveList(buildActiveRuleResponse(activeRule));
+      activeRulesListResponse.addActiveList(buildActiveRuleResponse(activeRule, activeRuleParamsByActiveRuleKey.get(activeRule.key())));
       qProfileKeys.add(activeRule.key().qProfile());
     }
     activesBuilder
@@ -108,7 +145,7 @@ public class ActiveRuleCompleter {
     return qProfileKeys;
   }
 
-  private static Rules.Active buildActiveRuleResponse(ActiveRule activeRule) {
+  private static Rules.Active buildActiveRuleResponse(ActiveRule activeRule, List<ActiveRuleParamDto> parameters) {
     Rules.Active.Builder activeRuleResponse = Rules.Active.newBuilder();
     activeRuleResponse.setQProfile(activeRule.key().qProfile());
     activeRuleResponse.setInherit(activeRule.inheritance().toString());
@@ -118,10 +155,10 @@ public class ActiveRuleCompleter {
       activeRuleResponse.setParent(parentKey.toString());
     }
     Rules.Active.Param.Builder paramBuilder = Rules.Active.Param.newBuilder();
-    for (Map.Entry<String, String> param : activeRule.params().entrySet()) {
+    for (ActiveRuleParamDto parameter : parameters) {
       activeRuleResponse.addParams(paramBuilder.clear()
-        .setKey(param.getKey())
-        .setValue(nullToEmpty(param.getValue())));
+        .setKey(parameter.getKey())
+        .setValue(nullToEmpty(parameter.getValue())));
     }
 
     return activeRuleResponse.build();
@@ -176,4 +213,22 @@ public class ActiveRuleCompleter {
     profilesResponse.put(profile.getKey(), profileResponse.build());
   }
 
+  private enum ActiveRuleToKey implements Function<ActiveRule, ActiveRuleKey> {
+    INSTANCE;
+
+    @Override
+    public ActiveRuleKey apply(@Nonnull ActiveRule input) {
+      return input.key();
+    }
+  }
+
+  private enum ActiveRuleDtoToId implements Function<ActiveRuleDto, Integer> {
+    INSTANCE;
+
+    @Override
+    public Integer apply(@Nonnull ActiveRuleDto input) {
+      return input.getId();
+    }
+  }
+
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapper.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
new file mode 100644 (file)
index 0000000..415cf1d
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.ws;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.markdown.Markdown;
+import org.sonar.server.rule.index.RuleNormalizer;
+import org.sonar.server.rule.ws.SearchAction.SearchResult;
+import org.sonar.server.search.IndexField;
+import org.sonar.server.text.MacroInterpreter;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.Rules;
+
+import static java.lang.String.format;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+
+/**
+ * Conversion of {@link org.sonar.db.rule.RuleDto} to {@link org.sonarqube.ws.Rules.Rule}
+ */
+public class RuleMapper {
+
+  private final Languages languages;
+  private final MacroInterpreter macroInterpreter;
+
+  public RuleMapper(final Languages languages, final MacroInterpreter macroInterpreter) {
+    this.languages = languages;
+    this.macroInterpreter = macroInterpreter;
+  }
+
+  public Rules.Rule toWsRule(RuleDto ruleDto, SearchResult result, Set<String> fieldsToReturn) {
+    Rules.Rule.Builder ruleResponse = Rules.Rule.newBuilder();
+
+    ruleResponse.setKey(ruleDto.getKey().toString());
+    setRepository(ruleResponse, ruleDto, fieldsToReturn);
+    setName(ruleResponse, ruleDto, fieldsToReturn);
+    setStatus(ruleResponse, ruleDto, fieldsToReturn);
+    setTags(ruleResponse, ruleDto, fieldsToReturn);
+    setSysTags(ruleResponse, ruleDto, fieldsToReturn);
+    setParams(ruleResponse, ruleDto, result, fieldsToReturn);
+    setCreatedAt(ruleResponse, ruleDto, fieldsToReturn);
+    setDescriptionFields(ruleResponse, ruleDto, fieldsToReturn);
+    setNotesFields(ruleResponse, ruleDto, fieldsToReturn);
+    setSeverity(ruleResponse, ruleDto, fieldsToReturn);
+    setInternalKey(ruleResponse, ruleDto, fieldsToReturn);
+    setLanguage(ruleResponse, ruleDto, fieldsToReturn);
+    setLanguageName(ruleResponse, ruleDto, fieldsToReturn);
+    setIsTemplate(ruleResponse, ruleDto, fieldsToReturn);
+    setTemplateKey(ruleResponse, ruleDto, result, fieldsToReturn);
+    setDebtRemediationFunctionFields(ruleResponse, ruleDto, fieldsToReturn);
+    setDefaultDebtRemediationFunctionFields(ruleResponse, ruleDto, fieldsToReturn);
+    setIsDebtOverloaded(ruleResponse, ruleDto, fieldsToReturn);
+    setEffortToFixDescription(ruleResponse, ruleDto, fieldsToReturn);
+
+    return ruleResponse.build();
+  }
+
+  private static void setRepository(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.REPOSITORY)) {
+      ruleResponse.setRepo(ruleDto.getKey().repository());
+    }
+  }
+
+  private static void setEffortToFixDescription(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.FIX_DESCRIPTION) && ruleDto.getEffortToFixDescription() != null) {
+      ruleResponse.setEffortToFixDescription(ruleDto.getEffortToFixDescription());
+    }
+  }
+
+  private static void setIsDebtOverloaded(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, "debtOverloaded")) {
+      ruleResponse.setDebtOverloaded(ruleToOverloaded(ruleDto));
+    }
+  }
+
+  private static void setDefaultDebtRemediationFunctionFields(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, "defaultDebtRemFn")) {
+      DebtRemediationFunction defaultDebtRemediationFunction = defaultDebtRemediationFunction(ruleDto);
+      if (defaultDebtRemediationFunction != null) {
+        if (defaultDebtRemediationFunction.coefficient() != null) {
+          ruleResponse.setDefaultDebtRemFnCoeff(defaultDebtRemediationFunction.coefficient());
+        }
+        if (defaultDebtRemediationFunction.offset() != null) {
+          ruleResponse.setDefaultDebtRemFnOffset(defaultDebtRemediationFunction.offset());
+        }
+        if (defaultDebtRemediationFunction.type() != null) {
+          ruleResponse.setDefaultDebtRemFnType(defaultDebtRemediationFunction.type().name());
+        }
+      }
+    }
+  }
+
+  private static void setDebtRemediationFunctionFields(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, "debtRemFn")) {
+      DebtRemediationFunction debtRemediationFunction = debtRemediationFunction(ruleDto);
+      if (debtRemediationFunction != null) {
+        if (debtRemediationFunction.type() != null) {
+          ruleResponse.setDebtRemFnType(debtRemediationFunction.type().name());
+        }
+        if (debtRemediationFunction.coefficient() != null) {
+          ruleResponse.setDebtRemFnCoeff(debtRemediationFunction.coefficient());
+        }
+        if (debtRemediationFunction.offset() != null) {
+          ruleResponse.setDebtRemFnOffset(debtRemediationFunction.offset());
+        }
+      }
+    }
+  }
+
+  private static void setName(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.NAME) && ruleDto.getName() != null) {
+      ruleResponse.setName(ruleDto.getName());
+    }
+  }
+
+  private static void setStatus(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.STATUS) && ruleDto.getStatus() != null) {
+      ruleResponse.setStatus(Common.RuleStatus.valueOf(ruleDto.getStatus().toString()));
+    }
+  }
+
+  private static void setTags(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.TAGS)) {
+      ruleResponse.getTagsBuilder().addAllTags(ruleDto.getTags());
+    }
+  }
+
+  private static void setSysTags(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.SYSTEM_TAGS)) {
+      ruleResponse.getSysTagsBuilder().addAllSysTags(ruleDto.getSystemTags());
+    }
+  }
+
+  private static void setParams(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, SearchResult searchResult, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.PARAMS)) {
+      List<RuleParamDto> ruleParameters = searchResult.getRuleParamsByRuleId().get(ruleDto.getId());
+      ruleResponse.getParamsBuilder().addAllParams(FluentIterable.from(ruleParameters)
+        .transform(RuleParamDtoToWsRuleParam.INSTANCE)
+        .toList());
+    }
+  }
+
+  private static void setCreatedAt(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.CREATED_AT) && ruleDto.getCreatedAt() != null) {
+      ruleResponse.setCreatedAt(formatDateTime(ruleDto.getCreatedAt()));
+    }
+  }
+
+  private void setDescriptionFields(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.HTML_DESCRIPTION)) {
+      String description = ruleDto.getDescription();
+      if (description != null) {
+        switch (ruleDto.getDescriptionFormat()) {
+          case MARKDOWN:
+            ruleResponse.setHtmlDesc(macroInterpreter.interpret(Markdown.convertToHtml(description)));
+            break;
+          case HTML:
+            ruleResponse.setHtmlDesc(macroInterpreter.interpret(description));
+            break;
+          default:
+            throw new IllegalStateException(format("Rule description format '%s' is unknown for key '%s'", ruleDto.getDescriptionFormat(), ruleDto.getKey().toString()));
+        }
+      }
+    }
+
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.MARKDOWN_DESCRIPTION) && ruleDto.getDescription() != null) {
+      ruleResponse.setMdDesc(ruleDto.getDescription());
+    }
+  }
+
+  private void setNotesFields(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, "htmlNote") && ruleDto.getNoteData() != null) {
+      ruleResponse.setHtmlNote(macroInterpreter.interpret(Markdown.convertToHtml(ruleDto.getNoteData())));
+    }
+    if (shouldReturnField(fieldsToReturn, "mdNote") && ruleDto.getNoteData() != null) {
+      ruleResponse.setMdNote(ruleDto.getNoteData());
+    }
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.NOTE_LOGIN) && ruleDto.getNoteUserLogin() != null) {
+      ruleResponse.setNoteLogin(ruleDto.getNoteUserLogin());
+    }
+  }
+
+  private static void setSeverity(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.SEVERITY) && ruleDto.getSeverityString() != null) {
+      ruleResponse.setSeverity(ruleDto.getSeverityString());
+    }
+  }
+
+  private static void setInternalKey(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.INTERNAL_KEY) && ruleDto.getConfigKey() != null) {
+      ruleResponse.setInternalKey(ruleDto.getConfigKey());
+    }
+  }
+
+  private static void setLanguage(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.LANGUAGE) && ruleDto.getLanguage() != null) {
+      ruleResponse.setLang(ruleDto.getLanguage());
+    }
+  }
+
+  private void setLanguageName(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, "langName") && ruleDto.getLanguage() != null) {
+      String languageKey = ruleDto.getLanguage();
+      Language language = languages.get(languageKey);
+      ruleResponse.setLangName(language == null ? languageKey : language.getName());
+    }
+  }
+
+  private static void setIsTemplate(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.IS_TEMPLATE)) {
+      ruleResponse.setIsTemplate(ruleDto.isTemplate());
+    }
+  }
+
+  private static void setTemplateKey(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, SearchResult result, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.TEMPLATE_KEY) && ruleDto.getTemplateId() != null) {
+      RuleDto templateRule = result.getTemplateRulesByRuleId().get(ruleDto.getTemplateId());
+      if (templateRule != null) {
+        ruleResponse.setTemplateKey(templateRule.getKey().toString());
+      }
+    }
+  }
+
+  private static boolean shouldReturnField(Set<String> fieldsToReturn, IndexField field) {
+    return fieldsToReturn.isEmpty() || fieldsToReturn.contains(field.field());
+  }
+
+  private static boolean shouldReturnField(Set<String> fieldsToReturn, String fieldName) {
+    return fieldsToReturn.isEmpty() || fieldsToReturn.contains(fieldName);
+  }
+
+  private static boolean ruleToOverloaded(RuleDto rule) {
+    return rule.getRemediationFunction() != null;
+  }
+
+  private static DebtRemediationFunction defaultDebtRemediationFunction(final RuleDto ruleDto) {
+    final String function = ruleDto.getDefaultRemediationFunction();
+    if (function == null || function.isEmpty()) {
+      return null;
+    } else {
+      return new DebtRemediationFunction() {
+        @Override
+        public Type type() {
+          return Type.valueOf(function.toUpperCase());
+        }
+
+        @Override
+        public String coefficient() {
+          return ruleDto.getDefaultRemediationCoefficient();
+        }
+
+        @Override
+        public String offset() {
+          return ruleDto.getDefaultRemediationOffset();
+        }
+      };
+    }
+  }
+
+  private static DebtRemediationFunction debtRemediationFunction(final RuleDto ruleDto) {
+    final String function = ruleDto.getRemediationFunction();
+    if (function == null || function.isEmpty()) {
+      return null;
+    } else {
+      return new DebtRemediationFunction() {
+        @Override
+        public Type type() {
+          return Type.valueOf(function.toUpperCase());
+        }
+
+        @Override
+        public String coefficient() {
+          return ruleDto.getRemediationCoefficient();
+        }
+
+        @Override
+        public String offset() {
+          return ruleDto.getRemediationOffset();
+        }
+      };
+    }
+  }
+
+  private enum RuleParamDtoToWsRuleParam implements Function<RuleParamDto, Rules.Rule.Param> {
+    INSTANCE;
+
+    @Override
+    public Rules.Rule.Param apply(@Nonnull RuleParamDto param) {
+      Rules.Rule.Param.Builder paramResponse = Rules.Rule.Param.newBuilder();
+      paramResponse.setKey(param.getName());
+      if (param.getDescription() != null) {
+        paramResponse.setHtmlDesc(Markdown.convertToHtml(param.getDescription()));
+      }
+      if (param.getDefaultValue() != null) {
+        paramResponse.setDefaultValue(param.getDefaultValue());
+      }
+      if (param.getType() != null) {
+        paramResponse.setType(param.getType());
+      }
+
+      return paramResponse.build();
+    }
+  }
+}
index 88219d1e768b378e76023a75dba5e482c74040a7..118a53e876919c01a4af500a32d60c7af87c2109 100644 (file)
@@ -114,8 +114,6 @@ public class RuleMapping extends BaseMapping<RuleDoc, RuleMappingContext> {
 
   public Rules.Rule buildRuleResponse(Rule ruleDoc, @Nullable QueryContext queryContext) {
     Rules.Rule.Builder ruleResponse = Rules.Rule.newBuilder();
-
-    RuleMappingContext context = new RuleMappingContext();
     Set<String> fieldsToReturn = fieldsToReturn(queryContext);
 
     ruleResponse.setKey(ruleDoc.key().toString());
index e504a565e8aef029c8c19939e624ef7a5b54ad38..9d56a98f4262723e83b35bf0ac76d2d792c278e9 100644 (file)
  */
 package org.sonar.server.rule.ws;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.io.Resources;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.server.debt.DebtCharacteristic;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
 import org.sonar.db.qualityprofile.QualityProfileDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
 import org.sonar.server.qualityprofile.ActiveRule;
 import org.sonar.server.rule.Rule;
-import org.sonar.server.rule.RuleService;
 import org.sonar.server.rule.index.RuleIndex;
 import org.sonar.server.rule.index.RuleNormalizer;
 import org.sonar.server.rule.index.RuleQuery;
 import org.sonar.server.search.FacetValue;
+import org.sonar.server.search.Facets;
 import org.sonar.server.search.QueryContext;
 import org.sonar.server.search.Result;
 import org.sonar.server.search.ws.SearchOptions;
@@ -56,6 +67,7 @@ import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Rules.SearchResponse;
 
+import static com.google.common.collect.FluentIterable.from;
 import static org.sonar.server.search.QueryContext.MAX_LIMIT;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 
@@ -83,16 +95,20 @@ public class SearchAction implements RulesWsAction {
 
   private static final Collection<String> DEFAULT_FACETS = ImmutableSet.of(PARAM_LANGUAGES, PARAM_REPOSITORIES, "tags");
 
-  private final RuleService ruleService;
+  private final UserSession userSession;
+  private final DbClient dbClient;
+  private final RuleIndex ruleIndex;
   private final ActiveRuleCompleter activeRuleCompleter;
   private final RuleMapping mapping;
-  private final UserSession userSession;
+  private final RuleMapper mapper;
 
-  public SearchAction(RuleService service, ActiveRuleCompleter activeRuleCompleter, RuleMapping mapping, UserSession userSession) {
+  public SearchAction(RuleIndex ruleIndex, ActiveRuleCompleter activeRuleCompleter, RuleMapping mapping, UserSession userSession, DbClient dbClient, RuleMapper mapper) {
     this.userSession = userSession;
-    this.ruleService = service;
+    this.ruleIndex = ruleIndex;
     this.activeRuleCompleter = activeRuleCompleter;
     this.mapping = mapping;
+    this.dbClient = dbClient;
+    this.mapper = mapper;
   }
 
   @Override
@@ -124,26 +140,30 @@ public class SearchAction implements RulesWsAction {
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    QueryContext context = getQueryContext(request);
-    RuleQuery query = doQuery(request);
-    Result<Rule> result = doSearch(query, context);
-
-    SearchResponse responseBuilder = buildResponse(request, context, result);
-    writeProtobuf(responseBuilder, request, response);
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      QueryContext context = getQueryContext(request);
+      RuleQuery query = doQuery(request);
+      SearchResult searchResult = doSearch(dbSession, query, context);
+      SearchResponse responseBuilder = buildResponse(dbSession, request, context, searchResult);
+      writeProtobuf(responseBuilder, request, response);
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
   }
 
-  private SearchResponse buildResponse(Request request, QueryContext context, Result<Rule> result) {
+  private SearchResponse buildResponse(DbSession dbSession, Request request, QueryContext context, SearchResult result) {
     SearchResponse.Builder responseBuilder = SearchResponse.newBuilder();
     writeStatistics(responseBuilder, result, context);
-    doContextResponse(request, result, responseBuilder);
+    doContextResponse(dbSession, request, result, responseBuilder);
     if (context.isFacet()) {
       writeFacets(responseBuilder, request, context, result);
     }
     return responseBuilder.build();
   }
 
-  protected void writeStatistics(SearchResponse.Builder response, Result searchResult, QueryContext context) {
-    response.setTotal(searchResult.getTotal());
+  protected void writeStatistics(SearchResponse.Builder response, SearchResult searchResult, QueryContext context) {
+    response.setTotal(searchResult.total);
     response.setP(context.getPage());
     response.setPs(context.getLimit());
   }
@@ -167,8 +187,7 @@ public class SearchAction implements RulesWsAction {
       RuleIndex.FACET_SEVERITIES,
       RuleIndex.FACET_ACTIVE_SEVERITIES,
       RuleIndex.FACET_STATUSES,
-      RuleIndex.FACET_OLD_DEFAULT
-      );
+      RuleIndex.FACET_OLD_DEFAULT);
   }
 
   /**
@@ -305,9 +324,9 @@ public class SearchAction implements RulesWsAction {
     return query;
   }
 
-  private void writeRules(SearchResponse.Builder response, Result<Rule> result, QueryContext context) {
-    for (Rule rule : result.getHits()) {
-      response.addRules(mapping.buildRuleResponse(rule, context));
+  private void writeRules(SearchResponse.Builder response, SearchResult result, QueryContext context) {
+    for (RuleDto rule : result.rules) {
+      response.addRules(mapper.toWsRule(rule, result, context.getFieldsToReturn()));
     }
   }
 
@@ -341,12 +360,40 @@ public class SearchAction implements RulesWsAction {
     return context;
   }
 
-  protected Result<Rule> doSearch(RuleQuery query, QueryContext context) {
-    return ruleService.search(query, context);
+  protected SearchResult doSearch(DbSession dbSession, RuleQuery query, QueryContext context) {
+    Result<Rule> result = ruleIndex.search(query, context);
+    List<RuleKey> ruleKeys = from(result.getHits()).transform(RuleToRuleKey.INSTANCE).toList();
+    // rule order is managed by ES
+    Map<RuleKey, RuleDto> rulesByRuleKey = Maps.uniqueIndex(
+      dbClient.ruleDao().selectByKeys(dbSession, ruleKeys),
+      new Function<RuleDto, RuleKey>() {
+        @Override
+        public RuleKey apply(@Nonnull RuleDto input) {
+          return input.getKey();
+        }
+      });
+    List<RuleDto> rules = new ArrayList<>();
+    for (RuleKey ruleKey : ruleKeys) {
+      RuleDto rule = rulesByRuleKey.get(ruleKey);
+      if (rule != null) {
+        rules.add(rule);
+      }
+    }
+    List<Integer> ruleIds = from(rules).transform(RuleDtoToId.INSTANCE).toList();
+    List<Integer> templateRuleIds = from(rules)
+      .transform(RuleDtoToTemplateId.INSTANCE)
+      .filter(Predicates.<Integer>notNull())
+      .toList();
+    List<RuleDto> templateRules = dbClient.ruleDao().selectByIds(dbSession, templateRuleIds);
+    List<RuleParamDto> ruleParamDtos = dbClient.ruleDao().selectRuleParamsByRuleIds(dbSession, ruleIds);
+    return new SearchResult(result)
+      .setRules(rules)
+      .setRuleParams(ruleParamDtos)
+      .setTemplateRules(templateRules);
   }
 
   protected RuleQuery doQuery(Request request) {
-    RuleQuery plainQuery = createRuleQuery(ruleService.newRuleQuery(), request);
+    RuleQuery plainQuery = createRuleQuery(new RuleQuery(), request);
 
     String qProfileKey = request.param(PARAM_QPROFILE);
     if (qProfileKey != null) {
@@ -359,12 +406,12 @@ public class SearchAction implements RulesWsAction {
     return plainQuery;
   }
 
-  protected void doContextResponse(Request request, Result<Rule> result, SearchResponse.Builder response) {
+  protected void doContextResponse(DbSession dbSession, Request request, SearchResult result, SearchResponse.Builder response) {
     // TODO Get rid of this horrible hack: fields on request are not the same as fields for ES search ! 2/2
     QueryContext contextForResponse = loadCommonContext(request);
     writeRules(response, result, contextForResponse);
     if (contextForResponse.getFieldsToReturn().contains("actives")) {
-      activeRuleCompleter.completeSearch(doQuery(request), result.getHits(), response);
+      activeRuleCompleter.completeSearch(dbSession, doQuery(request), result.rules, response);
     }
   }
 
@@ -374,7 +421,7 @@ public class SearchAction implements RulesWsAction {
     return builder.add("actives").build();
   }
 
-  protected void writeFacets(SearchResponse.Builder response, Request request, QueryContext context, Result<?> results) {
+  protected void writeFacets(SearchResponse.Builder response, Request request, QueryContext context, SearchResult results) {
     addMandatoryFacetValues(results, RuleIndex.FACET_LANGUAGES, request.paramAsStrings(PARAM_LANGUAGES));
     addMandatoryFacetValues(results, RuleIndex.FACET_REPOSITORIES, request.paramAsStrings(PARAM_REPOSITORIES));
     addMandatoryFacetValues(results, RuleIndex.FACET_STATUSES, RuleIndex.ALL_STATUSES_EXCEPT_REMOVED);
@@ -386,9 +433,9 @@ public class SearchAction implements RulesWsAction {
     Common.FacetValue.Builder value = Common.FacetValue.newBuilder();
     for (String facetName : context.facets()) {
       facet.clear().setProperty(facetName);
-      if (results.getFacets().containsKey(facetName)) {
+      if (results.facets.getFacets().containsKey(facetName)) {
         Set<String> itemsFromFacets = Sets.newHashSet();
-        for (FacetValue facetValue : results.getFacets().get(facetName)) {
+        for (FacetValue facetValue : results.facets.getFacets().get(facetName)) {
           itemsFromFacets.add(facetValue.getKey());
           facet.addValues(value
             .clear()
@@ -415,8 +462,8 @@ public class SearchAction implements RulesWsAction {
     }
   }
 
-  protected void addMandatoryFacetValues(Result<?> results, String facetName, @Nullable List<String> mandatoryValues) {
-    Collection<FacetValue> facetValues = results.getFacetValues(facetName);
+  protected void addMandatoryFacetValues(SearchResult results, String facetName, @Nullable List<String> mandatoryValues) {
+    Collection<FacetValue> facetValues = results.facets.getFacetValues(facetName);
     if (facetValues != null) {
       Map<String, Long> valuesByItem = Maps.newHashMap();
       for (FacetValue value : facetValues) {
@@ -430,4 +477,88 @@ public class SearchAction implements RulesWsAction {
       }
     }
   }
+
+  static class SearchResult {
+    private List<RuleDto> rules;
+    private final ListMultimap<Integer, RuleParamDto> ruleParamsByRuleId;
+    private final Map<Integer, RuleDto> templateRulesByRuleId;
+    private final long total;
+    private final Facets facets;
+
+    public SearchResult(Result<Rule> result) {
+      this.rules = new ArrayList<>();
+      this.ruleParamsByRuleId = ArrayListMultimap.create();
+      this.templateRulesByRuleId = new HashMap<>();
+      this.total = result.getTotal();
+      this.facets = result.getFacetsObject();
+    }
+
+    public List<RuleDto> getRules() {
+      return rules;
+    }
+
+    public SearchResult setRules(List<RuleDto> rules) {
+      this.rules = rules;
+      return this;
+    }
+
+    public ListMultimap<Integer, RuleParamDto> getRuleParamsByRuleId() {
+      return ruleParamsByRuleId;
+    }
+
+    public SearchResult setRuleParams(List<RuleParamDto> ruleParams) {
+      ruleParamsByRuleId.clear();
+      for (RuleParamDto ruleParam : ruleParams) {
+        ruleParamsByRuleId.put(ruleParam.getRuleId(), ruleParam);
+      }
+      return this;
+    }
+
+    public Map<Integer, RuleDto> getTemplateRulesByRuleId() {
+      return templateRulesByRuleId;
+    }
+
+    public SearchResult setTemplateRules(List<RuleDto> templateRules) {
+      templateRulesByRuleId.clear();
+      for (RuleDto templateRule : templateRules) {
+        templateRulesByRuleId.put(templateRule.getId(), templateRule);
+      }
+      return this;
+    }
+
+    public long getTotal() {
+      return total;
+    }
+
+    public Facets getFacets() {
+      return facets;
+    }
+  }
+
+  private enum RuleDtoToId implements Function<RuleDto, Integer> {
+    INSTANCE;
+
+    @Override
+    public Integer apply(@Nonnull RuleDto input) {
+      return input.getId();
+    }
+  }
+
+  private enum RuleDtoToTemplateId implements Function<RuleDto, Integer> {
+    INSTANCE;
+
+    @Override
+    public Integer apply(@Nonnull RuleDto input) {
+      return input.getTemplateId();
+    }
+  }
+
+  private enum RuleToRuleKey implements Function<Rule, RuleKey> {
+    INSTANCE;
+
+    @Override
+    public RuleKey apply(@Nonnull Rule input) {
+      return input.key();
+    }
+  }
 }
index 0ca124905e8dc8340fdc3e92c3c0c51e8c3309be..6b65220e09cc65b197e96ab0db7972249720b51b 100644 (file)
@@ -35,7 +35,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-class Facets {
+public class Facets {
 
   private static final Logger LOGGER = Loggers.get(Facets.class);
 
index 518a3c6edf110f3cbdc1f954946453ee7f7a8d3d..408eacaef996f6acada47366d03e4c7255632342 100644 (file)
@@ -73,6 +73,10 @@ public class Result<K> {
     return this.facets.getFacets();
   }
 
+  public Facets getFacetsObject() {
+    return this.facets;
+  }
+
   @CheckForNull
   public Collection<FacetValue> getFacetValues(String facetName) {
     return this.facets.getFacetValues(facetName);
index 2805464f2af30254606dad80be80d7fd6b0c7c23..fefef67bca2c7f98b2ba9c5965f7d1d0e2fc7527 100644 (file)
@@ -20,6 +20,9 @@
 package org.sonar.server.rule.ws;
 
 import com.google.common.collect.ImmutableSet;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
@@ -48,10 +51,6 @@ import org.sonar.server.tester.ServerTester;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsTester;
 
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class SearchActionMediumTest {
@@ -166,7 +165,7 @@ public class SearchActionMediumTest {
 
   @Test
   public void return_lang_key_field_when_language_name_is_not_available() throws Exception {
-    ruleDao.insert(dbSession, RuleTesting.newDto(RuleKey.of("other", "rule"))).setLanguage("unknown");
+    ruleDao.insert(dbSession, RuleTesting.newDto(RuleKey.of("other", "rule")).setLanguage("unknown"));
     dbSession.commit();
 
     WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD).setParam(WebService.Param.FIELDS, "langName");
@@ -184,8 +183,7 @@ public class SearchActionMediumTest {
       .setDefaultRemediationOffset("15min")
       .setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
       .setRemediationCoefficient("2h")
-      .setRemediationOffset("25min")
-      );
+      .setRemediationOffset("25min"));
     dbSession.commit();
 
     WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
@@ -202,8 +200,7 @@ public class SearchActionMediumTest {
       .setDefaultRemediationOffset("15min")
       .setRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE.name())
       .setRemediationCoefficient(null)
-      .setRemediationOffset("5min")
-      );
+      .setRemediationOffset("5min"));
     dbSession.commit();
 
     WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
@@ -220,8 +217,7 @@ public class SearchActionMediumTest {
       .setDefaultRemediationOffset("15min")
       .setRemediationFunction(DebtRemediationFunction.Type.LINEAR.name())
       .setRemediationCoefficient("1h")
-      .setRemediationOffset(null)
-      );
+      .setRemediationOffset(null));
     dbSession.commit();
 
     WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
@@ -248,7 +244,7 @@ public class SearchActionMediumTest {
   public void search_custom_rules_from_template_key() throws Exception {
     RuleDto templateRule = RuleTesting.newXooX1().setIsTemplate(true);
     ruleDao.insert(dbSession, templateRule);
-    ruleDao.insert(dbSession, RuleTesting.newXooX2()).setTemplateId(templateRule.getId());
+    ruleDao.insert(dbSession, RuleTesting.newXooX2().setTemplateId(templateRule.getId()));
     dbSession.commit();
 
     WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD);
index 6064dc2324280241a9c49fb86c8f18c3b89030b3..3077a4e80a28c8ba5a3926adfefa9584150aa6ca 100644 (file)
@@ -27,7 +27,7 @@ import org.sonar.api.rule.RuleKey;
  *
  * @since 4.4
  */
-public class ActiveRuleKey implements Serializable {
+public class ActiveRuleKey implements Serializable, Comparable<ActiveRuleKey> {
 
   private final String qualityProfileKey;
   private final RuleKey ruleKey;
@@ -104,4 +104,13 @@ public class ActiveRuleKey implements Serializable {
   public String toString() {
     return String.format("%s:%s", qualityProfileKey, ruleKey.toString());
   }
+
+  @Override
+  public int compareTo(ActiveRuleKey o) {
+    int compareQualityProfileKey = this.qualityProfileKey.compareTo(o.qualityProfileKey);
+    if (compareQualityProfileKey == 0) {
+      return this.ruleKey.compareTo(o.ruleKey);
+    }
+    return compareQualityProfileKey;
+  }
 }
index d243a7b667a7b70754256fdab674542e6630610b..d3c67a8abe1ae4bd62e82d93afabe617d024051b 100644 (file)
@@ -36,6 +36,10 @@ public interface ActiveRuleMapper {
   @CheckForNull
   ActiveRuleDto selectById(Integer id);
 
+  ActiveRuleDto selectByKey(@Param("profileKey") String profileKey, @Param("repository") String repository, @Param("rule") String rule);
+
+  List<ActiveRuleDto> selectByKeys(@Param("keys") List<ActiveRuleKey> keys);
+
   List<ActiveRuleDto> selectByRuleId(int ruleId);
 
   List<ActiveRuleDto> selectByProfileKey(String key);
@@ -55,13 +59,11 @@ public interface ActiveRuleMapper {
 
   List<ActiveRuleParamDto> selectParamsByActiveRuleId(int activeRuleId);
 
-  List<ActiveRuleParamDto> selectParamsByProfileKey(String profileKey);
+  List<ActiveRuleParamDto> selectParamsByActiveRuleIds(@Param("ids") List<Integer> ids);
 
-  ActiveRuleDto selectByKey(@Param("profileKey") String profileKey,
-    @Param("repository") String repository, @Param("rule") String rule);
+  List<ActiveRuleParamDto> selectParamsByProfileKey(String profileKey);
 
   List<ActiveRuleParamDto> selectAllParams();
 
   List<ActiveRuleDto> selectAfterDate(@Nullable Timestamp date);
-
 }
index 9e85c6ea90b3d4c1511bebeeeadf66667d1f33e7..4cb73663198ddbcb32118de9d76ddcaed6a9a238 100644 (file)
@@ -23,12 +23,15 @@ import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import java.util.List;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.apache.ibatis.session.ResultHandler;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.db.Dao;
+import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbSession;
 import org.sonar.db.RowNotFoundException;
 
+import static java.util.Collections.emptyList;
 import static org.sonar.db.DatabaseUtils.executeLargeInputs;
 
 public class RuleDao implements Dao {
@@ -54,6 +57,18 @@ public class RuleDao implements Dao {
     return Optional.fromNullable(mapper(session).selectById(id));
   }
 
+  public List<RuleDto> selectByIds(final DbSession dbSession, List<Integer> ids) {
+    if (ids.isEmpty()) {
+      return emptyList();
+    }
+    return DatabaseUtils.executeLargeInputs(ids, new Function<List<Integer>, List<RuleDto>>() {
+      @Override
+      public List<RuleDto> apply(@Nullable List<Integer> input) {
+        return mapper(dbSession).selectByIds(input);
+      }
+    });
+  }
+
   /**
    * Select rules by keys, whatever their status. Returns an empty list
    * if the list of {@code keys} is empty, without any db round trip.
@@ -74,6 +89,18 @@ public class RuleDao implements Dao {
     return mapper(session).selectAll();
   }
 
+  public List<RuleParamDto> selectRuleParamsByRuleIds(final DbSession dbSession, List<Integer> ruleIds) {
+    if (ruleIds.isEmpty()) {
+      return emptyList();
+    }
+    return DatabaseUtils.executeLargeInputs(ruleIds, new Function<List<Integer>, List<RuleParamDto>>() {
+      @Override
+      public List<RuleParamDto> apply(@Nonnull List<Integer> input) {
+        return mapper(dbSession).selectParamsByRuleIds(input);
+      }
+    });
+  }
+
   public void insert(DbSession session, RuleDto dto) {
     mapper(session).insert(dto);
   }
index b0e5b9acc91604253a92964ba171262404da791a..6471993d00cba77281f0fca494618328f75ec842 100644 (file)
@@ -42,6 +42,8 @@ public interface RuleMapper {
 
   RuleDto selectById(long id);
 
+  List<RuleDto> selectByIds(@Param("ruleIds") List<Integer> ids);
+
   RuleDto selectByKey(RuleKey ruleKey);
 
   List<RuleDto> selectByKeys(@Param("ruleKeys") List<RuleKey> keys);
index a85bf7e31dd339ac21faf7bf066e9c6c2a09bdae..c542ac5cb8e6b8fe07ed7c9db632e51671a5ae3c 100644 (file)
     AND r.plugin_name = #{repository}
   </select>
 
+  <select id="selectByKeys" parameterType="map" resultType="ActiveRule">
+    SELECT
+    <include refid="activeRuleKeyColumns"/>
+    FROM active_rules a
+    <include refid="activeRuleKeyJoin"/>
+    WHERE
+    <foreach collection="keys" item="key" open="(" separator=" or " close=")">
+      (qp.kee = #{key.qProfile()}
+      AND r.plugin_rule_key = #{key.ruleKey().rule()}
+      AND r.plugin_name = #{key.ruleKey().repository()})
+    </foreach>
+  </select>
+
   <select id="selectByProfileKey" parameterType="string" resultType="ActiveRule">
     SELECT
     <include refid="activeRuleKeyColumns"/>
     <include refid="activeRuleParamColumns"/>
     from active_rule_parameters p
     <where>
-      (<foreach collection="list" item="id" open="(" separator=" or " close=")">
-      p.active_rule_id=#{id}
-    </foreach>)
+      <foreach collection="ids" item="id" open="(" separator=" or " close=")">
+        p.active_rule_id=#{id}
+    </foreach>
     </where>
   </select>
 
index bd3a3b6ac0623edc81ae0975763e2ceec427f1e3..74c3901a98854b1a3b888f87a56e66990344dfbd 100644 (file)
     from rules r WHERE r.id=#{id}
   </select>
 
+  <select id="selectByIds" parameterType="Integer" resultType="Rule">
+    select
+    <include refid="selectColumns"/>
+    from rules r WHERE r.id in
+    <foreach collection="ruleIds" index="index" item="id" open="(" separator="," close=")">
+      #{id}
+    </foreach>
+  </select>
+
   <select id="selectByKey" parameterType="map" resultType="Rule">
     SELECT
     <include refid="selectColumns"/>