diff options
7 files changed, 135 insertions, 25 deletions
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/QualityProfileDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/QualityProfileDaoIT.java index 34674beab94..faddc97c163 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/QualityProfileDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/QualityProfileDaoIT.java @@ -538,7 +538,7 @@ public class QualityProfileDaoIT { } @Test - public void selectDefaultBuiltInProfilesWithoutActiveRules() { + public void selectDefaultProfilesWithoutActiveRules_whenIsBuiltIn_shouldReturnBuiltInProfiles() { // a quality profile without active rules but not builtin db.qualityProfiles().insert(qp -> qp.setIsBuiltIn(false).setLanguage("java")); @@ -557,22 +557,37 @@ public class QualityProfileDaoIT { dbSession.commit(); - assertThat(underTest.selectDefaultBuiltInProfilesWithoutActiveRules(dbSession, Sets.newHashSet("java", "cpp"))) + assertThat(underTest.selectDefaultProfilesWithoutActiveRules(dbSession, Sets.newHashSet("java", "cpp"), true)) .extracting(QProfileDto::getName) .containsOnly(javaQPWithoutActiveRules.getName(), cppQPWithoutActiveRules.getName()); - assertThat(underTest.selectDefaultBuiltInProfilesWithoutActiveRules(dbSession, Sets.newHashSet("java"))) + assertThat(underTest.selectDefaultProfilesWithoutActiveRules(dbSession, Sets.newHashSet("java"), true)) .extracting(QProfileDto::getName) .containsOnly(javaQPWithoutActiveRules.getName()); - assertThat(underTest.selectDefaultBuiltInProfilesWithoutActiveRules(dbSession, Sets.newHashSet("cobol"))) + assertThat(underTest.selectDefaultProfilesWithoutActiveRules(dbSession, Sets.newHashSet("cobol"), true)) .isEmpty(); - assertThat(underTest.selectDefaultBuiltInProfilesWithoutActiveRules(dbSession, Sets.newHashSet())) + assertThat(underTest.selectDefaultProfilesWithoutActiveRules(dbSession, Sets.newHashSet(), true)) .isEmpty(); } @Test + public void selectDefaultProfilesWithoutActiveRules_whenNotBuiltIn_shouldReturnNotBuiltInOnly() { + // a quality profile without active rules but not builtin + QProfileDto customJavaQpWithoutActiveRules = db.qualityProfiles().insert(qp -> qp.setIsBuiltIn(false).setLanguage("java")); + db.qualityProfiles().setAsDefault(customJavaQpWithoutActiveRules); + + // a built-in quality profile without active rules + db.qualityProfiles().insert(qp -> qp.setIsBuiltIn(true).setLanguage("java")); + + + assertThat(underTest.selectDefaultProfilesWithoutActiveRules(dbSession, Sets.newHashSet("java"), false)) + .extracting(QProfileDto::getName) + .containsOnly(customJavaQpWithoutActiveRules.getName()); + } + + @Test public void selectDescendants_returns_empty_if_no_children() { QProfileDto base = db.qualityProfiles().insert(); 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 7a57fad23ee..7407ba9307b 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 @@ -148,8 +148,8 @@ public class QualityProfileDao implements Dao { return mapper(dbSession).selectAllDefaultProfiles(); } - public List<QProfileDto> selectDefaultBuiltInProfilesWithoutActiveRules(DbSession dbSession, Set<String> languages) { - return executeLargeInputs(languages, partition -> mapper(dbSession).selectDefaultBuiltInProfilesWithoutActiveRules(partition)); + public List<QProfileDto> selectDefaultProfilesWithoutActiveRules(DbSession dbSession, Set<String> languages, boolean builtIn) { + return executeLargeInputs(languages, partition -> mapper(dbSession).selectDefaultProfilesWithoutActiveRules(partition, builtIn)); } @CheckForNull diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java index 5c20434255c..6403d02a913 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java @@ -52,7 +52,7 @@ public interface QualityProfileMapper { @CheckForNull QProfileDto selectDefaultProfile(@Param("language") String language); - List<QProfileDto> selectDefaultBuiltInProfilesWithoutActiveRules(@Param("languages") List<String> languages); + List<QProfileDto> selectDefaultProfilesWithoutActiveRules(@Param("languages") List<String> languages, @Param("builtIn") boolean builtIn); List<QProfileDto> selectDefaultProfiles( @Param("languages") Collection<String> languages); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml index 6712eaf5003..785cd1e8d54 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml @@ -145,14 +145,14 @@ where dp.language = #{language, jdbcType=VARCHAR} </select> - <select id="selectDefaultBuiltInProfilesWithoutActiveRules" parameterType="map" resultType="org.sonar.db.qualityprofile.QProfileDto"> + <select id="selectDefaultProfilesWithoutActiveRules" parameterType="map" resultType="org.sonar.db.qualityprofile.QProfileDto"> SELECT <include refid="qProfileColumns"/> FROM org_qprofiles oqp INNER JOIN rules_profiles rp ON oqp.rules_profile_uuid = rp.uuid INNER JOIN default_qprofiles dp ON dp.qprofile_uuid = oqp.uuid WHERE - rp.is_built_in = ${_true} + rp.is_built_in = #{builtIn, jdbcType=BOOLEAN} AND rp.language IN <foreach collection="languages" open="(" close=")" item="language" separator=",">#{language, jdbcType=VARCHAR}</foreach> AND NOT EXISTS ( diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesIT.java index eac7cc3e7b5..c6ed2963fc8 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesIT.java @@ -32,8 +32,10 @@ import org.junit.Test; import org.slf4j.event.Level; import org.sonar.api.impl.utils.TestSystem2; import org.sonar.api.resources.Language; +import org.sonar.api.resources.Languages; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.api.utils.System2; +import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; @@ -72,8 +74,9 @@ public class RegisterQualityProfilesIT { private final DbClient dbClient = db.getDbClient(); private final DummyBuiltInQProfileInsert insert = new DummyBuiltInQProfileInsert(); private final DummyBuiltInQProfileUpdate update = new DummyBuiltInQProfileUpdate(); + private final Languages languages = LanguageTesting.newLanguages("foo"); private final RegisterQualityProfiles underTest = new RegisterQualityProfiles(builtInQProfileRepositoryRule, dbClient, insert, update, - mock(BuiltInQualityProfilesUpdateListener.class), system2); + mock(BuiltInQualityProfilesUpdateListener.class), system2, languages); @Test public void start_fails_if_BuiltInQProfileRepository_has_not_been_initialized() { @@ -182,7 +185,7 @@ public class RegisterQualityProfilesIT { RulesProfileDto ruleProfileWithoutRule = newRuleProfileDto(rp -> rp.setIsBuiltIn(true).setName("Foo way").setLanguage(FOO_LANGUAGE.getKey())); RulesProfileDto ruleProfileLongNameWithoutRule = newRuleProfileDto(rp -> rp.setIsBuiltIn(true).setName("That's a very very very very very very " - + "very very very very long name").setLanguage(FOO_LANGUAGE.getKey())); + + "very very very very long name").setLanguage(FOO_LANGUAGE.getKey())); RulesProfileDto ruleProfileWithOneRuleToBeRenamed = newRuleProfileDto(rp -> rp.setIsBuiltIn(true).setName("Foo way 2").setLanguage(FOO_LANGUAGE.getKey())); RulesProfileDto ruleProfileWithOneRule = newRuleProfileDto(rp -> rp.setIsBuiltIn(true).setName("Foo way 3").setLanguage(FOO_LANGUAGE.getKey())); @@ -230,13 +233,13 @@ public class RegisterQualityProfilesIT { assertThat(logTester.logs(Level.INFO)).contains( format("Quality profile [%s] for language [%s] is no longer built-in and has been renamed to [%s] " - + "since it does not have any active rules.", + + "since it does not have any active rules.", qProfileWithoutRule.getName(), qProfileWithoutRule.getLanguage(), qProfileWithoutRule.getName() + expectedSuffix), format("Quality profile [%s] for language [%s] is no longer built-in and has been renamed to [%s] " - + "since it does not have any active rules.", + + "since it does not have any active rules.", qProfileLongNameWithoutRule.getName(), qProfileLongNameWithoutRule.getLanguage(), "That's a very very very very very ver..." + expectedSuffix), format("Quality profile [%s] for language [%s] is no longer built-in and has been renamed to [%s] " - + "since it does not have any active rules.", + + "since it does not have any active rules.", qProfileWithOneRuleToBeRenamed.getName(), qProfileWithOneRuleToBeRenamed.getLanguage(), qProfileWithOneRuleToBeRenamed.getName() + expectedSuffix)); assertThat(dbClient.qualityProfileDao().selectByUuid(db.getSession(), qProfileWithoutRule.getKee())) @@ -259,8 +262,8 @@ public class RegisterQualityProfilesIT { private Optional<String> selectUuidOfDefaultProfile(String language) { return db.select("select qprofile_uuid as \"profileUuid\" " + - " from default_qprofiles " + - " where language='" + language + "'") + " from default_qprofiles " + + " where language='" + language + "'") .stream() .findFirst() .map(m -> (String) m.get("profileUuid")); @@ -297,4 +300,59 @@ public class RegisterQualityProfilesIT { return Collections.emptyList(); } } + + @Test + public void start_shouldSetBuiltInProfileAsDefault_whenCustomDefaultProfileHasNoRule() { + QProfileDto customProfile = newQualityProfileDto() + .setIsBuiltIn(false) + .setLanguage(FOO_LANGUAGE.getKey()) + .setName("Name") + .setRulesProfileUuid(Uuids.createFast()); + + QProfileDto builtinProfile = newQualityProfileDto() + .setIsBuiltIn(true) + .setName(Uuids.createFast()) + .setLanguage(FOO_LANGUAGE.getKey()) + .setRulesProfileUuid(Uuids.createFast()); + + db.qualityProfiles().insert(customProfile, builtinProfile); + db.qualityProfiles().setAsDefault(customProfile); + + builtInQProfileRepositoryRule.add(FOO_LANGUAGE, builtinProfile.getName(), false); + builtInQProfileRepositoryRule.initialize(); + underTest.start(); + + assertThat(selectUuidOfDefaultProfile(FOO_LANGUAGE.getKey())) + .isPresent().contains(builtinProfile.getKee()); + + } + + @Test + public void start_shouldNotSetBuiltInProfileAsDefault_whenCustomDefaultProfileHasRule() { + QProfileDto customProfile = newQualityProfileDto() + .setIsBuiltIn(false) + .setLanguage(FOO_LANGUAGE.getKey()) + .setName("Name") + .setRulesProfileUuid(Uuids.createFast()); + + QProfileDto builtinProfile = newQualityProfileDto() + .setIsBuiltIn(true) + .setName(Uuids.createFast()) + .setLanguage(FOO_LANGUAGE.getKey()) + .setRulesProfileUuid(Uuids.createFast()); + + db.qualityProfiles().insert(customProfile, builtinProfile); + db.qualityProfiles().setAsDefault(customProfile); + + RuleDto ruleDto = db.rules().insert(r -> r.setLanguage(FOO_LANGUAGE.getKey())); + db.qualityProfiles().activateRule(customProfile, ruleDto); + + builtInQProfileRepositoryRule.add(FOO_LANGUAGE, builtinProfile.getName(), false); + builtInQProfileRepositoryRule.initialize(); + underTest.start(); + + assertThat(selectUuidOfDefaultProfile(FOO_LANGUAGE.getKey())) + .isPresent().contains(customProfile.getKee()); + + } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationIT.java index 1ca712de56b..7b28090f108 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationIT.java @@ -32,6 +32,7 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.sonar.api.config.Configuration; +import org.sonar.api.resources.Languages; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.api.rules.RulePriority; @@ -49,6 +50,7 @@ import org.sonar.db.qualityprofile.ActiveRuleDto; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.db.qualityprofile.RulesProfileDto; import org.sonar.db.rule.RuleDto; +import org.sonar.server.language.LanguageTesting; import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService; import org.sonar.server.qualityprofile.builtin.BuiltInQProfile; import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsert; @@ -113,8 +115,9 @@ public class RegisterQualityProfilesNotificationIT { private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, mock(RuleIndex.class), activeRuleIndexer, qualityProfileChangeEventService); private BuiltInQProfileUpdate builtInQProfileUpdate = new BuiltInQProfileUpdateImpl(dbClient, ruleActivator, activeRuleIndexer, qualityProfileChangeEventService); private BuiltInQualityProfilesUpdateListener builtInQualityProfilesNotification = mock(BuiltInQualityProfilesUpdateListener.class); + private final Languages languages = LanguageTesting.newLanguages(); private RegisterQualityProfiles underTest = new RegisterQualityProfiles(builtInQProfileRepositoryRule, dbClient, - builtInQProfileInsert, builtInQProfileUpdate, builtInQualityProfilesNotification, system2); + builtInQProfileInsert, builtInQProfileUpdate, builtInQualityProfilesNotification, system2, languages); @Test public void do_not_send_notification_on_new_profile() { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java index 03eab5fc21c..b85d8d84439 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java @@ -24,6 +24,7 @@ import com.google.common.collect.Multimap; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -34,6 +35,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; import org.sonar.api.Startable; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Languages; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.System2; import org.sonar.api.utils.log.Logger; @@ -69,16 +72,18 @@ public class RegisterQualityProfiles implements Startable { private final BuiltInQProfileUpdate builtInQProfileUpdate; private final BuiltInQualityProfilesUpdateListener builtInQualityProfilesNotification; private final System2 system2; + private final Languages languages; public RegisterQualityProfiles(BuiltInQProfileRepository builtInQProfileRepository, DbClient dbClient, BuiltInQProfileInsert builtInQProfileInsert, BuiltInQProfileUpdate builtInQProfileUpdate, - BuiltInQualityProfilesUpdateListener builtInQualityProfilesNotification, System2 system2) { + BuiltInQualityProfilesUpdateListener builtInQualityProfilesNotification, System2 system2, Languages languages) { this.builtInQProfileRepository = builtInQProfileRepository; this.dbClient = dbClient; this.builtInQProfileInsert = builtInQProfileInsert; this.builtInQProfileUpdate = builtInQProfileUpdate; this.builtInQualityProfilesNotification = builtInQualityProfilesNotification; this.system2 = system2; + this.languages = languages; } @Override @@ -116,6 +121,7 @@ public class RegisterQualityProfiles implements Startable { } ensureBuiltInDefaultQPContainsRules(dbSession); unsetBuiltInFlagAndRenameQPWhenPluginUninstalled(dbSession); + ensureBuiltInAreDefaultQPWhenNoRules(dbSession); dbSession.commit(); } @@ -168,6 +174,30 @@ public class RegisterQualityProfiles implements Startable { } /** + * This method ensure that after plugin removal, we don't end-up in a situation where a custom default quality profile is set, + * and cannot be deleted because builtIn quality profile cannot be set to default. + * + * @see <a href="https://jira.sonarsource.com/browse/SONAR-19478">SONAR-19478</a> + */ + private void ensureBuiltInAreDefaultQPWhenNoRules(DbSession dbSession) { + Set<String> activeLanguages = Arrays.stream(languages.all()).map(Language::getKey).collect(Collectors.toSet()); + Map<String, RulesProfileDto> builtInQProfileByLanguage = dbClient.qualityProfileDao().selectBuiltInRuleProfiles(dbSession).stream() + .collect(toMap(RulesProfileDto::getLanguage, Function.identity(), (oldValue, newValue) -> oldValue)); + List<QProfileDto> defaultProfileWithNoRules = dbClient.qualityProfileDao().selectDefaultProfilesWithoutActiveRules(dbSession, activeLanguages, false); + + for (QProfileDto defaultProfileWithNoRule : defaultProfileWithNoRules) { + long rulesCountByLanguage = dbClient.ruleDao().countByLanguage(dbSession, defaultProfileWithNoRule.getLanguage()); + RulesProfileDto builtInQProfile = builtInQProfileByLanguage.get(defaultProfileWithNoRule.getLanguage()); + if (builtInQProfile != null && rulesCountByLanguage == 0) { + QProfileDto builtInQualityProfile = dbClient.qualityProfileDao().selectByRuleProfileUuid(dbSession, builtInQProfile.getUuid()); + if (builtInQualityProfile != null) { + reassignDefaultQualityProfile(dbSession, defaultProfileWithNoRule, builtInQualityProfile); + } + } + } + } + + /** * This method ensure that if a default built-in quality profile does not have any active rules but another built-in one for the same language * does have active rules, the last one will be the default one. * @@ -177,7 +207,7 @@ public class RegisterQualityProfiles implements Startable { Map<String, RulesProfileDto> rulesProfilesByLanguage = dbClient.qualityProfileDao().selectBuiltInRuleProfilesWithActiveRules(dbSession).stream() .collect(toMap(RulesProfileDto::getLanguage, Function.identity(), (oldValue, newValue) -> oldValue)); - dbClient.qualityProfileDao().selectDefaultBuiltInProfilesWithoutActiveRules(dbSession, rulesProfilesByLanguage.keySet()) + dbClient.qualityProfileDao().selectDefaultProfilesWithoutActiveRules(dbSession, rulesProfilesByLanguage.keySet(), true) .forEach(qp -> { RulesProfileDto rulesProfile = rulesProfilesByLanguage.get(qp.getLanguage()); if (rulesProfile == null) { @@ -189,11 +219,7 @@ public class RegisterQualityProfiles implements Startable { return; } - Set<String> uuids = dbClient.defaultQProfileDao().selectExistingQProfileUuids(dbSession, Collections.singleton(qp.getKee())); - dbClient.defaultQProfileDao().deleteByQProfileUuids(dbSession, uuids); - dbClient.defaultQProfileDao().insertOrUpdate(dbSession, new DefaultQProfileDto() - .setQProfileUuid(qualityProfile.getKee()) - .setLanguage(qp.getLanguage())); + reassignDefaultQualityProfile(dbSession, qp, qualityProfile); LOGGER.info("Default built-in quality profile for language [{}] has been updated from [{}] to [{}] since previous default does not have active rules.", qp.getLanguage(), @@ -202,6 +228,14 @@ public class RegisterQualityProfiles implements Startable { }); } + private void reassignDefaultQualityProfile(DbSession dbSession, QProfileDto currentDefaultQualityProfile, QProfileDto newDefaultQualityProfile) { + Set<String> uuids = dbClient.defaultQProfileDao().selectExistingQProfileUuids(dbSession, Collections.singleton(currentDefaultQualityProfile.getKee())); + dbClient.defaultQProfileDao().deleteByQProfileUuids(dbSession, uuids); + dbClient.defaultQProfileDao().insertOrUpdate(dbSession, new DefaultQProfileDto() + .setQProfileUuid(newDefaultQualityProfile.getKee()) + .setLanguage(currentDefaultQualityProfile.getLanguage())); + } + public void unsetBuiltInFlagAndRenameQPWhenPluginUninstalled(DbSession dbSession) { var pluginsBuiltInQProfiles = builtInQProfileRepository.get() .stream() |