]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6547 Clear rules overloaded debt 533/head
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 22 Sep 2015 16:03:55 +0000 (18:03 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 28 Sep 2015 11:35:54 +0000 (13:35 +0200)
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
server/sonar-server/src/main/java/org/sonar/server/startup/ClearRulesOverloadedDebt.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/startup/ClearRulesOverloadedDebtTest.java [new file with mode: 0644]

index 2d8da8594b29926586b9ef27a65bb01cd53d05f7..1ef7e6e19b2af87b31857e03da9c6a46722cd862 100644 (file)
@@ -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 (file)
index 0000000..5ef82d2
--- /dev/null
@@ -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 <a href="https://jira.sonarsource.com/browse/SONAR-6547">SONAR-6547</a>.
+ *
+ * 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 (file)
index 0000000..e31f79e
--- /dev/null
@@ -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();
+  }
+}