diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 8fe597c29422..08e83e1d6a15 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -257,6 +257,7 @@ 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 @@ protected void configureLevel() { org.sonar.server.rule.ws.ListAction.class, TagsAction.class, RuleMapping.class, + RuleMapper.class, ActiveRuleCompleter.class, RepositoriesAction.class, org.sonar.server.rule.ws.AppAction.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/db/ActiveRuleDao.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/db/ActiveRuleDao.java index ed8190c710bb..4e331f4c81b1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/db/ActiveRuleDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/db/ActiveRuleDao.java @@ -19,8 +19,15 @@ */ 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.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 { @@ -59,9 +64,6 @@ public ActiveRuleDao(QualityProfileDao profileDao, RuleDao ruleDao, System2 syst this.profileDao = profileDao; } - /** - * @deprecated do not use ids but keys - */ @CheckForNull @Deprecated public ActiveRuleDto selectById(DbSession session, int activeRuleId) { @@ -126,6 +128,23 @@ public List selectAllParams(DbSession dbSession) { return mapper(dbSession).selectAllParams(); } + public Optional selectByActiveRuleKey(DbSession dbSession, ActiveRuleKey key) { + return Optional.fromNullable(mapper(dbSession).selectByKey(key.qProfile(), key.ruleKey().repository(), key.ruleKey().rule())); + } + + public List selectByActiveRuleKeys(final DbSession dbSession, final List keys) { + if (keys.isEmpty()) { + return emptyList(); + } + + return DatabaseUtils.executeLargeInputs(keys, new Function, List>() { + @Override + public List apply(@Nonnull List input) { + return mapper(dbSession).selectByKeys(input); + } + }); + } + /** * Nested DTO ActiveRuleParams */ @@ -187,6 +206,23 @@ public List selectParamsByActiveRuleKey(DbSession session, A return mapper(session).selectParamsByActiveRuleId(activeRule.getId()); } + public List selectParamsByActiveRuleId(DbSession dbSession, Integer activeRuleId) { + return mapper(dbSession).selectParamsByActiveRuleId(activeRuleId); + } + + public List selectParamsByActiveRuleIds(final DbSession dbSession, List activeRuleIds) { + if (activeRuleIds.isEmpty()) { + return emptyList(); + } + + return DatabaseUtils.executeLargeInputs(activeRuleIds, new Function, List>() { + @Override + public List apply(@Nullable List 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); diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/ActiveRuleCompleter.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/ActiveRuleCompleter.java index ff36057894d4..7b5c496db024 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/ActiveRuleCompleter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/ActiveRuleCompleter.java @@ -19,19 +19,32 @@ */ 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 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 rules, SearchResponse.Builder searchResponse) { - Collection harvestedProfileKeys = writeActiveRules(searchResponse, query, rules); + void completeSearch(DbSession dbSession, RuleQuery query, List rules, SearchResponse.Builder searchResponse) { + Collection harvestedProfileKeys = writeActiveRules(dbSession, searchResponse, query, rules); searchResponse.setQProfiles(buildQProfiles(harvestedProfileKeys)); } - private Collection writeActiveRules(SearchResponse.Builder response, RuleQuery query, Collection rules) { + private Collection writeActiveRules(DbSession dbSession, SearchResponse.Builder response, RuleQuery query, Collection rules) { Collection 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 = dbClient.activeRuleDao().selectByActiveRuleKey(dbSession, activeRule.key()); + checkFoundWithOptional(activeRuleDto, "Active rule with key '%s' not found", activeRule.key().toString()); + List activeRuleParamDtos = dbClient.activeRuleDao().selectParamsByActiveRuleId(dbSession, activeRuleDto.get().getId()); + ListMultimap 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 activeRules = loader.findActiveRulesByRule(rule.getKey()); + List activeRuleDtos = dbClient.activeRuleDao().selectByActiveRuleKeys(dbSession, Lists.transform(activeRules, ActiveRuleToKey.INSTANCE)); + Map activeRuleIdsByKey = new HashMap<>(); + for (ActiveRuleDto activeRuleDto : activeRuleDtos) { + activeRuleIdsByKey.put(activeRuleDto.getId(), activeRuleDto.getKey()); + } + + List activeRuleParamDtos = dbClient.activeRuleDao().selectParamsByActiveRuleIds(dbSession, Lists.transform(activeRuleDtos, ActiveRuleDtoToId.INSTANCE)); + ListMultimap 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 @@ private Collection writeActiveRules(SearchResponse.Builder response, Rul void completeShow(Rule rule, ShowResponse.Builder response) { for (ActiveRule activeRule : loader.findActiveRulesByRule(rule.key())) { - response.addActives(buildActiveRuleResponse(activeRule)); + response.addActives(buildActiveRuleResponse(activeRule, Collections.emptyList())); } } - private static Collection writeActiveRules(RuleKey ruleKey, Collection activeRules, Rules.Actives.Builder activesBuilder) { + private static Collection writeActiveRules(RuleKey ruleKey, Collection activeRules, + ListMultimap activeRuleParamsByActiveRuleKey, Rules.Actives.Builder activesBuilder) { Collection 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 @@ private static Collection writeActiveRules(RuleKey ruleKey, Collection parameters) { Rules.Active.Builder activeRuleResponse = Rules.Active.newBuilder(); activeRuleResponse.setQProfile(activeRule.key().qProfile()); activeRuleResponse.setInherit(activeRule.inheritance().toString()); @@ -118,10 +155,10 @@ private static Rules.Active buildActiveRuleResponse(ActiveRule activeRule) { activeRuleResponse.setParent(parentKey.toString()); } Rules.Active.Param.Builder paramBuilder = Rules.Active.Param.newBuilder(); - for (Map.Entry 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 @@ private void writeProfile(Map profilesResponse, QualityP profilesResponse.put(profile.getKey(), profileResponse.build()); } + private enum ActiveRuleToKey implements Function { + INSTANCE; + + @Override + public ActiveRuleKey apply(@Nonnull ActiveRule input) { + return input.key(); + } + } + + private enum ActiveRuleDtoToId implements Function { + 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 index 000000000000..415cf1d34e99 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapper.java @@ -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 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 fieldsToReturn) { + if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.REPOSITORY)) { + ruleResponse.setRepo(ruleDto.getKey().repository()); + } + } + + private static void setEffortToFixDescription(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set 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 fieldsToReturn) { + if (shouldReturnField(fieldsToReturn, "debtOverloaded")) { + ruleResponse.setDebtOverloaded(ruleToOverloaded(ruleDto)); + } + } + + private static void setDefaultDebtRemediationFunctionFields(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set 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 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 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 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 fieldsToReturn) { + if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.TAGS)) { + ruleResponse.getTagsBuilder().addAllTags(ruleDto.getTags()); + } + } + + private static void setSysTags(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set 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 fieldsToReturn) { + if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.PARAMS)) { + List 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 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 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 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 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 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 fieldsToReturn) { + if (shouldReturnField(fieldsToReturn, RuleNormalizer.RuleField.LANGUAGE) && ruleDto.getLanguage() != null) { + ruleResponse.setLang(ruleDto.getLanguage()); + } + } + + private void setLanguageName(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set 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 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 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 fieldsToReturn, IndexField field) { + return fieldsToReturn.isEmpty() || fieldsToReturn.contains(field.field()); + } + + private static boolean shouldReturnField(Set 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 { + 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(); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java index 88219d1e768b..118a53e87691 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java @@ -114,8 +114,6 @@ private void mapParamFields() { public Rules.Rule buildRuleResponse(Rule ruleDoc, @Nullable QueryContext queryContext) { Rules.Rule.Builder ruleResponse = Rules.Rule.newBuilder(); - - RuleMappingContext context = new RuleMappingContext(); Set fieldsToReturn = fieldsToReturn(queryContext); ruleResponse.setKey(ruleDoc.key().toString()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java index e504a565e8ae..9d56a98f4262 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java @@ -19,36 +19,47 @@ */ 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.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 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 void define(WebService.NewController controller) { @Override public void handle(Request request, Response response) throws Exception { - QueryContext context = getQueryContext(request); - RuleQuery query = doQuery(request); - Result 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 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 @@ protected Collection possibleFacets() { RuleIndex.FACET_SEVERITIES, RuleIndex.FACET_ACTIVE_SEVERITIES, RuleIndex.FACET_STATUSES, - RuleIndex.FACET_OLD_DEFAULT - ); + RuleIndex.FACET_OLD_DEFAULT); } /** @@ -305,9 +324,9 @@ public static RuleQuery createRuleQuery(RuleQuery query, Request request) { return query; } - private void writeRules(SearchResponse.Builder response, Result 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 @@ private QueryContext loadCommonContext(Request request) { return context; } - protected Result doSearch(RuleQuery query, QueryContext context) { - return ruleService.search(query, context); + protected SearchResult doSearch(DbSession dbSession, RuleQuery query, QueryContext context) { + Result result = ruleIndex.search(query, context); + List ruleKeys = from(result.getHits()).transform(RuleToRuleKey.INSTANCE).toList(); + // rule order is managed by ES + Map rulesByRuleKey = Maps.uniqueIndex( + dbClient.ruleDao().selectByKeys(dbSession, ruleKeys), + new Function() { + @Override + public RuleKey apply(@Nonnull RuleDto input) { + return input.getKey(); + } + }); + List rules = new ArrayList<>(); + for (RuleKey ruleKey : ruleKeys) { + RuleDto rule = rulesByRuleKey.get(ruleKey); + if (rule != null) { + rules.add(rule); + } + } + List ruleIds = from(rules).transform(RuleDtoToId.INSTANCE).toList(); + List templateRuleIds = from(rules) + .transform(RuleDtoToTemplateId.INSTANCE) + .filter(Predicates.notNull()) + .toList(); + List templateRules = dbClient.ruleDao().selectByIds(dbSession, templateRuleIds); + List 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 @@ protected RuleQuery doQuery(Request request) { return plainQuery; } - protected void doContextResponse(Request request, Result 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 @@ protected Collection possibleFields() { 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 @@ protected void writeFacets(SearchResponse.Builder response, Request request, Que 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 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 @@ private static void addZeroFacetsForSelectedItems(Common.Facet.Builder facet, Re } } - protected void addMandatoryFacetValues(Result results, String facetName, @Nullable List mandatoryValues) { - Collection facetValues = results.getFacetValues(facetName); + protected void addMandatoryFacetValues(SearchResult results, String facetName, @Nullable List mandatoryValues) { + Collection facetValues = results.facets.getFacetValues(facetName); if (facetValues != null) { Map valuesByItem = Maps.newHashMap(); for (FacetValue value : facetValues) { @@ -430,4 +477,88 @@ protected void addMandatoryFacetValues(Result results, String facetName, @Nul } } } + + static class SearchResult { + private List rules; + private final ListMultimap ruleParamsByRuleId; + private final Map templateRulesByRuleId; + private final long total; + private final Facets facets; + + public SearchResult(Result result) { + this.rules = new ArrayList<>(); + this.ruleParamsByRuleId = ArrayListMultimap.create(); + this.templateRulesByRuleId = new HashMap<>(); + this.total = result.getTotal(); + this.facets = result.getFacetsObject(); + } + + public List getRules() { + return rules; + } + + public SearchResult setRules(List rules) { + this.rules = rules; + return this; + } + + public ListMultimap getRuleParamsByRuleId() { + return ruleParamsByRuleId; + } + + public SearchResult setRuleParams(List ruleParams) { + ruleParamsByRuleId.clear(); + for (RuleParamDto ruleParam : ruleParams) { + ruleParamsByRuleId.put(ruleParam.getRuleId(), ruleParam); + } + return this; + } + + public Map getTemplateRulesByRuleId() { + return templateRulesByRuleId; + } + + public SearchResult setTemplateRules(List 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 { + INSTANCE; + + @Override + public Integer apply(@Nonnull RuleDto input) { + return input.getId(); + } + } + + private enum RuleDtoToTemplateId implements Function { + INSTANCE; + + @Override + public Integer apply(@Nonnull RuleDto input) { + return input.getTemplateId(); + } + } + + private enum RuleToRuleKey implements Function { + INSTANCE; + + @Override + public RuleKey apply(@Nonnull Rule input) { + return input.key(); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/Facets.java b/server/sonar-server/src/main/java/org/sonar/server/search/Facets.java index 0ca124905e8d..6b65220e09cc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/Facets.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/Facets.java @@ -35,7 +35,7 @@ import java.util.List; import java.util.Map; -class Facets { +public class Facets { private static final Logger LOGGER = Loggers.get(Facets.class); diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/Result.java b/server/sonar-server/src/main/java/org/sonar/server/search/Result.java index 518a3c6edf11..408eacaef996 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/Result.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/Result.java @@ -73,6 +73,10 @@ public Map> getFacets() { return this.facets.getFacets(); } + public Facets getFacetsObject() { + return this.facets; + } + @CheckForNull public Collection getFacetValues(String facetName) { return this.facets.getFacetValues(facetName); diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionMediumTest.java index 2805464f2af3..fefef67bca2c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionMediumTest.java @@ -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.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 void return_lang_name_field() throws Exception { @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 void search_debt_rules_with_default_and_overridden_debt_values() throws E .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 void search_debt_rules_with_default_linear_offset_and_overridden_constant .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 void search_debt_rules_with_default_linear_offset_and_overridden_linear_d .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 void search_template_rules() throws Exception { 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); diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java index 6064dc232428..3077a4e80a28 100644 --- a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java +++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java @@ -27,7 +27,7 @@ * * @since 4.4 */ -public class ActiveRuleKey implements Serializable { +public class ActiveRuleKey implements Serializable, Comparable { private final String qualityProfileKey; private final RuleKey ruleKey; @@ -104,4 +104,13 @@ public int hashCode() { 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; + } } diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java index d243a7b667a7..d3c67a8abe1a 100644 --- a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java @@ -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 selectByKeys(@Param("keys") List keys); + List selectByRuleId(int ruleId); List selectByProfileKey(String key); @@ -55,13 +59,11 @@ public interface ActiveRuleMapper { List selectParamsByActiveRuleId(int activeRuleId); - List selectParamsByProfileKey(String profileKey); + List selectParamsByActiveRuleIds(@Param("ids") List ids); - ActiveRuleDto selectByKey(@Param("profileKey") String profileKey, - @Param("repository") String repository, @Param("rule") String rule); + List selectParamsByProfileKey(String profileKey); List selectAllParams(); List selectAfterDate(@Nullable Timestamp date); - } diff --git a/sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java b/sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java index 9e85c6ea90b3..4cb73663198d 100644 --- a/sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java +++ b/sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java @@ -23,12 +23,15 @@ 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 Optional selectById(long id, DbSession session) { return Optional.fromNullable(mapper(session).selectById(id)); } + public List selectByIds(final DbSession dbSession, List ids) { + if (ids.isEmpty()) { + return emptyList(); + } + return DatabaseUtils.executeLargeInputs(ids, new Function, List>() { + @Override + public List apply(@Nullable List 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 List selectAll(DbSession session) { return mapper(session).selectAll(); } + public List selectRuleParamsByRuleIds(final DbSession dbSession, List ruleIds) { + if (ruleIds.isEmpty()) { + return emptyList(); + } + return DatabaseUtils.executeLargeInputs(ruleIds, new Function, List>() { + @Override + public List apply(@Nonnull List input) { + return mapper(dbSession).selectParamsByRuleIds(input); + } + }); + } + public void insert(DbSession session, RuleDto dto) { mapper(session).insert(dto); } diff --git a/sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java b/sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java index b0e5b9acc916..6471993d00cb 100644 --- a/sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java @@ -42,6 +42,8 @@ public interface RuleMapper { RuleDto selectById(long id); + List selectByIds(@Param("ruleIds") List ids); + RuleDto selectByKey(RuleKey ruleKey); List selectByKeys(@Param("ruleKeys") List keys); diff --git a/sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml b/sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml index a85bf7e31dd3..c542ac5cb8e6 100644 --- a/sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml @@ -89,6 +89,19 @@ AND r.plugin_name = #{repository} + + diff --git a/sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml b/sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml index bd3a3b6ac062..74c3901a9885 100644 --- a/sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml @@ -65,6 +65,15 @@ from rules r WHERE r.id=#{id} + +