From d639a965bce7acafb004906cd07a8f0b5f7af993 Mon Sep 17 00:00:00 2001 From: Pierre Guillot Date: Tue, 10 Dec 2024 15:29:09 +0100 Subject: SONAR-22998 fetch active rules with a dedicated endpoint Co-authored-by: Julien HENRY --- .../sonar/db/qualityprofile/ActiveRuleDaoIT.java | 74 +++++++- .../org/sonar/db/qualityprofile/ActiveRuleDao.java | 15 +- .../org/sonar/db/qualityprofile/ActiveRuleDto.java | 51 ++++- .../sonar/db/qualityprofile/ActiveRuleMapper.java | 4 +- .../sonar/db/qualityprofile/QualityProfileDao.java | 7 +- .../src/main/java/org/sonar/db/rule/RuleDto.java | 6 +- .../sonar/db/qualityprofile/ActiveRuleMapper.xml | 39 +++- .../org/sonar/server/rule/ActiveRuleServiceIT.java | 206 ++++++++++++++++++++ .../sonar/server/rule/ActiveRuleRestReponse.java | 123 ++++++++++++ .../org/sonar/server/rule/ActiveRuleService.java | 177 ++++++++++++++++++ .../org/sonar/server/rule/CachingRuleFinder.java | 1 - .../java/org/sonar/server/v2/WebApiEndpoints.java | 1 + .../analysis/controller/ActiveRulesController.java | 47 +++++ .../controller/DefaultActiveRulesController.java | 38 ++++ .../api/analysis/service/ActiveRulesHandler.java | 27 +++ .../analysis/service/ActiveRulesHandlerImpl.java | 47 +++++ .../server/v2/config/PlatformLevel4WebConfig.java | 20 ++ .../DefaultActiveRulesControllerTest.java | 107 +++++++++++ .../service/ActiveRulesHandlerImplTest.java | 71 +++++++ .../java/org/sonar/core/rule/ImpactFormatter.java | 1 + .../api/batch/rule/internal/NewActiveRule.java | 14 ++ .../mediumtest/bootstrap/BootstrapMediumIT.java | 9 +- .../scanner/mediumtest/issues/ChecksMediumIT.java | 3 +- .../scanner/mediumtest/issues/IssuesMediumIT.java | 6 +- .../language/DefaultLanguagesLoader.java | 4 +- .../org/sonar/scanner/rule/ActiveRulesLoader.java | 2 +- .../sonar/scanner/rule/ActiveRulesProvider.java | 51 +---- .../scanner/rule/DefaultActiveRulesLoader.java | 173 ++++++++--------- .../org/sonar/scanner/rule/LoadedActiveRule.java | 12 +- .../scanner/rule/ActiveRulesProviderTest.java | 69 +++---- .../scanner/rule/DefaultActiveRulesLoaderTest.java | 208 +++++++++++++-------- .../scanner/mediumtest/ScannerMediumTester.java | 4 + 32 files changed, 1319 insertions(+), 298 deletions(-) create mode 100644 server/sonar-server-common/src/it/java/org/sonar/server/rule/ActiveRuleServiceIT.java create mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleRestReponse.java create mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleService.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ActiveRulesController.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesController.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandler.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImpl.java create mode 100644 server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesControllerTest.java create mode 100644 server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImplTest.java diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java index b229d7895b6..eeebb9d5b9e 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java @@ -62,6 +62,7 @@ import static org.sonar.api.rule.Severity.MAJOR; import static org.sonar.db.qualityprofile.ActiveRuleDto.INHERITED; import static org.sonar.db.qualityprofile.ActiveRuleDto.OVERRIDES; import static org.sonar.db.qualityprofile.ActiveRuleDto.createFor; +import static org.sonar.db.rule.RuleDto.deserializeSecurityStandardsString; class ActiveRuleDaoIT { @@ -112,6 +113,19 @@ class ActiveRuleDaoIT { rule2Param1 = db.rules().insertRuleParam(rule2); } + @Test + void selectAllParamsByProfileUuids() { + ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER); + underTest.insert(dbSession, activeRule); + ActiveRuleParamDto activeRuleParam1 = ActiveRuleParamDto.createFor(rule1Param1); + underTest.insertParam(dbSession, activeRule, activeRuleParam1); + ActiveRuleParamDto activeRuleParam2 = ActiveRuleParamDto.createFor(rule1Param2); + underTest.insertParam(dbSession, activeRule, activeRuleParam2); + dbSession.commit(); + + assertThat(underTest.selectAllParamsByProfileUuids(dbSession, List.of(profile1.getKee()))).hasSize(2); + } + @Test void selectByKey() { ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER); @@ -183,6 +197,50 @@ class ActiveRuleDaoIT { assertThat(underTest.selectByProfile(dbSession, profile1)).isEmpty(); } + @Test + void selectByProfileUuids_ignores_removed_rules() { + ActiveRuleDto activeRule = createFor(profile1, removedRule).setSeverity(BLOCKER); + underTest.insert(dbSession, activeRule); + + assertThat(underTest.selectByProfileUuids(dbSession, List.of(profile1.getKee()))).isEmpty(); + } + + @Test + void selectByProfileUuids_exclude_other_profiles() { + ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER); + underTest.insert(dbSession, activeRule); + ActiveRuleDto activeRule2 = createFor(profile2, rule2).setSeverity(BLOCKER); + underTest.insert(dbSession, activeRule2); + + assertThat(underTest.selectByProfileUuids(dbSession, List.of(profile1.getKee()))).hasSize(1); + } + + @Test + void selectByProfileUuids_returns_all_fields() { + ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER); + underTest.insert(dbSession, activeRule); + + List actualActiveRule = underTest.selectByProfileUuids(dbSession, List.of(profile1.getKee())); + // verify data from the "rules" table + assertThat(actualActiveRule) + .extracting(OrgActiveRuleDto::getRuleKey, org -> deserializeSecurityStandardsString(org.getSecurityStandards()), OrgActiveRuleDto::isExternal, OrgActiveRuleDto::getName, + OrgActiveRuleDto::getConfigKey, OrgActiveRuleDto::getTemplateUuid, OrgActiveRuleDto::getLanguage) + .containsExactly( + tuple(rule1.getKey(), rule1.getSecurityStandards(), rule1.isExternal(), rule1.getName(), rule1.getConfigKey(), rule1.getTemplateUuid(), rule1.getLanguage())); + + // verify data from "active_rules" table + assertThat(actualActiveRule) + .extracting(OrgActiveRuleDto::getUuid, OrgActiveRuleDto::getProfileUuid, OrgActiveRuleDto::getRuleUuid, OrgActiveRuleDto::getSeverity, OrgActiveRuleDto::getInheritance, + OrgActiveRuleDto::isPrioritizedRule, OrgActiveRuleDto::getCreatedAt, OrgActiveRuleDto::getUpdatedAt) + .containsExactly(tuple(activeRule.getUuid(), activeRule.getProfileUuid(), activeRule.getRuleUuid(), activeRule.getSeverity(), activeRule.getInheritance(), + activeRule.isPrioritizedRule(), activeRule.getCreatedAt(), activeRule.getUpdatedAt())); + + // verify data from "rules_profiles" and "org_qprofiles" + assertThat(actualActiveRule) + .extracting(o -> o.getKey().getRuleProfileUuid(), OrgActiveRuleDto::getOrgProfileUuid) + .containsExactly(tuple(activeRule.getKey().getRuleProfileUuid(), profile1.getKee())); + } + @Test void selectByTypeAndProfileUuids() { RuleDto rule1 = db.rules().insert(r -> r.setType(RuleType.VULNERABILITY.getDbConstant())); @@ -192,8 +250,8 @@ class ActiveRuleDaoIT { assertThat(underTest.selectByTypeAndProfileUuids(dbSession, singletonList(RuleType.VULNERABILITY.getDbConstant()), singletonList(profile1.getKee()))) - .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid) - .contains(tuple(profile1.getKee(), rule1.getUuid())); + .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid) + .contains(tuple(profile1.getKee(), rule1.getUuid())); } @Test @@ -205,7 +263,7 @@ class ActiveRuleDaoIT { assertThat(underTest.selectByTypeAndProfileUuids(dbSession, singletonList(RuleType.VULNERABILITY.getDbConstant()), singletonList(profile1.getKee()))) - .isEmpty(); + .isEmpty(); } @Test @@ -219,14 +277,14 @@ class ActiveRuleDaoIT { underTest.selectByTypeAndProfileUuids(dbSession, singletonList(RuleType.VULNERABILITY.getDbConstant()), singletonList(profile1.getKee()))) - .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid) - .contains(tuple(profile1.getKee(), rule1.getUuid())); + .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid) + .contains(tuple(profile1.getKee(), rule1.getUuid())); assertThat( underTest.selectByTypeAndProfileUuids(dbSession, asList(RuleType.CODE_SMELL.getDbConstant(), RuleType.SECURITY_HOTSPOT.getDbConstant(), RuleType.BUG.getDbConstant()), singletonList(profile1.getKee()))) - .isEmpty(); + .isEmpty(); } @Test @@ -684,10 +742,10 @@ class ActiveRuleDaoIT { ActiveRuleCountQuery.Builder builder = ActiveRuleCountQuery.builder(); assertThat(underTest.countActiveRulesByQuery(dbSession, builder.setProfiles(asList(profile1, profile2)).setInheritance(OVERRIDES).build())) - .containsOnly(entry(profile1.getKee(), 1L), entry(profile2.getKee(), 1L)); + .containsOnly(entry(profile1.getKee(), 1L), entry(profile2.getKee(), 1L)); assertThat(underTest.countActiveRulesByQuery(dbSession, builder.setProfiles(asList(profile1, profile2)).setInheritance(INHERITED).build())) - .containsOnly(entry(profile2.getKee(), 1L)); + .containsOnly(entry(profile2.getKee(), 1L)); } @Test diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java index e085a3cc315..76c9ae977ef 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java @@ -84,8 +84,15 @@ public class ActiveRuleDao implements Dao { /** * Active rule on removed rule are NOT returned */ - public List selectByProfileUuid(DbSession dbSession, String uuid) { - return mapper(dbSession).selectByProfileUuid(uuid); + public List selectByProfileUuid(DbSession dbSession, String profileUuid) { + return selectByProfileUuids(dbSession, List.of(profileUuid)); + } + + /** + * Active rule on removed rule are NOT returned + */ + public List selectByProfileUuids(DbSession dbSession, Collection profileUuids) { + return mapper(dbSession).selectByProfileUuids(profileUuids); } public List selectByTypeAndProfileUuids(DbSession dbSession, List types, List uuids) { @@ -162,6 +169,10 @@ public class ActiveRuleDao implements Dao { return executeLargeInputs(activeRuleUuids, mapper(dbSession)::selectParamsByActiveRuleUuids); } + public List selectAllParamsByProfileUuids(final DbSession dbSession, Collection profileUuids) { + return mapper(dbSession).selectAllParamsByProfileUuids(profileUuids); + } + public ActiveRuleParamDto insertParam(DbSession dbSession, ActiveRuleDto activeRule, ActiveRuleParamDto activeRuleParam) { checkArgument(activeRule.getUuid() != null, ACTIVE_RULE_IS_NOT_PERSISTED); checkArgument(activeRuleParam.getUuid() == null, ACTIVE_RULE_PARAM_IS_ALREADY_PERSISTED); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java index 906cdb4a2c7..483ed6e34a9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java @@ -58,10 +58,14 @@ public class ActiveRuleDto { // These fields do not exist in db, it's only retrieve by joins private String repository; private String ruleField; - private String ruleProfileUuid; private String securityStandards; private boolean isExternal; + private String name; + private String configKey; + private String templateUuid; + private String language; + public ActiveRuleDto() { // nothing to do here } @@ -69,12 +73,12 @@ public class ActiveRuleDto { public ActiveRuleDto setKey(ActiveRuleKey key) { this.repository = key.getRuleKey().repository(); this.ruleField = key.getRuleKey().rule(); - this.ruleProfileUuid = key.getRuleProfileUuid(); + this.profileUuid = key.getRuleProfileUuid(); return this; } public ActiveRuleKey getKey() { - return new ActiveRuleKey(ruleProfileUuid, RuleKey.of(repository, ruleField)); + return new ActiveRuleKey(profileUuid, RuleKey.of(repository, ruleField)); } public RuleKey getRuleKey() { @@ -207,6 +211,43 @@ public class ActiveRuleDto { return this; } + public String getName() { + return name; + } + + public ActiveRuleDto setName(String name) { + this.name = name; + return this; + } + + public String getConfigKey() { + return configKey; + } + + public ActiveRuleDto setConfigKey(String configKey) { + this.configKey = configKey; + return this; + } + + @CheckForNull + public String getTemplateUuid() { + return templateUuid; + } + + public ActiveRuleDto setTemplateUuid(@Nullable String templateUuid) { + this.templateUuid = templateUuid; + return this; + } + + public String getLanguage() { + return language; + } + + public ActiveRuleDto setLanguage(String language) { + this.language = language; + return this; + } + public static ActiveRuleDto createFor(QProfileDto profile, RuleDto ruleDto) { requireNonNull(profile.getRulesProfileUuid(), "Profile is not persisted"); requireNonNull(ruleDto.getUuid(), "Rule is not persisted"); @@ -214,6 +255,10 @@ public class ActiveRuleDto { dto.setProfileUuid(profile.getRulesProfileUuid()); dto.setRuleUuid(ruleDto.getUuid()); dto.setKey(ActiveRuleKey.of(profile, ruleDto.getKey())); + dto.setName(ruleDto.getName()); + dto.setConfigKey(ruleDto.getConfigKey()); + dto.setTemplateUuid(ruleDto.getTemplateUuid()); + dto.setLanguage(ruleDto.getLanguage()); dto.setImpacts(ruleDto.getDefaultImpactsMap()); return dto; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java index d2b2dddfdbe..af2d2dcaa98 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java @@ -52,7 +52,7 @@ public interface ActiveRuleMapper { List selectByRuleUuids(@Param("ruleUuids") List partitionOfRuleUuids); - List selectByProfileUuid(String uuid); + List selectByProfileUuids(@Param("profileUuids") Collection profileUuids); List selectByTypeAndProfileUuids(@Param("types") List types, @Param("profileUuids") List uuids); @@ -90,4 +90,6 @@ public interface ActiveRuleMapper { void scrollByRuleProfileUuidForIndexing(@Param("ruleProfileUuid") String ruleProfileUuid, ResultHandler handler); int countMissingRules(@Param("rulesProfileUuid") String rulesProfileUuid, @Param("compareToRulesProfileUuid") String compareToRulesProfileUuid); + + List selectAllParamsByProfileUuids(@Param("profileUuids") Collection profileUuids); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java index f9d0982e4b4..34d9bd3ea71 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java @@ -168,7 +168,11 @@ public class QualityProfileDao implements Dao { } public List selectAssociatedToProjectAndLanguages(DbSession dbSession, ProjectDto project, Collection languages) { - return executeLargeInputs(languages, partition -> mapper(dbSession).selectAssociatedToProjectUuidAndLanguages(project.getUuid(), partition)); + return selectAssociatedToProjectAndLanguages(dbSession, project.getUuid(), languages); + } + + public List selectAssociatedToProjectAndLanguages(DbSession dbSession, String projectUuid, Collection languages) { + return executeLargeInputs(languages, partition -> mapper(dbSession).selectAssociatedToProjectUuidAndLanguages(projectUuid, partition)); } public List selectQProfilesByProjectUuid(DbSession dbSession, String projectUuid) { @@ -251,6 +255,7 @@ public class QualityProfileDao implements Dao { public List selectAllProjectAssociations(DbSession dbSession) { return mapper(dbSession).selectAllProjectAssociations(); } + public Collection selectUuidsOfCustomRulesProfiles(DbSession dbSession, String language, String name) { return mapper(dbSession).selectUuidsOfCustomRuleProfiles(language, name); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java index 6d1f544e076..722f131695b 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java @@ -72,7 +72,7 @@ public class RuleDto { private RuleDto.Format descriptionFormat = null; private RuleStatus status = null; - private Set defaultImpacts = new HashSet<>(); + private final Set defaultImpacts = new HashSet<>(); private String name = null; private String configKey = null; @@ -375,7 +375,6 @@ public class RuleDto { return this; } - @CheckForNull public String getLanguage() { return language; } @@ -650,13 +649,12 @@ public class RuleDto { @Override public boolean equals(Object obj) { - if (!(obj instanceof RuleDto)) { + if (!(obj instanceof RuleDto other)) { return false; } if (this == obj) { return true; } - RuleDto other = (RuleDto) obj; return Objects.equals(this.uuid, other.uuid); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml index 10442858528..c6facb12ff6 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml @@ -26,14 +26,19 @@ a.inheritance as "inheritance", a.impacts as "impactsString", a.prioritized_rule as "prioritizedRule", + a.created_at as "createdAt", + a.updated_at as "updatedAt", + r.plugin_rule_key as "rulefield", r.plugin_name as "repository", r.security_standards as "securityStandards", - rp.uuid as "ruleProfileUuid", - a.created_at as "createdAt", - a.updated_at as "updatedAt", - oqp.uuid as "orgProfileUuid", - r.is_external as "isExternal" + r.is_external as "isExternal", + r.name as "name", + r.plugin_config_key as "configKey", + r.template_uuid as "templateUuid", + r.language as "language", + + oqp.uuid as "orgProfileUuid" @@ -113,14 +118,18 @@ and r.plugin_name = #{repository, jdbcType=VARCHAR} - select from active_rules a inner join rules_profiles rp on rp.uuid = a.profile_uuid inner join org_qprofiles oqp on oqp.rules_profile_uuid = rp.uuid inner join rules r on r.uuid = a.rule_uuid and r.status != 'REMOVED' - where oqp.uuid = #{id, jdbcType=VARCHAR} + where + oqp.uuid in + + #{profileUuid, jdbcType=VARCHAR} + + +