From: Julien Lancelot Date: Tue, 22 Sep 2015 16:03:55 +0000 (+0200) Subject: SONAR-6547 Clear rules overloaded debt X-Git-Tag: 5.2-RC1~240 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b9b0e42026f12be329b5f5a152faee2094814f02;p=sonarqube.git SONAR-6547 Clear rules overloaded debt --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index 2d8da8594b2..1ef7e6e19b2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -26,6 +26,7 @@ import org.sonar.server.qualitygate.RegisterQualityGates; import org.sonar.server.qualityprofile.RegisterQualityProfiles; import org.sonar.server.rule.RegisterRules; import org.sonar.server.search.IndexSynchronizer; +import org.sonar.server.startup.ClearRulesOverloadedDebt; import org.sonar.server.startup.DisplayLogOnDeprecatedProjects; import org.sonar.server.startup.GeneratePluginIndex; import org.sonar.server.startup.JdbcDriverDeployer; @@ -67,7 +68,8 @@ public class PlatformLevelStartup extends PlatformLevel { RenameIssueWidgets.class, ServerLifecycleNotifier.class, PurgeCeActivities.class, - DisplayLogOnDeprecatedProjects.class + DisplayLogOnDeprecatedProjects.class, + ClearRulesOverloadedDebt.class ); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/ClearRulesOverloadedDebt.java b/server/sonar-server/src/main/java/org/sonar/server/startup/ClearRulesOverloadedDebt.java new file mode 100644 index 00000000000..5ef82d238a8 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/ClearRulesOverloadedDebt.java @@ -0,0 +1,112 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.startup; + +import org.picocontainer.Startable; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.DbSession; +import org.sonar.db.loadedtemplate.LoadedTemplateDto; +import org.sonar.db.rule.RuleDto; +import org.sonar.server.db.DbClient; + +import static org.sonar.db.loadedtemplate.LoadedTemplateDto.ONE_SHOT_TASK_TYPE; + +/** + * Clear the overloaded technical debt of rules when SQALE plugin is not installed. + * See SONAR-6547. + * + * Should be removed after LTS 5.X + * + * @since 5.2 + */ +public class ClearRulesOverloadedDebt implements Startable { + + private static final Logger LOG = Loggers.get(ClearRulesOverloadedDebt.class); + + private static final String TEMPLATE_KEY = "ClearRulesOverloadedDebt"; + + private static final String SQALE_LICENSE_PROPERTY = "sonar.sqale.licenseHash.secured"; + + private final DbClient dbClient; + + public ClearRulesOverloadedDebt(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public void start() { + DbSession session = dbClient.openSession(false); + try { + if (hasAlreadyBeenExecuted(session)) { + return; + } + if (!isSqalePluginInstalled(session)) { + clearDebt(session); + } + markAsExecuted(session); + session.commit(); + } finally { + dbClient.closeSession(session); + } + } + + private void clearDebt(DbSession session) { + int countClearedRules = 0; + for (RuleDto rule : dbClient.deprecatedRuleDao().selectAll(session)) { + if (isDebtOverridden(rule)) { + rule.setSubCharacteristicId(null); + rule.setRemediationFunction(null); + rule.setRemediationCoefficient(null); + rule.setRemediationOffset(null); + dbClient.deprecatedRuleDao().update(session, rule); + countClearedRules++; + } + } + if (countClearedRules > 0) { + LOG.warn("The SQALE model has been cleaned to remove useless data left over by previous migrations. The technical debt of {} rules was reset to their default values.", + countClearedRules); + LOG.warn("=> As a consequence, the overall technical debt of your projects might slightly evolve during the next analysis."); + } + } + + private static boolean isDebtOverridden(RuleDto ruleDto) { + return ruleDto.getSubCharacteristicId() != null || ruleDto.getRemediationFunction() != null || ruleDto.getRemediationCoefficient() != null + || ruleDto.getRemediationOffset() != null; + } + + private boolean isSqalePluginInstalled(DbSession session) { + return dbClient.propertiesDao().selectGlobalProperty(session, SQALE_LICENSE_PROPERTY) != null; + } + + private boolean hasAlreadyBeenExecuted(DbSession session) { + return dbClient.loadedTemplateDao().countByTypeAndKey(ONE_SHOT_TASK_TYPE, TEMPLATE_KEY, session) > 0; + } + + private void markAsExecuted(DbSession session) { + dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(TEMPLATE_KEY, ONE_SHOT_TASK_TYPE), session); + } + + @Override + public void stop() { + // Nothing to do + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/ClearRulesOverloadedDebtTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/ClearRulesOverloadedDebtTest.java new file mode 100644 index 00000000000..e31f79e6bba --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/ClearRulesOverloadedDebtTest.java @@ -0,0 +1,182 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.startup; + +import java.util.Date; +import javax.annotation.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.db.DbSession; +import org.sonar.db.property.PropertyDto; +import org.sonar.db.rule.RuleDto; +import org.sonar.db.rule.RuleTesting; +import org.sonar.server.db.DbClient; +import org.sonar.server.rule.Rule; +import org.sonar.server.rule.db.RuleDao; +import org.sonar.server.rule.index.RuleIndex; +import org.sonar.server.tester.ServerTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.loadedtemplate.LoadedTemplateDto.ONE_SHOT_TASK_TYPE; + +public class ClearRulesOverloadedDebtTest { + + static final int SUB_CHARACTERISTIC_ID = 1; + + private static final RuleKey RULE_KEY_1 = RuleTesting.XOO_X1; + private static final RuleKey RULE_KEY_2 = RuleTesting.XOO_X2; + private static final RuleKey RULE_KEY_3 = RuleTesting.XOO_X3; + + @ClassRule + public static ServerTester tester = new ServerTester(); + + @org.junit.Rule + public LogTester logTester = new LogTester(); + + RuleDao ruleDao = tester.get(RuleDao.class); + RuleIndex ruleIndex = tester.get(RuleIndex.class); + DbClient dbClient = tester.get(DbClient.class); + DbSession dbSession = tester.get(DbClient.class).openSession(false); + + ClearRulesOverloadedDebt underTest = new ClearRulesOverloadedDebt(dbClient); + + @Before + public void before() { + tester.clearDbAndIndexes(); + } + + @After + public void after() { + dbSession.close(); + } + + @Test + public void remove_overridden_debt() throws Exception { + // Characteristic and remediation function is overridden + insertRuleDto(RULE_KEY_1, SUB_CHARACTERISTIC_ID, "LINEAR", null, "1d"); + // Only characteristic is overridden + insertRuleDto(RULE_KEY_2, SUB_CHARACTERISTIC_ID, null, null, null); + // Only remediation function is overridden + insertRuleDto(RULE_KEY_3, null, "CONSTANT_ISSUE", "5min", null); + + underTest.start(); + + verifyRuleHasNotOverriddenDebt(RULE_KEY_1); + verifyRuleHasNotOverriddenDebt(RULE_KEY_2); + verifyRuleHasNotOverriddenDebt(RULE_KEY_3); + verifyTaskRegistered(); + verifyLog(3); + } + + @Test + public void not_update_rule_debt_not_overridden() throws Exception { + RuleDto rule = insertRuleDto(RULE_KEY_1, null, null, null, null); + Date updateAt = rule.getUpdatedAt(); + + underTest.start(); + + RuleDto reloaded = ruleDao.getByKey(dbSession, RULE_KEY_1); + assertThat(reloaded.getUpdatedAt()).isEqualTo(updateAt); + verifyRuleHasNotOverriddenDebt(RULE_KEY_1); + + verifyTaskRegistered(); + verifyEmptyLog(); + } + + @Test + public void not_update_rule_debt_when_sqale_is_installed() throws Exception { + insertSqaleProperty(); + RuleDto rule = insertRuleDto(RULE_KEY_1, SUB_CHARACTERISTIC_ID, "LINEAR", null, "1d"); + Date updateAt = rule.getUpdatedAt(); + + underTest.start(); + + RuleDto reloaded = ruleDao.getByKey(dbSession, RULE_KEY_1); + assertThat(reloaded.getUpdatedAt()).isEqualTo(updateAt); + + Rule ruleEs = ruleIndex.getByKey(RULE_KEY_1); + assertThat(ruleEs.debtOverloaded()).isTrue(); + + verifyTaskRegistered(); + verifyEmptyLog(); + } + + @Test + public void not_execute_task_when_already_executed() throws Exception { + insertRuleDto(RULE_KEY_1, SUB_CHARACTERISTIC_ID, "LINEAR", null, "1d"); + underTest.start(); + verifyLog(1); + verifyTaskRegistered(); + + logTester.clear(); + underTest.start(); + assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); + verifyEmptyLog(); + } + + private void verifyRuleHasNotOverriddenDebt(RuleKey ruleKey) { + // Refresh session + dbSession.commit(true); + + RuleDto ruleDto = ruleDao.getByKey(dbSession, ruleKey); + assertThat(ruleDto.getSubCharacteristicId()).isNull(); + assertThat(ruleDto.getRemediationFunction()).isNull(); + assertThat(ruleDto.getRemediationCoefficient()).isNull(); + assertThat(ruleDto.getRemediationOffset()).isNull(); + + Rule rule = ruleIndex.getByKey(ruleKey); + assertThat(rule.debtOverloaded()).isFalse(); + } + + private RuleDto insertRuleDto(RuleKey ruleKey, @Nullable Integer subCharId, @Nullable String function, @Nullable String coeff, @Nullable String offset) { + RuleDto ruleDto = RuleTesting.newDto(ruleKey).setSubCharacteristicId(subCharId).setRemediationFunction(function).setRemediationOffset(offset).setRemediationCoefficient(coeff); + ruleDao.insert(dbSession, + ruleDto + ); + dbSession.commit(); + return ruleDto; + } + + private void insertSqaleProperty() { + dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey("sonar.sqale.licenseHash.secured").setValue("ABCD")); + dbSession.commit(); + } + + private void verifyTaskRegistered() { + assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(ONE_SHOT_TASK_TYPE, "ClearRulesOverloadedDebt")).isEqualTo(1); + } + + private void verifyLog(int nbOfUpdatedRules) { + assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly( + "The SQALE model has been cleaned to remove useless data left over by previous migrations. The technical debt of " + nbOfUpdatedRules + + " rules was reset to their default values.", + "=> As a consequence, the overall technical debt of your projects might slightly evolve during the next analysis."); + } + + private void verifyEmptyLog() { + assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); + } +}