]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5056 Restore requirements columns and move migration of requirements data to...
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 12 Mar 2014 16:52:33 +0000 (17:52 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 12 Mar 2014 16:52:33 +0000 (17:52 +0100)
32 files changed:
sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java
sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/java/org/sonar/core/rule/RuleDao.java
sonar-core/src/main/java/org/sonar/core/rule/RuleDto.java
sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementDao.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementDto.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementMapper.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql
sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl
sonar-core/src/main/resources/org/sonar/core/technicaldebt/db/RequirementMapper.xml [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/technicaldebt/db/CharacteristicDaoTest.java
sonar-core/src/test/java/org/sonar/core/technicaldebt/db/RequirementDaoTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/db/RequirementDaoTest/shared.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/MassUpdater.java
sonar-server/src/main/java/org/sonar/server/db/migrations/v42/CompleteIssueMessageMigration.java
sonar-server/src/main/java/org/sonar/server/db/migrations/v42/PackageKeysMigration.java
sonar-server/src/main/java/org/sonar/server/db/migrations/v43/DevelopmentCostMeasuresMigration.java
sonar-server/src/main/java/org/sonar/server/db/migrations/v43/IssueChangelogMigration.java
sonar-server/src/main/java/org/sonar/server/db/migrations/v43/IssueMigration.java
sonar-server/src/main/java/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigration.java
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRules.java [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/db/migrate/521_copy_debt_to_rules.rb [deleted file]
sonar-server/src/main/webapp/WEB-INF/db/migrate/522_delete_requirements.rb [deleted file]
sonar-server/src/test/java/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest.java [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/copy_requirements_from_characteristics_to_rules.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/copy_requirements_from_characteristics_to_rules_result.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/remove_requirements_data_from_characteristics.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/remove_requirements_data_from_characteristics_result.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/requirements.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/schema.sql [new file with mode: 0644]

index 37f9282d44e6fe22e8d027fc934156b185dc2b92..bcccf44a798b72f3180e7cb45baefadd752f22b1 100644 (file)
@@ -42,6 +42,7 @@ import org.sonar.core.rule.RuleTagDao;
 import org.sonar.core.source.db.SnapshotDataDao;
 import org.sonar.core.source.db.SnapshotSourceDao;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.RequirementDao;
 import org.sonar.core.template.LoadedTemplateDao;
 import org.sonar.core.user.*;
 
@@ -80,6 +81,7 @@ public final class DaoUtils {
       QualityProfileDao.class,
       PurgeDao.class,
       CharacteristicDao.class,
+      RequirementDao.class,
       ResourceIndexerDao.class,
       ResourceDao.class,
       ResourceKeyUpdaterDao.class,
index 0425b2440f0072720dc3a8f2a9c01ea81271bde2..a4ad3c2eb75c41a2c876f4d70ae60ca9b26d2b65 100644 (file)
@@ -33,7 +33,7 @@ import java.util.List;
  */
 public class DatabaseVersion implements BatchComponent, ServerComponent {
 
-  public static final int LAST_VERSION = 522;
+  public static final int LAST_VERSION = 520;
 
   public static enum Status {
     UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL
index 2d49c204bc8c3294009eceabd1f5272e6424fe29..249e0a0164808584d20f50aaff94c4477f506888 100644 (file)
@@ -65,6 +65,8 @@ import org.sonar.core.source.db.SnapshotDataMapper;
 import org.sonar.core.source.db.SnapshotSourceMapper;
 import org.sonar.core.technicaldebt.db.CharacteristicDto;
 import org.sonar.core.technicaldebt.db.CharacteristicMapper;
+import org.sonar.core.technicaldebt.db.RequirementDto;
+import org.sonar.core.technicaldebt.db.RequirementMapper;
 import org.sonar.core.template.LoadedTemplateDto;
 import org.sonar.core.template.LoadedTemplateMapper;
 import org.sonar.core.user.*;
@@ -144,6 +146,7 @@ public class MyBatis implements BatchComponent, ServerComponent {
     loadAlias(conf, "QualityProfile", QualityProfileDto.class);
     loadAlias(conf, "ActiveRule", ActiveRuleDto.class);
     loadAlias(conf, "ActiveRuleParam", ActiveRuleParamDto.class);
+    loadAlias(conf, "Requirement", RequirementDto.class);
 
     // AuthorizationMapper has to be loaded before IssueMapper because this last one used it
     loadMapper(conf, "org.sonar.core.user.AuthorizationMapper");
@@ -160,7 +163,8 @@ public class MyBatis implements BatchComponent, ServerComponent {
       MeasureMapper.class, SnapshotDataMapper.class, SnapshotSourceMapper.class, ActionPlanMapper.class, ActionPlanStatsMapper.class,
       NotificationQueueMapper.class, CharacteristicMapper.class, RuleTagMapper.class,
       GroupMembershipMapper.class, QualityProfileMapper.class, ActiveRuleMapper.class,
-      MeasureDataMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, ProjectQgateAssociationMapper.class
+      MeasureDataMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, ProjectQgateAssociationMapper.class,
+      RequirementMapper.class
     };
     loadMappers(conf, mappers);
     configureLogback(mappers);
index ebd38e5e27dc7eb42a9daca4082234daa6260103..1557307c0f5497c11a3acabc3245b57d1e1b60a5 100644 (file)
@@ -40,12 +40,16 @@ public class RuleDao implements BatchComponent, ServerComponent {
   public List<RuleDto> selectAll() {
     SqlSession session = mybatis.openSession();
     try {
-      return getMapper(session).selectAll();
+      return selectAll(session);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  public List<RuleDto> selectAll(SqlSession session) {
+    return getMapper(session).selectAll();
+  }
+
   public List<RuleDto> selectEnablesAndNonManual() {
     SqlSession session = mybatis.openSession();
     try {
index 9e8dfe7860ddd94a58f04e517a09c84b444014a2..1a15490d8a6d2b58e2b3e5803918aef505f7af6a 100644 (file)
@@ -31,6 +31,9 @@ import javax.annotation.Nullable;
 import java.util.Date;
 
 public final class RuleDto {
+
+  public final static Integer DISABLED_CHARACTERISTIC_ID = -1;
+
   private Integer id;
   private String repositoryKey;
   private String ruleKey;
@@ -311,6 +314,10 @@ public final class RuleDto {
     return this;
   }
 
+  public boolean isCharacteristicOverridden(){
+    return !DISABLED_CHARACTERISTIC_ID.equals(characteristicId);
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (!(obj instanceof RuleDto)) {
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementDao.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementDao.java
new file mode 100644 (file)
index 0000000..e57bf83
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.core.technicaldebt.db;
+
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.MyBatis;
+
+import java.util.List;
+
+public class RequirementDao implements ServerComponent {
+
+  private final MyBatis mybatis;
+
+  public RequirementDao(MyBatis mybatis) {
+    this.mybatis = mybatis;
+  }
+
+  public List<RequirementDto> selectRequirements() {
+    SqlSession session = mybatis.openSession();
+    try {
+      return selectRequirements(session);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public List<RequirementDto> selectRequirements(SqlSession session) {
+    return session.getMapper(RequirementMapper.class).selectRequirements();
+  }
+
+
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementDto.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementDto.java
new file mode 100644 (file)
index 0000000..101961d
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * 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.core.technicaldebt.db;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class RequirementDto implements Serializable {
+
+  public static final String DAYS = "d";
+  public static final String MINUTES = "mn";
+  public static final String HOURS = "h";
+
+  private Integer id;
+  private Integer parentId;
+  private Integer rootId;
+  private Integer ruleId;
+  private String functionKey;
+  private Double factorValue;
+  private String factorUnit;
+  private Double offsetValue;
+  private String offsetUnit;
+  private Date createdAt;
+  private Date updatedAt;
+  private boolean enabled;
+
+  public Integer getId() {
+    return id;
+  }
+
+  public RequirementDto setId(Integer id) {
+    this.id = id;
+    return this;
+  }
+
+  public Integer getParentId() {
+    return parentId;
+  }
+
+  public RequirementDto setParentId(Integer i) {
+    this.parentId = i;
+    return this;
+  }
+
+  public Integer getRootId() {
+    return rootId;
+  }
+
+  public RequirementDto setRootId(Integer rootId) {
+    this.rootId = rootId;
+    return this;
+  }
+
+  public Integer getRuleId() {
+    return ruleId;
+  }
+
+  public RequirementDto setRuleId(Integer ruleId) {
+    this.ruleId = ruleId;
+    return this;
+  }
+
+  public String getFunction() {
+    return functionKey;
+  }
+
+  public RequirementDto setFunction(String function) {
+    this.functionKey = function;
+    return this;
+  }
+
+  @CheckForNull
+  public Double getFactorValue() {
+    return factorValue;
+  }
+
+  public RequirementDto setFactorValue(@Nullable Double factor) {
+    this.factorValue = factor;
+    return this;
+  }
+
+  @CheckForNull
+  public String getFactorUnit() {
+    return factorUnit;
+  }
+
+  public RequirementDto setFactorUnit(@Nullable String factorUnit) {
+    this.factorUnit = factorUnit;
+    return this;
+  }
+
+  @CheckForNull
+  public Double getOffsetValue() {
+    return offsetValue;
+  }
+
+  public RequirementDto setOffsetValue(@Nullable Double offset) {
+    this.offsetValue = offset;
+    return this;
+  }
+
+  @CheckForNull
+  public String getOffsetUnit() {
+    return offsetUnit;
+  }
+
+  public RequirementDto setOffsetUnit(@Nullable String offsetUnit) {
+    this.offsetUnit = offsetUnit;
+    return this;
+  }
+
+  public Date getCreatedAt() {
+    return createdAt;
+  }
+
+  public RequirementDto setCreatedAt(Date createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+
+  @CheckForNull
+  public Date getUpdatedAt() {
+    return updatedAt;
+  }
+
+  public RequirementDto setUpdatedAt(@Nullable Date updatedAt) {
+    this.updatedAt = updatedAt;
+    return this;
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public RequirementDto setEnabled(boolean enabled) {
+    this.enabled = enabled;
+    return this;
+  }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementMapper.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/db/RequirementMapper.java
new file mode 100644 (file)
index 0000000..d826fb0
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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.core.technicaldebt.db;
+
+import java.util.List;
+
+public interface RequirementMapper {
+
+  List<RequirementDto> selectRequirements();
+
+}
index 1761f27ecec5889419c44bd33697adff634d045c..01b91d42ffc6567401a8489ac649208dc40802d5 100644 (file)
@@ -218,8 +218,6 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('517');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('518');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('519');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('520');
-INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('521');
-INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('522');
 
 INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '2011-09-26 22:27:48.0', '2011-09-26 22:27:48.0', null, null);
 ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
index 058641b81da719831e00dc3d137e0f4117d95f3f..7292b42d6d9760ca956d3fd562b6731fced4b6c2 100644 (file)
@@ -27,6 +27,13 @@ CREATE TABLE "CHARACTERISTICS" (
   "KEE" VARCHAR(100),
   "NAME" VARCHAR(100),
   "PARENT_ID" INTEGER,
+  "ROOT_ID" INTEGER,
+  "RULE_ID" INTEGER,
+  "FUNCTION_KEY" VARCHAR(100),
+  "FACTOR_VALUE" DOUBLE,
+  "FACTOR_UNIT" VARCHAR(100),
+  "OFFSET_VALUE" DOUBLE,
+  "OFFSET_UNIT" VARCHAR(100),
   "CHARACTERISTIC_ORDER" INTEGER,
   "ENABLED" BOOLEAN,
   "CREATED_AT" TIMESTAMP,
diff --git a/sonar-core/src/main/resources/org/sonar/core/technicaldebt/db/RequirementMapper.xml b/sonar-core/src/main/resources/org/sonar/core/technicaldebt/db/RequirementMapper.xml
new file mode 100644 (file)
index 0000000..983b39e
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.core.technicaldebt.db.RequirementMapper">
+
+  <sql id="requirementsColumns">
+    c.id,
+    c.parent_id as parentId,
+    c.root_id as rootId,
+    c.rule_id as ruleId,
+    c.function_key as functionKey,
+    c.factor_value as factorValue,
+    c.factor_unit as factorUnit,
+    c.offset_value as offsetValue,
+    c.offset_unit as offsetUnit,
+    c.enabled as enabled,
+    c.created_at as createdAt,
+    c.updated_at as updatedAt
+  </sql>
+
+  <select id="selectRequirements" parameterType="map" resultType="Requirement">
+    select <include refid="requirementsColumns"/>
+    from characteristics c
+    <where>
+      and c.rule_id IS NOT NULL
+    </where>
+  </select>
+
+</mapper>
+
index e197cc6fb136fc533601febad7cfef02a45c3bf2..1a3fa00574afa353e820a5f837484dcb2fc91fd3 100644 (file)
@@ -31,7 +31,8 @@ import static org.fest.assertions.Assertions.assertThat;
 
 public class CharacteristicDaoTest extends AbstractDaoTestCase {
 
-  private static final String[] EXCLUDED_COLUMNS = new String[]{"id", "created_at", "updated_at"};
+  private static final String[] EXCLUDED_COLUMNS = new String[]{"id", "root_id", "rule_id", "function_key", "factor_unit", "factor_value", "offset_unit", "offset_value",
+    "created_at", "updated_at"};
 
   CharacteristicDao dao;
 
@@ -158,7 +159,7 @@ public class CharacteristicDaoTest extends AbstractDaoTestCase {
 
     dao.update(dto);
 
-    checkTables("update_characteristic", new String[]{"id", "depth", "description", "quality_model_id", "updated_at"}, "characteristics");
+    checkTables("update_characteristic", EXCLUDED_COLUMNS, "characteristics");
   }
 
   @Test
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/db/RequirementDaoTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/db/RequirementDaoTest.java
new file mode 100644 (file)
index 0000000..d16c7af
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.core.technicaldebt.db;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RequirementDaoTest extends AbstractDaoTestCase {
+
+  private static final String[] EXCLUDED_COLUMNS = new String[]{"id", "created_at", "updated_at"};
+
+  RequirementDao dao;
+
+  @Before
+  public void createDao() {
+    dao = new RequirementDao(getMyBatis());
+  }
+
+  @Test
+  public void select_requirements() {
+    setupData("shared");
+
+    assertThat(dao.selectRequirements()).hasSize(2);
+  }
+
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/db/RequirementDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/db/RequirementDaoTest/shared.xml
new file mode 100644 (file)
index 0000000..8e097ce
--- /dev/null
@@ -0,0 +1,13 @@
+<dataset>
+
+  <!-- Requirement -->
+  <characteristics id="3" kee="[null]" name="[null]" parent_id="2" root_id="1" rule_id="10" characteristic_order="[null]"
+                   function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[true]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <!-- Disabled requirement -->
+  <characteristics id="6" kee="[null]" name="[null]" parent_id="5" root_id="4" rule_id="10" characteristic_order="[null]"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
index a7e3bf91496ba4914dd84e3097a2e02b847f8053..d349ce6417f92cd7b80409f3c9e2de817f30cc6b 100644 (file)
@@ -53,7 +53,10 @@ public class MassUpdater {
   public static interface InputConverter<S> {
     String updateSql();
 
-    void convert(S input, PreparedStatement updateStatement) throws SQLException;
+    /**
+     * Return false if you do not want to update this statement
+     */
+    boolean convert(S input, PreparedStatement updateStatement) throws SQLException;
   }
 
   public <S> void execute(InputLoader<S> inputLoader, InputConverter<S> converter) {
@@ -79,11 +82,12 @@ public class MassUpdater {
 
         int cursor = 0;
         while (rs.next()) {
-          converter.convert(inputLoader.load(rs), writeStatement);
-          writeStatement.addBatch();
+          if (converter.convert(inputLoader.load(rs), writeStatement)) {
+            writeStatement.addBatch();
+            cursor++;
+            count++;
+          }
 
-          cursor++;
-          count++;
           if (cursor == GROUP_SIZE) {
             writeStatement.executeBatch();
             writeConnection.commit();
index 9aa4a01cc12e16030747a4973c195f6fb98a9034..d8dcd62b620831c803f9b33c0d94fa4ae934f8f7 100644 (file)
@@ -65,9 +65,10 @@ public class CompleteIssueMessageMigration implements DatabaseMigration {
         }
 
         @Override
-        public void convert(Row row, PreparedStatement updateStatement) throws SQLException {
+        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
           updateStatement.setString(1, row.ruleName);
           updateStatement.setLong(2, row.issueId);
+          return true;
         }
       }
     );
index 9cdae43b84b3c4cc88950b1f5d6a4841e7543b32..7e353a29ce94f701ec140e6875495385d13ff60c 100644 (file)
@@ -66,9 +66,10 @@ public class PackageKeysMigration implements DatabaseMigration {
         }
 
         @Override
-        public void convert(Row row, PreparedStatement updateStatement) throws SQLException {
+        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
           updateStatement.setString(1, convertKey(row.key));
           updateStatement.setLong(2, row.id);
+          return true;
         }
       }
     );
index 64a974d91bd469f1a722353fe8114971234cf954..06d39511ceb0e0629fbe1cf37222efb932a9d395 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.server.db.migrations.MassUpdater;
 import org.sonar.server.db.migrations.SqlUtil;
 
 import javax.annotation.CheckForNull;
+
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -71,9 +72,10 @@ public class DevelopmentCostMeasuresMigration implements DatabaseMigration {
         }
 
         @Override
-        public void convert(Row row, PreparedStatement updateStatement) throws SQLException {
+        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
           updateStatement.setString(1, convertDebtForDays(row.value));
           updateStatement.setLong(2, row.id);
+          return true;
         }
       }
     );
index e5738ad0b7f506c44cd2f715cf008868353141b1..0bc03306782e060b2a88ece747a96a1e87ab79df 100644 (file)
@@ -82,10 +82,11 @@ public class IssueChangelogMigration implements DatabaseMigration {
         }
 
         @Override
-        public void convert(Row row, PreparedStatement updateStatement) throws SQLException {
+        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
           updateStatement.setString(1, convertChangelog(row.changeData));
           updateStatement.setDate(2, new Date(system2.now()));
           updateStatement.setLong(3, row.id);
+          return true;
         }
       }
     );
index 847af3b789fb29d81229aa8003f5756e6bdf5931..0edbfb3a54588ffa7aed0c1412609c3a14d47932 100644 (file)
@@ -78,10 +78,11 @@ public class IssueMigration implements DatabaseMigration {
         }
 
         @Override
-        public void convert(Row row, PreparedStatement updateStatement) throws SQLException {
+        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
           updateStatement.setLong(1, workDurationConvertor.createFromLong(row.debt));
           updateStatement.setDate(2, new Date(system2.now()));
           updateStatement.setLong(3, row.id);
+          return true;
         }
       }
     );
index 582922acc16af06728005953b9ce99623c3d41d6..022d58632382f4fac7d9285dd48a9ac4b4421f39 100644 (file)
@@ -88,7 +88,7 @@ public class TechnicalDebtMeasuresMigration implements DatabaseMigration {
         }
 
         @Override
-        public void convert(Row row, PreparedStatement updateStatement) throws SQLException {
+        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
           setDouble(updateStatement, 1, row.value);
           setDouble(updateStatement, 2, row.var1);
           setDouble(updateStatement, 3, row.var2);
@@ -96,6 +96,7 @@ public class TechnicalDebtMeasuresMigration implements DatabaseMigration {
           setDouble(updateStatement, 5, row.var4);
           setDouble(updateStatement, 6, row.var5);
           updateStatement.setLong(7, row.id);
+          return true;
         }
       }
     );
index 4f1c290733f934990b475dd8a1fef38ce585ecbc..556a357015c18458a84e781d75ce3cc21a9fe55d 100644 (file)
@@ -448,6 +448,7 @@ public final class Platform {
     startupContainer.addSingleton(LogServerId.class);
     startupContainer.addSingleton(RegisterServletFilters.class);
     startupContainer.addSingleton(CleanPreviewAnalysisCache.class);
+    startupContainer.addSingleton(CopyRequirementsFromCharacteristicsToRules.class);
     startupContainer.startComponents();
 
     startupContainer.getComponentByType(ServerLifecycleNotifier.class).notifyStart();
diff --git a/sonar-server/src/main/java/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRules.java b/sonar-server/src/main/java/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRules.java
new file mode 100644 (file)
index 0000000..82120bc
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.Database;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.technicaldebt.db.RequirementDao;
+import org.sonar.core.technicaldebt.db.RequirementDto;
+import org.sonar.server.db.migrations.MassUpdater;
+import org.sonar.server.db.migrations.SqlUtil;
+import org.sonar.server.rule.RuleRegistration;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.sql.*;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * This script need to be executed after rules registration because default debt columns (characteristics, function, factor and offset) has to be populated
+ * in order to be able to compare default values with overridden values.
+ *
+ * @since 4.3 this component could be removed after 4 or 5 releases.
+ */
+public class CopyRequirementsFromCharacteristicsToRules {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(CopyRequirementsFromCharacteristicsToRules.class);
+
+  private final System2 system2;
+
+  private final Database db;
+
+  private final ServerUpgradeStatus status;
+
+  private final RequirementDao requirementDao;
+
+  /**
+   * @param ruleRegistration used only to be started after init of rules
+   */
+  public CopyRequirementsFromCharacteristicsToRules(Database database, RequirementDao requirementDao, ServerUpgradeStatus status, RuleRegistration ruleRegistration) {
+    this(database, requirementDao, status, System2.INSTANCE);
+  }
+
+  @VisibleForTesting
+  CopyRequirementsFromCharacteristicsToRules(Database database, RequirementDao requirementDao, ServerUpgradeStatus status, System2 system2) {
+    this.db = database;
+    this.system2 = system2;
+    this.status = status;
+    this.requirementDao = requirementDao;
+  }
+
+  public void start() {
+    if (mustDoPurge()) {
+      doPurge();
+    }
+  }
+
+  private boolean mustDoPurge() {
+    return status.isUpgraded() && status.getInitialDbVersion() <= 520;
+  }
+
+  private void doPurge() {
+    LOGGER.info("Copying requirement from characteristics to rules");
+    copyRequirementsFromCharacteristicsToRules();
+
+    LOGGER.info("Deleting requirements data");
+    removeRequirementsDataFromCharacteristics();
+  }
+
+  private void copyRequirementsFromCharacteristicsToRules() {
+    List<RequirementDto> requirementDtos = requirementDao.selectRequirements();
+    final Multimap<Integer, RequirementDto> requirementsByRuleId = ArrayListMultimap.create();
+    for (RequirementDto requirementDto : requirementDtos) {
+      requirementsByRuleId.put(requirementDto.getRuleId(), requirementDto);
+    }
+
+    new MassUpdater(db).execute(
+      new MassUpdater.InputLoader<Row>() {
+        @Override
+        public String selectSql() {
+          return "SELECT r.id,r.characteristic_id,r.remediation_function,r.remediation_factor,r.remediation_offset," +
+            "r.default_characteristic_id,r.default_remediation_function,r.default_remediation_factor,r.default_remediation_offset,r.status " +
+            "FROM rules r";
+        }
+
+        @Override
+        public Row load(ResultSet rs) throws SQLException {
+          Row row = new Row();
+          row.id = SqlUtil.getInt(rs, 1);
+          row.characteristicId = SqlUtil.getInt(rs, 2);
+          row.function = rs.getString(3);
+          row.factor = rs.getString(4);
+          row.offset = rs.getString(5);
+          row.defaultCharacteristicId = SqlUtil.getInt(rs, 6);
+          row.defaultFunction = rs.getString(7);
+          row.defaultFactor = rs.getString(8);
+          row.defaultOffset = rs.getString(9);
+          row.status = rs.getString(10);
+          return row;
+        }
+      },
+      new MassUpdater.InputConverter<Row>() {
+        @Override
+        public String updateSql() {
+          return "UPDATE rules SET characteristic_id=?,remediation_function=?,remediation_factor=?,remediation_offset=?,updated_at=? WHERE id=?";
+        }
+
+        @Override
+        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
+          Collection<RequirementDto> requirementsForCurrentRule = requirementsByRuleId.get(row.id);
+
+          if (requirementsForCurrentRule.isEmpty()) {
+            // Nothing to do, there's no requirement on this rule
+            return false;
+
+          } else {
+            RequirementDto enabledRequirement = Iterables.find(requirementsForCurrentRule, new Predicate<RequirementDto>() {
+              @Override
+              public boolean apply(RequirementDto input) {
+                return input.isEnabled();
+              }
+            }, null);
+
+            if (enabledRequirement == null && !"REMOVED".equals(row.getStatus())) {
+              // If no requirements are enable, it means that the requirement has been disabled for this rule
+              updateStatement.setInt(1, RuleDto.DISABLED_CHARACTERISTIC_ID);
+              updateStatement.setNull(2, Types.VARCHAR);
+              updateStatement.setNull(3, Types.VARCHAR);
+              updateStatement.setNull(4, Types.VARCHAR);
+              updateStatement.setDate(5, new Date(system2.now()));
+              updateStatement.setInt(6, row.getId());
+              return true;
+
+            } else if (enabledRequirement != null) {
+              // If one requirement is enable, it means either that this requirement has been set from SQALE, or that it come from a XML model definition
+
+              row.setCharacteristicId(enabledRequirement.getParentId());
+              row.setFunction(enabledRequirement.getFunction().toUpperCase());
+              row.setFactor(convertDuration(enabledRequirement.getFactorValue(), enabledRequirement.getFactorUnit()));
+              row.setOffset(convertDuration(enabledRequirement.getOffsetValue(), enabledRequirement.getOffsetUnit()));
+
+              if (isDebtDefaultValuesSameAsOverriddenValues(row)) {
+                // Default values on debt are the same that ones set by SQALE, nothing to do
+                return false;
+              } else {
+                // Default values on debt are not the same that ones set by SQALE, update the rule
+                updateStatement.setInt(1, row.getCharacteristicId());
+                updateStatement.setString(2, row.getFunction());
+                updateStatement.setString(3, row.getFactor());
+                updateStatement.setString(4, row.getOffset());
+                updateStatement.setDate(5, new Date(system2.now()));
+                updateStatement.setInt(6, row.getId());
+                return true;
+              }
+            }
+          }
+          return false;
+        }
+      }
+    );
+  }
+
+  @CheckForNull
+  @VisibleForTesting
+  String convertDuration(@Nullable Double oldValue, @Nullable String oldUnit) {
+    if (oldValue != null) {
+      String unit = oldUnit != null ? oldUnit : RequirementDto.DAYS;
+      // min is replaced by mn
+      unit = RequirementDto.MINUTES.equals(unit) ? Duration.MINUTE : unit;
+      // As value is stored in double, we have to round it in order to have an integer (for instance, if it was 1.6, we'll use 2)
+      return Integer.toString((int) Math.round(oldValue)) + unit;
+    }
+    return null;
+  }
+
+  @VisibleForTesting
+  boolean isDebtDefaultValuesSameAsOverriddenValues(Row row) {
+    return new EqualsBuilder()
+      .append(row.getDefaultCharacteristicId(), row.getCharacteristicId())
+      .append(row.getDefaultFunction(), row.getFunction())
+      .append(row.getDefaultFactor(), row.getFactor())
+      .append(row.getDefaultOffset(), row.getOffset())
+      .isEquals();
+  }
+
+
+  private void removeRequirementsDataFromCharacteristics(){
+    try {
+      Connection connection = db.getDataSource().getConnection();
+      Statement stmt = connection.createStatement();
+      stmt.executeUpdate("DELETE FROM characteristics WHERE rule_id IS NOT NULL");
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to remove requirements data from characteristics");
+    }
+  }
+
+  @VisibleForTesting
+  static class Row {
+    private Integer id;
+    private Integer characteristicId;
+    private Integer defaultCharacteristicId;
+    private String function;
+    private String defaultFunction;
+    private String factor;
+    private String defaultFactor;
+    private String offset;
+    private String defaultOffset;
+    private String status;
+
+    public Integer getId() {
+      return id;
+    }
+
+    public Row setId(Integer id) {
+      this.id = id;
+      return this;
+    }
+
+    public Integer getCharacteristicId() {
+      return characteristicId;
+    }
+
+    public Row setCharacteristicId(Integer characteristicId) {
+      this.characteristicId = characteristicId;
+      return this;
+    }
+
+    public Integer getDefaultCharacteristicId() {
+      return defaultCharacteristicId;
+    }
+
+    public Row setDefaultCharacteristicId(Integer defaultCharacteristicId) {
+      this.defaultCharacteristicId = defaultCharacteristicId;
+      return this;
+    }
+
+    public String getFunction() {
+      return function;
+    }
+
+    public Row setFunction(String function) {
+      this.function = function;
+      return this;
+    }
+
+    public String getDefaultFunction() {
+      return defaultFunction;
+    }
+
+    public Row setDefaultFunction(String defaultFunction) {
+      this.defaultFunction = defaultFunction;
+      return this;
+    }
+
+    public String getFactor() {
+      return factor;
+    }
+
+    public Row setFactor(String factor) {
+      this.factor = factor;
+      return this;
+    }
+
+    public String getDefaultFactor() {
+      return defaultFactor;
+    }
+
+    public Row setDefaultFactor(String defaultFactor) {
+      this.defaultFactor = defaultFactor;
+      return this;
+    }
+
+    public String getOffset() {
+      return offset;
+    }
+
+    public Row setOffset(String offset) {
+      this.offset = offset;
+      return this;
+    }
+
+    public String getDefaultOffset() {
+      return defaultOffset;
+    }
+
+    public Row setDefaultOffset(String defaultOffset) {
+      this.defaultOffset = defaultOffset;
+      return this;
+    }
+
+    public String getStatus() {
+      return status;
+    }
+
+    public Row setStatus(String status) {
+      this.status = status;
+      return this;
+    }
+  }
+
+}
diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/521_copy_debt_to_rules.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/521_copy_debt_to_rules.rb
deleted file mode 100644 (file)
index 7c893d4..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# 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.
-#
-
-#
-# Sonar 4.3
-# SONAR-5056
-#
-class CopyDebtToRules < ActiveRecord::Migration
-
-  class Characteristic < ActiveRecord::Base
-  end
-
-  class Rule < ActiveRecord::Base
-  end
-
-  def self.up
-    Rule.reset_column_information
-
-    requirements = Characteristic.all(
-        :conditions => ['rule_id IS NOT NULL AND function_key IS NOT NULL AND enabled=?', true]
-    )
-    requirements.each do |requirement|
-      rule = Rule.find_by_id(requirement.rule_id)
-      if rule
-        rule.characteristic_id = requirement.parent_id
-        # functions are now store in upper case
-        rule.remediation_function = requirement.function_key.upcase
-        rule.remediation_factor = to_new_remediation(requirement.factor_value, requirement.factor_unit)
-        rule.remediation_offset = to_new_remediation(requirement.offset_value, requirement.offset_unit)
-        rule.save
-      end
-    end
-  end
-
-  def self.to_new_remediation(old_value, old_unit)
-    if old_value
-      unit = old_unit || 'd'
-      unit = unit == 'mn' ? 'min' : unit
-      # As value is stored in double, we have to round it in order to have an integer (for instance, if it was 1.6, we'll use 2)
-      old_value.to_f.ceil.to_s + unit
-    else
-      '0d'
-    end
-  end
-
-end
-
diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/522_delete_requirements.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/522_delete_requirements.rb
deleted file mode 100644 (file)
index 8b1414b..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# 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.
-#
-
-#
-# Sonar 4.3
-# SONAR-5056
-#
-class DeleteRequirements < ActiveRecord::Migration
-
-  class Characteristic < ActiveRecord::Base
-  end
-
-  def self.up
-    Characteristic.reset_column_information
-
-    Characteristic.delete_all('rule_id IS NOT NULL')
-
-    # Remove columns on debt
-    remove_column('characteristics', 'root_id')
-    remove_column('characteristics', 'rule_id')
-    remove_column('characteristics', 'function_key')
-    remove_column('characteristics', 'factor_value')
-    remove_column('characteristics', 'factor_unit')
-    remove_column('characteristics', 'offset_value')
-    remove_column('characteristics', 'offset_unit')
-  end
-
-end
-
diff --git a/sonar-server/src/test/java/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest.java b/sonar-server/src/test/java/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest.java
new file mode 100644 (file)
index 0000000..a27c634
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.TestDatabase;
+import org.sonar.core.technicaldebt.db.RequirementDao;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CopyRequirementsFromCharacteristicsToRulesTest extends AbstractDaoTestCase {
+
+  @ClassRule
+  public static TestDatabase db = new TestDatabase().schema(CopyRequirementsFromCharacteristicsToRulesTest.class, "schema.sql");
+
+  @Mock
+  ServerUpgradeStatus status;
+
+  @Mock
+  System2 system2;
+
+  CopyRequirementsFromCharacteristicsToRules service;
+
+  @Before
+  public void setUp() throws Exception {
+    when(system2.now()).thenReturn(DateUtils.parseDate("2014-03-12").getTime());
+    service = new CopyRequirementsFromCharacteristicsToRules(db.database(), new RequirementDao(getMyBatis()), status, system2);
+  }
+
+  @Test
+  public void copy_requirements_from_characteristics_to_rules() throws Exception {
+    setupData("requirements");
+    db.prepareDbUnit(getClass(), "copy_requirements_from_characteristics_to_rules.xml");
+
+    when(status.isUpgraded()).thenReturn(true);
+    when(status.getInitialDbVersion()).thenReturn(498);
+
+    service.start();
+
+    db.assertDbUnit(getClass(), "copy_requirements_from_characteristics_to_rules_result.xml", "rules");
+  }
+
+  @Test
+  public void remove_requirements_data_from_characteristics() throws Exception {
+    db.prepareDbUnit(getClass(), "remove_requirements_data_from_characteristics.xml");
+
+    when(status.isUpgraded()).thenReturn(true);
+    when(status.getInitialDbVersion()).thenReturn(498);
+
+    service.start();
+
+    db.assertDbUnit(getClass(), "remove_requirements_data_from_characteristics_result.xml", "characteristics");
+  }
+
+  @Test
+  public void convert_duration() throws Exception {
+    assertThat(service.convertDuration(1.0, "h")).isEqualTo("1h");
+    assertThat(service.convertDuration(15.0, "d")).isEqualTo("15d");
+    assertThat(service.convertDuration(5.0, "min")).isEqualTo("5min");
+    assertThat(service.convertDuration(5.0, "mn")).isEqualTo("5min");
+
+    assertThat(service.convertDuration(0.9, "h")).isEqualTo("1h");
+    assertThat(service.convertDuration(1.4, "h")).isEqualTo("1h");
+
+    assertThat(service.convertDuration(1.0, null)).isEqualTo("1d");
+    assertThat(service.convertDuration(null, "d")).isNull();
+  }
+
+  @Test
+  public void is_debt_default_values_same_as_overridden_values() throws Exception {
+    assertThat(service.isDebtDefaultValuesSameAsOverriddenValues(new CopyRequirementsFromCharacteristicsToRules.Row()
+      .setDefaultCharacteristicId(1).setCharacteristicId(1)
+      .setDefaultFunction("LINEAR_OFFSET").setFunction("LINEAR_OFFSET")
+      .setDefaultFactor("5h").setFactor("5h")
+      .setDefaultOffset("10min").setOffset("10min")
+    )).isTrue();
+
+    assertThat(service.isDebtDefaultValuesSameAsOverriddenValues(new CopyRequirementsFromCharacteristicsToRules.Row()
+      .setDefaultCharacteristicId(1).setCharacteristicId(2)
+      .setDefaultFunction("LINEAR_OFFSET").setFunction("LINEAR_OFFSET")
+      .setDefaultFactor("5h").setFactor("5h")
+      .setDefaultOffset("10min").setOffset("10min")
+    )).isFalse();
+
+    assertThat(service.isDebtDefaultValuesSameAsOverriddenValues(new CopyRequirementsFromCharacteristicsToRules.Row()
+      .setDefaultCharacteristicId(1).setCharacteristicId(1)
+      .setDefaultFunction("LINEAR_OFFSET").setFunction("LINEAR_OFFSET")
+      .setDefaultFactor("5h").setFactor("4h")
+      .setDefaultOffset("10min").setOffset("5min")
+    )).isFalse();
+
+    assertThat(service.isDebtDefaultValuesSameAsOverriddenValues(new CopyRequirementsFromCharacteristicsToRules.Row()
+      .setDefaultCharacteristicId(1).setCharacteristicId(1)
+      .setDefaultFunction("CONSTANT_ISSUE").setFunction("LINEAR")
+      .setDefaultFactor(null).setFactor("5h")
+      .setDefaultOffset("10min").setOffset(null)
+    )).isFalse();
+  }
+}
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/copy_requirements_from_characteristics_to_rules.xml b/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/copy_requirements_from_characteristics_to_rules.xml
new file mode 100644 (file)
index 0000000..9b96cf3
--- /dev/null
@@ -0,0 +1,38 @@
+<dataset>
+
+  <!-- Rule not linked to a requirement -> Nothing to do -->
+  <rules id="1" plugin_rule_key="UselessImportCheck" plugin_name="squid" name="UselessImportCheck" description="Useless imports should be removed" status="READY"
+         characteristic_id="[null]" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Rule linked to a disabled requirement -> Update rule to disable characteristic -->
+  <rules id="2" plugin_rule_key="LeftCurlyBraceStartLineCheck" plugin_name="squid" name="LeftCurlyBraceStartLineCheck" description="Left curly braces should be located at the beginning of lines of code" status="READY"
+         characteristic_id="[null]" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Removed rule linked to a disabled requirement -> Do nothing -->
+  <rules id="3" plugin_rule_key="CallToFileDeleteOnExitMethod" plugin_name="squid" name="CallToFileDeleteOnExitMethod" description="CallToFileDeleteOnExitMethod" status="REMOVED"
+         characteristic_id="[null]" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Rule linked to one enable requirement, with same value of debt -> Nothing to do -->
+  <rules id="4" plugin_rule_key="ObjectFinalizeOverridenCallsSuperFinalizeCheck" plugin_name="squid" name="ObjectFinalizeOverridenCallsSuperFinalizeCheck" description="super.finalize() should be called at the end of Object.finalize() implementations" status="READY"
+         characteristic_id="[null]" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5min"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Rule linked to one enable requirement, with different value of debt -> Update rule -->
+  <rules id="5" plugin_rule_key="RightCurlyBraceStartLineCheck" plugin_name="squid" name="RightCurlyBraceStartLineCheck" description="Right curly braces should be located at the beginning of lines of code" status="READY"
+         characteristic_id="[null]" default_characteristic_id="20"
+         remediation_function="[null]" default_remediation_function="LINEAR"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="[null]" updated_at="2014-02-19"/>
+
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/copy_requirements_from_characteristics_to_rules_result.xml b/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/copy_requirements_from_characteristics_to_rules_result.xml
new file mode 100644 (file)
index 0000000..2faa176
--- /dev/null
@@ -0,0 +1,38 @@
+<dataset>
+
+  <!-- Rule not linked to a requirement -> Nothing to do -->
+  <rules id="1" plugin_rule_key="UselessImportCheck" plugin_name="squid" name="UselessImportCheck" description="Useless imports should be removed" status="READY"
+         characteristic_id="[null]" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Rule linked to a disabled requirements -> Update rule to disable characteristic -->
+  <rules id="2" plugin_rule_key="LeftCurlyBraceStartLineCheck" plugin_name="squid" name="LeftCurlyBraceStartLineCheck" description="Left curly braces should be located at the beginning of lines of code" status="READY"
+         characteristic_id="-1" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-03-12"/>
+
+  <!-- Removed rule linked to a disabled requirement -> Do nothing -->
+  <rules id="3" plugin_rule_key="CallToFileDeleteOnExitMethod" plugin_name="squid" name="CallToFileDeleteOnExitMethod" description="CallToFileDeleteOnExitMethod" status="REMOVED"
+         characteristic_id="[null]" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Rule linked to one enable requirement, with same value of debt -> Nothing to do -->
+  <rules id="4" plugin_rule_key="ObjectFinalizeOverridenCallsSuperFinalizeCheck" plugin_name="squid" name="ObjectFinalizeOverridenCallsSuperFinalizeCheck" description="super.finalize() should be called at the end of Object.finalize() implementations" status="READY"
+         characteristic_id="[null]" default_characteristic_id="10"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5min"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Rule linked to one enable requirement, with different value of debt -> Update rule -->
+  <rules id="5" plugin_rule_key="RightCurlyBraceStartLineCheck" plugin_name="squid" name="RightCurlyBraceStartLineCheck" description="Right curly braces should be located at the beginning of lines of code" status="READY"
+         characteristic_id="10" default_characteristic_id="20"
+         remediation_function="LINEAR_OFFSET" default_remediation_function="LINEAR"
+         remediation_factor="20min" default_remediation_factor="5d"
+         remediation_offset="30h" default_remediation_offset="[null]" updated_at="2014-03-12"/>
+
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/remove_requirements_data_from_characteristics.xml b/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/remove_requirements_data_from_characteristics.xml
new file mode 100644 (file)
index 0000000..687155a
--- /dev/null
@@ -0,0 +1,47 @@
+<dataset>
+
+  <!-- Requirements to be updated -->
+  <characteristics id="1" parent_id="10" rule_id="2"
+                   function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="2" parent_id="10" rule_id="2"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <characteristics id="3" parent_id="10" rule_id="3"
+                   function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="4" parent_id="10" rule_id="3"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <characteristics id="5" parent_id="10" rule_id="4"
+                   function_key="linear_offset" factor_value="5.0" factor_unit="mn" offset_value="9.8" offset_unit="h" enabled="[true]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="6" parent_id="10" rule_id="4"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <characteristics id="7" parent_id="10" rule_id="5"
+                   function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[true]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="8" parent_id="10" rule_id="5"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <!-- Characteristics not to be updated -->
+
+  <characteristics id="10" parent_id="10" rule_id="[null]"
+                   function_key="[null]" factor_value="[null]" factor_unit="[null]" offset_value="[null]" offset_unit="[null]" enabled="[true]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="11" parent_id="10" rule_id="[null]"
+                   function_key="[null]" factor_value="[null]" factor_unit="[null]" offset_value="[null]" offset_unit="[null]" enabled="[false]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/remove_requirements_data_from_characteristics_result.xml b/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/remove_requirements_data_from_characteristics_result.xml
new file mode 100644 (file)
index 0000000..86eac5d
--- /dev/null
@@ -0,0 +1,67 @@
+<!--
+  ~ 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.
+  -->
+
+<dataset>
+
+  <!-- Requirements to be updated -->
+  <!--<characteristics id="1" parent_id="10" rule_id="2"-->
+                   <!--function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[false]"-->
+                   <!--created_at="2013-11-20" updated_at="[null]"/>-->
+
+  <!--<characteristics id="2" parent_id="10" rule_id="2"-->
+                   <!--function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"-->
+                   <!--created_at="2013-11-20" updated_at="2013-11-22"/>-->
+
+  <!--<characteristics id="3" parent_id="10" rule_id="3"-->
+                   <!--function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[false]"-->
+                   <!--created_at="2013-11-20" updated_at="[null]"/>-->
+
+  <!--<characteristics id="4" parent_id="10" rule_id="3"-->
+                   <!--function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"-->
+                   <!--created_at="2013-11-20" updated_at="2013-11-22"/>-->
+
+  <!--<characteristics id="5" parent_id="10" rule_id="4"-->
+                   <!--function_key="linear_offset" factor_value="5.0" factor_unit="mn" offset_value="9.8" offset_unit="h" enabled="[true]"-->
+                   <!--created_at="2013-11-20" updated_at="[null]"/>-->
+
+  <!--<characteristics id="6" parent_id="10" rule_id="4"-->
+                   <!--function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"-->
+                   <!--created_at="2013-11-20" updated_at="2013-11-22"/>-->
+
+  <!--<characteristics id="7" parent_id="10" rule_id="5"-->
+                   <!--function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[true]"-->
+                   <!--created_at="2013-11-20" updated_at="[null]"/>-->
+
+  <!--<characteristics id="8" parent_id="10" rule_id="5"-->
+                   <!--function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"-->
+                   <!--created_at="2013-11-20" updated_at="2013-11-22"/>-->
+
+  <!-- Characteristics not to be updated -->
+
+  <characteristics id="10" parent_id="10" rule_id="[null]"
+                   function_key="[null]" factor_value="[null]" factor_unit="[null]" offset_value="[null]" offset_unit="[null]" enabled="[true]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="11" parent_id="10" rule_id="[null]"
+                   function_key="[null]" factor_value="[null]" factor_unit="[null]" offset_value="[null]" offset_unit="[null]" enabled="[false]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/requirements.xml b/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/requirements.xml
new file mode 100644 (file)
index 0000000..0eb2db8
--- /dev/null
@@ -0,0 +1,41 @@
+<dataset>
+
+  <!-- No requirement for rule 1 -->
+
+  <!-- Requirement of rule 2 -->
+  <characteristics id="1" parent_id="10" rule_id="2"
+                   function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="2" parent_id="10" rule_id="2"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <!-- Requirement of rule 3 -->
+  <characteristics id="3" parent_id="10" rule_id="3"
+                   function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="4" parent_id="10" rule_id="3"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <!-- Requirement of rule 4 -->
+  <characteristics id="5" parent_id="10" rule_id="4"
+                   function_key="linear_offset" factor_value="5.0" factor_unit="mn" offset_value="9.8" offset_unit="h" enabled="[true]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="6" parent_id="10" rule_id="4"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+  <!-- Requirement of rule 5 -->
+  <characteristics id="7" parent_id="10" rule_id="5"
+                   function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h" enabled="[true]"
+                   created_at="2013-11-20" updated_at="[null]"/>
+
+  <characteristics id="8" parent_id="10" rule_id="5"
+                   function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h" enabled="[false]"
+                   created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/schema.sql b/sonar-server/src/test/resources/org/sonar/server/startup/CopyRequirementsFromCharacteristicsToRulesTest/schema.sql
new file mode 100644 (file)
index 0000000..3f02904
--- /dev/null
@@ -0,0 +1,31 @@
+CREATE TABLE "RULES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "PLUGIN_RULE_KEY" VARCHAR(200) NOT NULL,
+  "PLUGIN_NAME" VARCHAR(255) NOT NULL,
+  "DESCRIPTION" VARCHAR(16777215),
+  "NAME" VARCHAR(200),
+  "STATUS" VARCHAR(40),
+  "CHARACTERISTIC_ID" INTEGER,
+  "DEFAULT_CHARACTERISTIC_ID" INTEGER,
+  "REMEDIATION_FUNCTION" VARCHAR(20),
+  "DEFAULT_REMEDIATION_FUNCTION" VARCHAR(20),
+  "REMEDIATION_FACTOR" VARCHAR(20),
+  "DEFAULT_REMEDIATION_FACTOR" VARCHAR(20),
+  "REMEDIATION_OFFSET" VARCHAR(20),
+  "DEFAULT_REMEDIATION_OFFSET" VARCHAR(20),
+  "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "CHARACTERISTICS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "PARENT_ID" INTEGER,
+  "RULE_ID" INTEGER,
+  "FUNCTION_KEY" VARCHAR(100),
+  "FACTOR_VALUE" DOUBLE,
+  "FACTOR_UNIT" VARCHAR(100),
+  "OFFSET_VALUE" DOUBLE,
+  "OFFSET_UNIT" VARCHAR(100),
+  "ENABLED" BOOLEAN,
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP
+);