]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5056 Create restore from provided model action
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 20 Mar 2014 14:21:36 +0000 (15:21 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 20 Mar 2014 14:21:50 +0000 (15:21 +0100)
25 files changed:
sonar-core/src/main/java/org/sonar/core/rule/RuleDao.java
sonar-core/src/main/java/org/sonar/core/rule/RuleMapper.java
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepository.java
sonar-core/src/main/resources/org/sonar/core/rule/RuleMapper.xml
sonar-core/src/main/resources/org/sonar/core/technicaldebt/db/CharacteristicMapper.xml
sonar-core/src/test/java/org/sonar/core/rule/RuleDaoTest.java
sonar-core/src/test/java/org/sonar/core/technicaldebt/db/CharacteristicDaoTest.java
sonar-core/src/test/resources/org/sonar/core/rule/RuleDaoTest/select_overriding_debt_rules.xml [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtCharacteristic.java
sonar-plugin-api/src/main/java/org/sonar/api/server/debt/internal/DefaultDebtCharacteristic.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/internal/DefaultCharacteristic.java
sonar-server/src/main/java/org/sonar/server/debt/DebtCharacteristicsXMLImporter.java
sonar-server/src/main/java/org/sonar/server/debt/DebtModel.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelLookup.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelOperations.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelRestore.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelService.java
sonar-server/src/main/java/org/sonar/server/debt/DebtModelSynchronizer.java
sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
sonar-server/src/test/java/org/sonar/server/debt/DebtCharacteristicsXMLImporterTest.java
sonar-server/src/test/java/org/sonar/server/debt/DebtModelLookupTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/debt/DebtModelOperationsTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/debt/DebtModelRestoreTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/debt/DebtModelServiceTest.java
sonar-server/src/test/java/org/sonar/server/debt/DebtModelSynchronizerTest.java

index 9afcd8014eec92a3ef4d44149c5c99a2f5ecd974..2f5e0441f3d62316718d43f9bac04e0490870dbe 100644 (file)
@@ -76,6 +76,19 @@ public class RuleDao implements BatchComponent, ServerComponent {
     return getMapper(session).selectByCharacteristicOrSubCharacteristicId(characteristicOrSubCharacteristicId);
   }
 
+  public List<RuleDto> selectOverridingDebt() {
+    SqlSession session = mybatis.openSession();
+    try {
+      return selectOverridingDebt(session);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public List<RuleDto> selectOverridingDebt(SqlSession session) {
+    return getMapper(session).selectOverridingDebt();
+  }
+
   @CheckForNull
   public RuleDto selectById(Integer id, SqlSession session) {
     return getMapper(session).selectById(id);
index 8733b199d027dffcebda8b233062a639669df6d1..5fd2b2f3658267b8184d489a1185b473f080a5cf 100644 (file)
@@ -32,6 +32,8 @@ public interface RuleMapper {
 
   List<RuleDto> selectByCharacteristicOrSubCharacteristicId(int id);
 
+  List<RuleDto> selectOverridingDebt();
+
   RuleDto selectById(Integer id);
 
   RuleDto selectByName(String name);
index f9cc46262a04864288cfa9ebf16969580967cb1c..89031c8304e06d6e3d32bd6b904cd9949438e8a9 100644 (file)
@@ -44,6 +44,7 @@ import static com.google.common.collect.Lists.newArrayList;
  * they must be named "<pluginKey>-model.xml".
  * </p>
  */
+// TODO move it to sonar-server and rename it DebtModelPluginRepository when it will be no more used by the SQALE plugin
 public class TechnicalDebtModelRepository implements ServerExtension, Startable {
 
   public static final String DEFAULT_MODEL = "technical-debt";
index 17ddab71423da92acd2047ef19352399ee3c1a24..aa93b05e4724a499ef9239ed171ebf4635e3fc59 100644 (file)
     </where>
   </select>
 
+  <select id="selectOverridingDebt" resultType="Rule">
+    SELECT <include refid="selectColumns"/> FROM rules
+    <where>
+      AND (characteristic_id is NOT NULL or remediation_function IS NOT NULL)
+    </where>
+  </select>
+
   <update id="update" parameterType="Rule">
     UPDATE rules SET
     plugin_rule_key=#{ruleKey},
index 495ef37d1b1f986a65b16125f7330f6a57385dcc..5a600667933281a27d3cc875c59e1803171def47 100644 (file)
 
   <insert id="insert" parameterType="Characteristic" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
     INSERT INTO characteristics (kee, name, parent_id, characteristic_order, enabled, created_at, updated_at)
-    VALUES (#{kee}, #{name}, #{parentId}, #{characteristicOrder}, #{enabled}, current_timestamp, current_timestamp)
+    VALUES (#{kee}, #{name}, #{parentId}, #{characteristicOrder}, #{enabled}, #{createdAt}, #{updatedAt})
   </insert>
 
   <update id="update" parameterType="Characteristic">
index 7e90f6aeb9cbd4d5c7f908464ed6b29b99478907..c7f0fa4c3fc40d296d1a26e552855648c926d3cc 100644 (file)
@@ -50,7 +50,8 @@ public class RuleDaoTest extends AbstractDaoTestCase {
     setupData("selectAll");
     List<RuleDto> ruleDtos = dao.selectAll();
 
-    assertThat(ruleDtos.size()).isEqualTo(1);
+    assertThat(ruleDtos).hasSize(1);
+
     RuleDto ruleDto = ruleDtos.get(0);
     assertThat(ruleDto.getId()).isEqualTo(1);
     assertThat(ruleDto.getName()).isEqualTo("Avoid Null");
@@ -153,6 +154,13 @@ public class RuleDaoTest extends AbstractDaoTestCase {
     assertThat(ruleDtos).isEmpty();
   }
 
+  @Test
+  public void select_overriding_debt_rules() throws Exception {
+    setupData("select_overriding_debt_rules");
+
+    assertThat(dao.selectOverridingDebt()).hasSize(3);
+  }
+
   @Test
   public void update() {
     setupData("update");
index 6a05a8c55990288f75daba40305177f9c38d0a87..1f76fe6327d4f82605978d6987b787df8ba30807 100644 (file)
@@ -31,8 +31,7 @@ import static org.fest.assertions.Assertions.assertThat;
 
 public class CharacteristicDaoTest extends AbstractDaoTestCase {
 
-  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"};
+  private static final String[] EXCLUDED_COLUMNS = new String[]{"id", "root_id", "rule_id", "function_key", "factor_unit", "factor_value", "offset_unit", "offset_value"};
 
   CharacteristicDao dao;
 
diff --git a/sonar-core/src/test/resources/org/sonar/core/rule/RuleDaoTest/select_overriding_debt_rules.xml b/sonar-core/src/test/resources/org/sonar/core/rule/RuleDaoTest/select_overriding_debt_rules.xml
new file mode 100644 (file)
index 0000000..2e4d227
--- /dev/null
@@ -0,0 +1,31 @@
+<dataset>
+
+  <!-- Rule overriding debt and with default debt should be returned -->
+  <rules id="1" plugin_rule_key="UselessImportCheck" plugin_name="squid" name="UselessImportCheck" description="Useless imports should be removed" status="READY"
+         characteristic_id="2" default_characteristic_id="50"
+         remediation_function="LINEAR_OFFSET" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="5d" default_remediation_factor="5d"
+         remediation_offset="10h" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+  <!-- Rule only overriding debt should be returned -->
+  <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="3" default_characteristic_id="[null]"
+         remediation_function="LINEAR_OFFSET" default_remediation_function="[null]"
+         remediation_factor="5d" default_remediation_factor="[null]"
+         remediation_offset="10h" default_remediation_offset="[null]" updated_at="2014-02-19"/>
+
+  <!-- Rule with only default debt should be returned -->
+  <rules id="3" plugin_rule_key="CallToFileDeleteOnExitMethod" plugin_name="squid" name="CallToFileDeleteOnExitMethod" description="CallToFileDeleteOnExitMethod" status="READY"
+         characteristic_id="[null]" default_characteristic_id="50"
+         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 overriding debt : should be returned -->
+  <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="REMOVED"
+         characteristic_id="3" default_characteristic_id="50"
+         remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="5d" default_remediation_factor="5min"
+         remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+</dataset>
index 8f3dd602d7bd988eb7ee042a81748604deb63eaa..81d704ced4af17ddebdceb3bf9fdfa5bfb39c090 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.api.server.debt;
 
 import javax.annotation.CheckForNull;
 
+import java.util.Date;
+
 /**
  * @since 4.3
  */
@@ -37,4 +39,9 @@ public interface DebtCharacteristic {
 
   @CheckForNull
   Integer parentId();
+
+  Date createdAt();
+
+  @CheckForNull
+  Date updatedAt();
 }
index 6b6fce040f5811aeb1b6e64f025ff9d6c17715e3..9c531cdaa24ed0bf2c14c8ce18dd170f0cca08d3 100644 (file)
@@ -22,6 +22,11 @@ package org.sonar.api.server.debt.internal;
 
 import org.sonar.api.server.debt.DebtCharacteristic;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.Date;
+
 /**
  * @since 4.3
  */
@@ -32,6 +37,8 @@ public class DefaultDebtCharacteristic implements DebtCharacteristic {
   private String name;
   private Integer order;
   private Integer parentId;
+  private Date createdAt;
+  private Date updatedAt;
 
   @Override
   public Integer id() {
@@ -82,4 +89,24 @@ public class DefaultDebtCharacteristic implements DebtCharacteristic {
     this.parentId = parentId;
     return this;
   }
+
+  public Date createdAt() {
+    return createdAt;
+  }
+
+  public DefaultDebtCharacteristic setCreatedAt(Date createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+
+  @CheckForNull
+  public Date updatedAt() {
+    return updatedAt;
+  }
+
+  public DefaultDebtCharacteristic setUpdatedAt(@Nullable Date updatedAt) {
+    this.updatedAt = updatedAt;
+    return this;
+  }
+
 }
index cb31ca502092fc9f0f7aa34ea7afa1c6433652bd..e5de8f01f4a9e6dd3eae7c17d23adc3b8c37384f 100644 (file)
@@ -33,6 +33,10 @@ import java.util.List;
 
 import static com.google.common.collect.Lists.newArrayList;
 
+/**
+ * @deprecated since 4.3
+ */
+@Deprecated
 public class DefaultCharacteristic implements Characteristic {
 
   private Integer id;
index 1c47ffbc6a084b479cea8fda08d162fb8a455bef..d19fc9a163cedaef3e301869b84dba1baf6b407a 100644 (file)
 
 package org.sonar.server.debt;
 
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
 import org.apache.commons.lang.StringUtils;
 import org.codehaus.stax2.XMLInputFactory2;
 import org.codehaus.staxmate.SMInputFactory;
 import org.codehaus.staxmate.in.SMHierarchicCursor;
 import org.codehaus.staxmate.in.SMInputCursor;
 import org.sonar.api.ServerExtension;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -37,10 +35,6 @@ import javax.xml.stream.XMLStreamException;
 
 import java.io.Reader;
 import java.io.StringReader;
-import java.util.Collection;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
 
 public class DebtCharacteristicsXMLImporter implements ServerExtension {
 
@@ -53,7 +47,7 @@ public class DebtCharacteristicsXMLImporter implements ServerExtension {
   }
 
   public DebtModel importXML(Reader xml) {
-    DebtModel model = new DebtModel();
+    DebtModel debtModel = new DebtModel();
     try {
       SMInputFactory inputFactory = initStax();
       SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml);
@@ -63,7 +57,7 @@ public class DebtCharacteristicsXMLImporter implements ServerExtension {
       SMInputCursor chcCursor = cursor.childElementCursor(CHARACTERISTIC);
 
       while (chcCursor.getNext() != null) {
-        processCharacteristic(model, null, chcCursor);
+        process(debtModel, null, chcCursor);
       }
 
       cursor.getStreamReader().closeCompletely();
@@ -71,7 +65,7 @@ public class DebtCharacteristicsXMLImporter implements ServerExtension {
     } catch (XMLStreamException e) {
       throw new IllegalStateException("XML is not valid", e);
     }
-    return model;
+    return debtModel;
   }
 
   private SMInputFactory initStax() {
@@ -84,80 +78,28 @@ public class DebtCharacteristicsXMLImporter implements ServerExtension {
   }
 
   @CheckForNull
-  private DefaultCharacteristic processCharacteristic(DebtModel model, @Nullable DefaultCharacteristic parent, SMInputCursor chcCursor) throws XMLStreamException {
-    DefaultCharacteristic characteristic = new DefaultCharacteristic();
+  private void process(DebtModel debtModel, @Nullable String parent, SMInputCursor chcCursor) throws XMLStreamException {
+    DefaultDebtCharacteristic characteristic = new DefaultDebtCharacteristic();
     SMInputCursor cursor = chcCursor.childElementCursor();
     while (cursor.getNext() != null) {
       String node = cursor.getLocalName();
       if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
+        // TODO Attached to parent only if a key is existing, otherwise characteristic with empty key can be added.
         characteristic.setKey(cursor.collectDescendantText().trim());
-        // Attached to parent only if a key is existing, otherwise characteristic with empty key can be added.
-        characteristic.setParent(parent);
+        if (parent == null) {
+          characteristic.setOrder(debtModel.rootCharacteristics().size() + 1);
+          debtModel.addRootCharacteristic(characteristic);
+        } else {
+          debtModel.addSubCharacteristic(characteristic, parent);
+        }
 
       } else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) {
-        characteristic.setName(cursor.collectDescendantText().trim(), false);
+        characteristic.setName(cursor.collectDescendantText().trim());
 
         // <chc> can contain characteristics or requirements
       } else if (StringUtils.equals(node, CHARACTERISTIC)) {
-        processCharacteristic(model, characteristic, cursor);
+        process(debtModel, characteristic.key(), cursor);
       }
     }
-
-    if (StringUtils.isNotBlank(characteristic.key()) && characteristic.isRoot()) {
-      characteristic.setOrder(model.rootCharacteristics().size() + 1);
-      model.addRootCharacteristic(characteristic);
-      return characteristic;
-    }
-    return null;
   }
-
-  static class DebtModel {
-
-    private Collection<DefaultCharacteristic> rootCharacteristics;
-
-    public DebtModel() {
-      rootCharacteristics = newArrayList();
-    }
-
-    public DebtModel addRootCharacteristic(DefaultCharacteristic characteristic) {
-      rootCharacteristics.add(characteristic);
-      return this;
-    }
-
-    public List<DefaultCharacteristic> rootCharacteristics() {
-      return newArrayList(Iterables.filter(rootCharacteristics, new Predicate<DefaultCharacteristic>() {
-        @Override
-        public boolean apply(DefaultCharacteristic input) {
-          return input.isRoot();
-        }
-      }));
-    }
-
-    @CheckForNull
-    public DefaultCharacteristic characteristicByKey(final String key) {
-      return Iterables.find(characteristics(), new Predicate<DefaultCharacteristic>() {
-        @Override
-        public boolean apply(DefaultCharacteristic input) {
-          return input.key().equals(key);
-        }
-      }, null);
-    }
-
-    public List<DefaultCharacteristic> characteristics() {
-      List<DefaultCharacteristic> flatCharacteristics = newArrayList();
-      for (DefaultCharacteristic rootCharacteristic : rootCharacteristics) {
-        flatCharacteristics.add(rootCharacteristic);
-        for (DefaultCharacteristic characteristic : rootCharacteristic.children()) {
-          flatCharacteristics.add(characteristic);
-        }
-      }
-      return flatCharacteristics;
-    }
-
-    public boolean isEmpty() {
-      return rootCharacteristics.isEmpty();
-    }
-
-  }
-
 }
diff --git a/sonar-server/src/main/java/org/sonar/server/debt/DebtModel.java b/sonar-server/src/main/java/org/sonar/server/debt/DebtModel.java
new file mode 100644 (file)
index 0000000..be3fa01
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.debt;
+
+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.sonar.api.server.debt.DebtCharacteristic;
+
+import javax.annotation.CheckForNull;
+
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+// TODO Maybe should be an inner class of DebtCharacteristicsXMLImporter? Will see following how will be implemented the Backup feature
+public class DebtModel {
+
+  private Multimap<String, DebtCharacteristic> characteristicsByRootKey;
+
+  public DebtModel() {
+    characteristicsByRootKey = ArrayListMultimap.create();
+  }
+
+  public DebtModel addRootCharacteristic(DebtCharacteristic characteristic) {
+    characteristicsByRootKey.put(null, characteristic);
+    return this;
+  }
+
+  public DebtModel addSubCharacteristic(DebtCharacteristic subCharacteristic, String characteristicKey) {
+    characteristicsByRootKey.put(characteristicKey, subCharacteristic);
+    return this;
+  }
+
+  public List<DebtCharacteristic> rootCharacteristics() {
+    return newArrayList(characteristicsByRootKey.get(null));
+  }
+
+  public List<DebtCharacteristic> subCharacteristics(String characteristicKey) {
+    return newArrayList(characteristicsByRootKey.get(characteristicKey));
+  }
+
+  @CheckForNull
+  public DebtCharacteristic characteristicByKey(final String key) {
+    return Iterables.find(characteristicsByRootKey.values(), new Predicate<DebtCharacteristic>() {
+      @Override
+      public boolean apply(DebtCharacteristic input) {
+        return key.equals(input.key());
+      }
+    }, null);
+  }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/debt/DebtModelLookup.java b/sonar-server/src/main/java/org/sonar/server/debt/DebtModelLookup.java
new file mode 100644 (file)
index 0000000..aa79358
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.debt;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class DebtModelLookup implements ServerComponent {
+
+  private final CharacteristicDao dao;
+
+  public DebtModelLookup(CharacteristicDao dao) {
+    this.dao = dao;
+  }
+
+  public List<DebtCharacteristic> rootCharacteristics() {
+    return toCharacteristics(dao.selectEnabledRootCharacteristics());
+  }
+
+  public List<DebtCharacteristic> characteristics() {
+    return toCharacteristics(dao.selectEnabledCharacteristics());
+  }
+
+  @CheckForNull
+  public DebtCharacteristic characteristicById(int id) {
+    CharacteristicDto dto = dao.selectById(id);
+    return dto != null ? toCharacteristic(dto) : null;
+  }
+
+  private static List<DebtCharacteristic> toCharacteristics(Collection<CharacteristicDto> dtos) {
+    return newArrayList(Iterables.transform(dtos, new Function<CharacteristicDto, DebtCharacteristic>() {
+      @Override
+      public DebtCharacteristic apply(CharacteristicDto input) {
+        return toCharacteristic(input);
+      }
+    }));
+  }
+
+  private static DebtCharacteristic toCharacteristic(CharacteristicDto dto) {
+    return new DefaultDebtCharacteristic()
+      .setId(dto.getId())
+      .setKey(dto.getKey())
+      .setName(dto.getName())
+      .setOrder(dto.getOrder())
+      .setParentId(dto.getParentId());
+  }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/debt/DebtModelOperations.java b/sonar-server/src/main/java/org/sonar/server/debt/DebtModelOperations.java
new file mode 100644 (file)
index 0000000..a87b0c5
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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.debt;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
+import org.sonar.api.utils.System2;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.rule.RuleDao;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.util.Validation;
+
+import javax.annotation.Nullable;
+
+import java.util.Date;
+import java.util.List;
+
+public class DebtModelOperations implements ServerComponent {
+
+  private final MyBatis mybatis;
+  private final CharacteristicDao dao;
+  private final RuleDao ruleDao;
+  private final System2 system2;
+
+  public DebtModelOperations(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao) {
+    this(mybatis, dao, ruleDao, System2.INSTANCE);
+  }
+
+  @VisibleForTesting
+  DebtModelOperations(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, System2 system2) {
+    this.mybatis = mybatis;
+    this.dao = dao;
+    this.ruleDao = ruleDao;
+    this.system2 = system2;
+  }
+
+  public DebtCharacteristic create(String name, @Nullable Integer parentId) {
+    checkPermission();
+
+    SqlSession session = mybatis.openSession();
+    try {
+      checkNotAlreadyExists(name, session);
+
+      CharacteristicDto newCharacteristic = new CharacteristicDto()
+        .setKey(name.toUpperCase().replace(" ", "_"))
+        .setName(name)
+        .setEnabled(true)
+        .setCreatedAt(new Date(system2.now()));
+
+      // New sub characteristic
+      if (parentId != null) {
+        CharacteristicDto parent = findCharacteristic(parentId, session);
+        if (parent.getParentId() != null) {
+          throw new BadRequestException("A sub characteristic can not have a sub characteristic as parent.");
+        }
+        newCharacteristic.setParentId(parent.getId());
+      } else {
+        // New root characteristic
+        newCharacteristic.setOrder(dao.selectMaxCharacteristicOrder(session) + 1);
+      }
+      dao.insert(newCharacteristic, session);
+      session.commit();
+      return toCharacteristic(newCharacteristic);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public DebtCharacteristic rename(int characteristicId, String newName) {
+    checkPermission();
+
+    SqlSession session = mybatis.openSession();
+    try {
+      checkNotAlreadyExists(newName, session);
+
+      CharacteristicDto dto = findCharacteristic(characteristicId, session);
+      if (!dto.getName().equals(newName)) {
+        dto.setName(newName);
+        dto.setUpdatedAt(new Date(system2.now()));
+        dao.update(dto, session);
+        session.commit();
+      }
+      return toCharacteristic(dto);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public DebtCharacteristic moveUp(int characteristicId) {
+    return move(characteristicId, true);
+  }
+
+  public DebtCharacteristic moveDown(int characteristicId) {
+    return move(characteristicId, false);
+  }
+
+  private DebtCharacteristic move(int characteristicId, boolean moveUpOrDown) {
+    checkPermission();
+
+    SqlSession session = mybatis.openSession();
+    try {
+      CharacteristicDto dto = findCharacteristic(characteristicId, session);
+      int currentOrder = dto.getOrder();
+      CharacteristicDto dtoToSwitchOrderWith = moveUpOrDown ? dao.selectPrevious(currentOrder, session) : dao.selectNext(currentOrder, session);
+
+      // Do nothing when characteristic is already to the new location
+      if (dtoToSwitchOrderWith == null) {
+        return toCharacteristic(dto);
+      }
+      int nextOrder = dtoToSwitchOrderWith.getOrder();
+      dtoToSwitchOrderWith.setOrder(currentOrder);
+      dtoToSwitchOrderWith.setUpdatedAt(new Date(system2.now()));
+      dao.update(dtoToSwitchOrderWith, session);
+
+      dto.setOrder(nextOrder);
+      dto.setUpdatedAt(new Date(system2.now()));
+      dao.update(dto, session);
+
+      session.commit();
+      return toCharacteristic(dto);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  /**
+   * Disable characteristic and sub characteristic or only sub characteristic.
+   * Will also update every rules linked to sub characteristics by setting characteristic id to -1 and remove function, factor and offset.
+   */
+  public void delete(int characteristicOrSubCharacteristicId) {
+    checkPermission();
+
+    Date updateDate = new Date(system2.now());
+    SqlSession session = mybatis.openBatchSession();
+    try {
+      CharacteristicDto characteristicOrSubCharacteristic = findCharacteristic(characteristicOrSubCharacteristicId, session);
+      disableDebtRules(
+        ruleDao.selectByCharacteristicOrSubCharacteristicId(characteristicOrSubCharacteristic.getId(), session),
+        updateDate,
+        session
+      );
+
+      if (characteristicOrSubCharacteristic.getParentId() == null) {
+        List<CharacteristicDto> subChracteristics = dao.selectCharacteristicsByParentId(characteristicOrSubCharacteristic.getId(), session);
+        for (CharacteristicDto subCharacteristic : subChracteristics) {
+          disableCharacteristic(subCharacteristic, updateDate, session);
+        }
+      }
+      disableCharacteristic(characteristicOrSubCharacteristic, updateDate, session);
+
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public void disableDebtRules(List<RuleDto> ruleDtos, Date updateDate, SqlSession session){
+    for (RuleDto ruleDto : ruleDtos) {
+      ruleDto.setCharacteristicId(RuleDto.DISABLED_CHARACTERISTIC_ID);
+      ruleDto.setRemediationFunction(null);
+      ruleDto.setRemediationFactor(null);
+      ruleDto.setRemediationOffset(null);
+      ruleDto.setUpdatedAt(updateDate);
+      ruleDao.update(ruleDto, session);
+      // TODO update rules from E/S
+    }
+  }
+
+  public void disableCharacteristic(CharacteristicDto characteristic, Date updateDate, SqlSession session){
+    characteristic.setEnabled(false);
+    characteristic.setUpdatedAt(updateDate);
+    dao.update(characteristic, session);
+  }
+
+  private CharacteristicDto findCharacteristic(Integer id, SqlSession session) {
+    CharacteristicDto dto = dao.selectById(id, session);
+    if (dto == null) {
+      throw new NotFoundException(String.format("Characteristic with id %s does not exists.", id));
+    }
+    return dto;
+  }
+
+  private void checkNotAlreadyExists(String name, SqlSession session) {
+    if (dao.selectByName(name, session) != null) {
+      throw BadRequestException.ofL10n(Validation.IS_ALREADY_USED_MESSAGE, name);
+    }
+  }
+
+  private void checkPermission() {
+    UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+  }
+
+  private static DebtCharacteristic toCharacteristic(CharacteristicDto dto) {
+    return new DefaultDebtCharacteristic()
+      .setId(dto.getId())
+      .setKey(dto.getKey())
+      .setName(dto.getName())
+      .setOrder(dto.getOrder())
+      .setParentId(dto.getParentId())
+      .setCreatedAt(dto.getCreatedAt())
+      .setUpdatedAt(dto.getUpdatedAt());
+  }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/debt/DebtModelRestore.java b/sonar-server/src/main/java/org/sonar/server/debt/DebtModelRestore.java
new file mode 100644 (file)
index 0000000..e9ed1c6
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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.debt;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.api.utils.System2;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.rule.RuleDao;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+import org.sonar.server.user.UserSession;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.Reader;
+import java.util.Date;
+import java.util.List;
+
+public class DebtModelRestore implements ServerComponent {
+
+  private final MyBatis mybatis;
+  private final CharacteristicDao dao;
+  private final RuleDao ruleDao;
+  private final DebtModelOperations debtModelOperations;
+  private final TechnicalDebtModelRepository debtModelPluginRepository;
+  private final DebtCharacteristicsXMLImporter importer;
+  private final System2 system2;
+
+  public DebtModelRestore(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, DebtModelOperations debtModelOperations, TechnicalDebtModelRepository debtModelPluginRepository,
+                          DebtCharacteristicsXMLImporter importer) {
+    this(mybatis, dao, ruleDao, debtModelOperations, debtModelPluginRepository, importer, System2.INSTANCE);
+  }
+
+  @VisibleForTesting
+  DebtModelRestore(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, DebtModelOperations debtModelOperations, TechnicalDebtModelRepository debtModelPluginRepository,
+                   DebtCharacteristicsXMLImporter importer,
+                   System2 system2) {
+    this.mybatis = mybatis;
+    this.dao = dao;
+    this.ruleDao = ruleDao;
+    this.debtModelOperations = debtModelOperations;
+    this.debtModelPluginRepository = debtModelPluginRepository;
+    this.importer = importer;
+    this.system2 = system2;
+  }
+
+  /**
+   * Restore model from all provided plugins
+   */
+  public void restoreFromProvidedModel() {
+    checkPermission();
+
+    Date updateDate = new Date(system2.now());
+    SqlSession session = mybatis.openSession();
+    try {
+      List<CharacteristicDto> persisted = dao.selectEnabledCharacteristics();
+      DebtModel providedModel = loadModelFromXml(TechnicalDebtModelRepository.DEFAULT_MODEL);
+      restoreCharacteristics(providedModel, persisted, updateDate, session);
+      resetOverridingRuleDebt(updateDate, session);
+
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  private void resetOverridingRuleDebt(Date updateDate, SqlSession session) {
+    for (RuleDto rule : ruleDao.selectOverridingDebt(session)) {
+      rule.setCharacteristicId(null);
+      rule.setRemediationFunction(null);
+      rule.setRemediationFactor(null);
+      rule.setRemediationOffset(null);
+      rule.setUpdatedAt(updateDate);
+      ruleDao.update(rule, session);
+      // TODO index rules in E/S
+    }
+  }
+
+  @VisibleForTesting
+  void restoreCharacteristics(DebtModel targetModel, List<CharacteristicDto> sourceCharacteristics, Date updateDate, SqlSession session) {
+    // Restore not existing characteristics
+    for (DebtCharacteristic characteristic : targetModel.rootCharacteristics()) {
+      CharacteristicDto rootCharacteristicDto = restoreCharacteristic(characteristic, null, sourceCharacteristics, updateDate, session);
+      for (DebtCharacteristic subCharacteristic : targetModel.subCharacteristics(characteristic.key())) {
+        restoreCharacteristic(subCharacteristic, rootCharacteristicDto.getId(), sourceCharacteristics, updateDate, session);
+      }
+    }
+    // Disable no more existing characteristics
+    for (CharacteristicDto sourceCharacteristic : sourceCharacteristics) {
+      if (targetModel.characteristicByKey(sourceCharacteristic.getKey()) == null) {
+        debtModelOperations.disableCharacteristic(sourceCharacteristic, updateDate, session);
+      }
+    }
+  }
+
+  private CharacteristicDto restoreCharacteristic(DebtCharacteristic targetCharacteristic, @Nullable Integer parentId,  List<CharacteristicDto> sourceCharacteristics,
+                                                  Date updateDate, SqlSession session) {
+    CharacteristicDto sourceCharacteristic = dtoByKey(sourceCharacteristics, targetCharacteristic.key());
+    if (sourceCharacteristic == null) {
+      CharacteristicDto newCharacteristic = toDto(targetCharacteristic, parentId).setCreatedAt(updateDate);
+      dao.insert(newCharacteristic, session);
+      return newCharacteristic;
+    } else {
+      // Update only if modifications
+      if (ObjectUtils.notEqual(sourceCharacteristic.getName(), targetCharacteristic.name()) ||
+        ObjectUtils.notEqual(sourceCharacteristic.getOrder(), targetCharacteristic.order())) {
+        sourceCharacteristic.setName(targetCharacteristic.name());
+        sourceCharacteristic.setOrder(targetCharacteristic.order());
+        sourceCharacteristic.setUpdatedAt(updateDate);
+        dao.update(sourceCharacteristic, session);
+      }
+      return sourceCharacteristic;
+    }
+  }
+
+  private DebtModel loadModelFromXml(String pluginKey) {
+    Reader xmlFileReader = null;
+    try {
+      xmlFileReader = debtModelPluginRepository.createReaderForXMLFile(pluginKey);
+      return importer.importXML(xmlFileReader);
+    } finally {
+      IOUtils.closeQuietly(xmlFileReader);
+    }
+  }
+
+  @CheckForNull
+  private CharacteristicDto dtoByKey(List<CharacteristicDto> existingModel, final String key) {
+    return Iterables.find(existingModel, new Predicate<CharacteristicDto>() {
+      @Override
+      public boolean apply(CharacteristicDto input) {
+        return key.equals(input.getKey());
+      }
+    }, null);
+  }
+
+  private static CharacteristicDto toDto(DebtCharacteristic characteristic, @Nullable Integer parentId) {
+    return new CharacteristicDto()
+      .setKey(characteristic.key())
+      .setName(characteristic.name())
+      .setOrder(characteristic.order())
+      .setParentId(parentId)
+      .setEnabled(true)
+      .setCreatedAt(characteristic.createdAt())
+      .setUpdatedAt(characteristic.updatedAt());
+  }
+
+  private void checkPermission() {
+    UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+  }
+
+
+}
index a17b23fdb17c9f3bbcdc5a7a4b6e5675a856f3ea..1049d0b1c254ea90278a817173175dec08638f99 100644 (file)
 
 package org.sonar.server.debt;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-import org.apache.ibatis.session.SqlSession;
 import org.sonar.api.server.debt.DebtCharacteristic;
 import org.sonar.api.server.debt.DebtModel;
-import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
-import org.sonar.api.utils.System2;
-import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.rule.RuleDao;
-import org.sonar.core.rule.RuleDto;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.user.UserSession;
-import org.sonar.server.util.Validation;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
-import java.util.Collection;
-import java.util.Date;
 import java.util.List;
 
-import static com.google.common.collect.Lists.newArrayList;
-
 /**
  * Used through ruby code <pre>Internal.debt</pre>
  * Also used by SQALE plugin.
  */
 public class DebtModelService implements DebtModel {
 
-  private final MyBatis mybatis;
-  private final CharacteristicDao dao;
-  private final RuleDao ruleDao;
-  private final System2 system2;
-
-  public DebtModelService(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao) {
-    this(mybatis, dao, ruleDao, System2.INSTANCE);
-  }
+  private final DebtModelOperations debtModelOperations;
+  private final DebtModelLookup debtModelLookup;
+  private final DebtModelRestore debtModelRestore;
 
-  @VisibleForTesting
-  DebtModelService(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, System2 system2) {
-    this.mybatis = mybatis;
-    this.dao = dao;
-    this.ruleDao = ruleDao;
-    this.system2 = system2;
+  public DebtModelService(DebtModelOperations debtModelOperations, DebtModelLookup debtModelLookup, DebtModelRestore debtModelRestore) {
+    this.debtModelOperations = debtModelOperations;
+    this.debtModelLookup = debtModelLookup;
+    this.debtModelRestore = debtModelRestore;
   }
 
   public List<DebtCharacteristic> rootCharacteristics() {
-    return toCharacteristics(dao.selectEnabledRootCharacteristics());
+    return debtModelLookup.rootCharacteristics();
   }
 
   public List<DebtCharacteristic> characteristics() {
-    return toCharacteristics(dao.selectEnabledCharacteristics());
+    return debtModelLookup.characteristics();
   }
 
   @CheckForNull
   public DebtCharacteristic characteristicById(int id) {
-    CharacteristicDto dto = dao.selectById(id);
-    return dto != null ? toCharacteristic(dto) : null;
+    return debtModelLookup.characteristicById(id);
   }
 
   public DebtCharacteristic create(String name, @Nullable Integer parentId) {
-    checkPermission();
-
-    SqlSession session = mybatis.openSession();
-    try {
-      checkNotAlreadyExists(name, session);
-
-      CharacteristicDto newCharacteristic = new CharacteristicDto()
-        .setKey(name.toUpperCase().replace(" ", "_"))
-        .setName(name)
-        .setEnabled(true);
-
-      // New sub characteristic
-      if (parentId != null) {
-        CharacteristicDto parent = findCharacteristic(parentId, session);
-        if (parent.getParentId() != null) {
-          throw new BadRequestException("A sub characteristic can not have a sub characteristic as parent.");
-        }
-        newCharacteristic.setParentId(parent.getId());
-      } else {
-        // New root characteristic
-        newCharacteristic.setOrder(dao.selectMaxCharacteristicOrder(session) + 1);
-      }
-      dao.insert(newCharacteristic, session);
-      session.commit();
-      return toCharacteristic(newCharacteristic);
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
+    return debtModelOperations.create(name, parentId);
   }
 
   public DebtCharacteristic rename(int characteristicId, String newName) {
-    checkPermission();
-
-    SqlSession session = mybatis.openSession();
-    try {
-      checkNotAlreadyExists(newName, session);
-
-      CharacteristicDto dto = findCharacteristic(characteristicId, session);
-      if (!dto.getName().equals(newName)) {
-        dto.setName(newName);
-        dao.update(dto, session);
-        session.commit();
-      }
-      return toCharacteristic(dto);
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
+    return debtModelOperations.rename(characteristicId, newName);
   }
 
   public DebtCharacteristic moveUp(int characteristicId) {
-    return move(characteristicId, true);
+    return debtModelOperations.moveUp(characteristicId);
   }
 
   public DebtCharacteristic moveDown(int characteristicId) {
-    return move(characteristicId, false);
-  }
-
-  private DebtCharacteristic move(int characteristicId, boolean moveUpOrDown) {
-    checkPermission();
-
-    SqlSession session = mybatis.openSession();
-    try {
-      CharacteristicDto dto = findCharacteristic(characteristicId, session);
-      int currentOrder = dto.getOrder();
-      CharacteristicDto dtoToSwitchOrderWith = moveUpOrDown ? dao.selectPrevious(currentOrder, session) : dao.selectNext(currentOrder, session);
-
-      // Do nothing when characteristic is already to the new location
-      if (dtoToSwitchOrderWith == null) {
-        return toCharacteristic(dto);
-      }
-      int nextOrder = dtoToSwitchOrderWith.getOrder();
-      dtoToSwitchOrderWith.setOrder(currentOrder);
-      dao.update(dtoToSwitchOrderWith, session);
-
-      dto.setOrder(nextOrder);
-      dao.update(dto, session);
-
-      session.commit();
-      return toCharacteristic(dto);
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
+    return debtModelOperations.moveDown(characteristicId);
   }
 
   /**
@@ -175,77 +78,11 @@ public class DebtModelService implements DebtModel {
    * Will also update every rules linked to sub characteristics by setting characteristic id to -1 and remove function, factor and offset.
    */
   public void delete(int characteristicOrSubCharactteristicId) {
-    checkPermission();
-
-    SqlSession session = mybatis.openBatchSession();
-    try {
-      Date now = new Date(system2.now());
-
-      CharacteristicDto characteristicOrSubCharacteristicDto = findCharacteristic(characteristicOrSubCharactteristicId, session);
-      List<RuleDto> ruleDtos = ruleDao.selectByCharacteristicOrSubCharacteristicId(characteristicOrSubCharacteristicDto.getId(), session);
-      for (RuleDto ruleDto : ruleDtos) {
-        ruleDto.setCharacteristicId(RuleDto.DISABLED_CHARACTERISTIC_ID);
-        ruleDto.setRemediationFunction(null);
-        ruleDto.setRemediationFactor(null);
-        ruleDto.setRemediationOffset(null);
-        ruleDto.setUpdatedAt(now);
-        ruleDao.update(ruleDto, session);
-
-        // TODO update rules from E/S
-      }
-
-      if (characteristicOrSubCharacteristicDto.getParentId() == null) {
-        List<CharacteristicDto> dtos = dao.selectCharacteristicsByParentId(characteristicOrSubCharacteristicDto.getId(), session);
-        for (CharacteristicDto subCharacteristicDto : dtos) {
-          subCharacteristicDto.setEnabled(false);
-          subCharacteristicDto.setUpdatedAt(now);
-          dao.update(subCharacteristicDto, session);
-        }
-      }
-      characteristicOrSubCharacteristicDto.setEnabled(false);
-      characteristicOrSubCharacteristicDto.setUpdatedAt(now);
-      dao.update(characteristicOrSubCharacteristicDto, session);
-
-      session.commit();
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
-  }
-
-  private CharacteristicDto findCharacteristic(Integer id, SqlSession session) {
-    CharacteristicDto dto = dao.selectById(id, session);
-    if (dto == null) {
-      throw new NotFoundException(String.format("Characteristic with id %s does not exists.", id));
-    }
-    return dto;
-  }
-
-  private void checkNotAlreadyExists(String name, SqlSession session) {
-    if (dao.selectByName(name, session) != null) {
-      throw BadRequestException.ofL10n(Validation.IS_ALREADY_USED_MESSAGE, name);
-    }
-  }
-
-  private void checkPermission() {
-    UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
-  }
-
-  private static List<DebtCharacteristic> toCharacteristics(Collection<CharacteristicDto> dtos) {
-    return newArrayList(Iterables.transform(dtos, new Function<CharacteristicDto, DebtCharacteristic>() {
-      @Override
-      public DebtCharacteristic apply(CharacteristicDto input) {
-        return toCharacteristic(input);
-      }
-    }));
+    debtModelOperations.delete(characteristicOrSubCharactteristicId);
   }
 
-  private static DebtCharacteristic toCharacteristic(CharacteristicDto dto) {
-    return new DefaultDebtCharacteristic()
-      .setId(dto.getId())
-      .setKey(dto.getKey())
-      .setName(dto.getName())
-      .setOrder(dto.getOrder())
-      .setParentId(dto.getParentId());
+  public void restore(){
+    debtModelRestore.restoreFromProvidedModel();
   }
 
 }
index 5fa4ff5bd68b905da4251c2aeceddd50a3dc720d..e8ea229be1857548404ca70a7ffdc108fb35690f 100644 (file)
@@ -22,15 +22,15 @@ package org.sonar.server.debt;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.ibatis.session.SqlSession;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.sonar.api.ServerExtension;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.server.debt.DebtCharacteristic;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
 import org.sonar.core.technicaldebt.db.CharacteristicDto;
 
+import javax.annotation.Nullable;
+
 import java.io.Reader;
 import java.util.List;
 
@@ -38,8 +38,6 @@ import static com.google.common.collect.Lists.newArrayList;
 
 public class DebtModelSynchronizer implements ServerExtension {
 
-  private static final Logger LOG = LoggerFactory.getLogger(DebtModelSynchronizer.class);
-
   private final MyBatis mybatis;
   private final CharacteristicDao dao;
   private final TechnicalDebtModelRepository languageModelFinder;
@@ -55,50 +53,36 @@ public class DebtModelSynchronizer implements ServerExtension {
   public List<CharacteristicDto> synchronize() {
     SqlSession session = mybatis.openSession();
 
-    List<CharacteristicDto> model = newArrayList();
+    List<CharacteristicDto> characteristics = newArrayList();
     try {
-      model = synchronize(session);
-      session.commit();
+      DebtModel defaultModel = loadModelFromXml(TechnicalDebtModelRepository.DEFAULT_MODEL);
+      List<CharacteristicDto> existingCharacteristics = dao.selectEnabledCharacteristics();
+      if (existingCharacteristics.isEmpty()) {
+        return createDebtModel(defaultModel, session);
+      }
     } finally {
       MyBatis.closeQuietly(session);
     }
-    return model;
-  }
-
-  public List<CharacteristicDto> synchronize(SqlSession session) {
-    DebtCharacteristicsXMLImporter.DebtModel defaultModel = loadModelFromXml(TechnicalDebtModelRepository.DEFAULT_MODEL);
-    List<CharacteristicDto> model = loadOrCreateModelFromDb(defaultModel, session);
-    return model;
-  }
-
-  private List<CharacteristicDto> loadOrCreateModelFromDb(DebtCharacteristicsXMLImporter.DebtModel defaultModel, SqlSession session) {
-    List<CharacteristicDto> characteristicDtos = loadModel();
-    if (characteristicDtos.isEmpty()) {
-      return createTechnicalDebtModel(defaultModel, session);
-    }
-    return characteristicDtos;
-  }
-
-  private List<CharacteristicDto> loadModel() {
-    return dao.selectEnabledCharacteristics();
+    return characteristics;
   }
 
-  private List<CharacteristicDto> createTechnicalDebtModel(DebtCharacteristicsXMLImporter.DebtModel defaultModel, SqlSession session) {
+  private List<CharacteristicDto> createDebtModel(DebtModel defaultModel, SqlSession session) {
     List<CharacteristicDto> characteristics = newArrayList();
-    for (DefaultCharacteristic rootCharacteristic : defaultModel.rootCharacteristics()) {
-      CharacteristicDto rootCharacteristicDto = CharacteristicDto.toDto(rootCharacteristic, null);
+    for (DebtCharacteristic rootCharacteristic : defaultModel.rootCharacteristics()) {
+      CharacteristicDto rootCharacteristicDto = toDto(rootCharacteristic, null);
       dao.insert(rootCharacteristicDto, session);
       characteristics.add(rootCharacteristicDto);
-      for (DefaultCharacteristic characteristic : rootCharacteristic.children()) {
-        CharacteristicDto characteristicDto = CharacteristicDto.toDto(characteristic, rootCharacteristicDto.getId());
+      for (DebtCharacteristic characteristic : defaultModel.subCharacteristics(rootCharacteristic.key())) {
+        CharacteristicDto characteristicDto = toDto(characteristic, rootCharacteristicDto.getId());
         dao.insert(characteristicDto, session);
         characteristics.add(characteristicDto);
       }
     }
+    session.commit();
     return characteristics;
   }
 
-  public DebtCharacteristicsXMLImporter.DebtModel loadModelFromXml(String pluginKey) {
+  private DebtModel loadModelFromXml(String pluginKey) {
     Reader xmlFileReader = null;
     try {
       xmlFileReader = languageModelFinder.createReaderForXMLFile(pluginKey);
@@ -108,4 +92,15 @@ public class DebtModelSynchronizer implements ServerExtension {
     }
   }
 
+  private static CharacteristicDto toDto(DebtCharacteristic characteristic, @Nullable Integer parentId) {
+    return new CharacteristicDto()
+      .setKey(characteristic.key())
+      .setName(characteristic.name())
+      .setOrder(characteristic.order())
+      .setParentId(parentId)
+      .setEnabled(true)
+      .setCreatedAt(characteristic.createdAt())
+      .setUpdatedAt(characteristic.updatedAt());
+  }
+
 }
index 99bd7f2fa896b107259332ae39799ea7eabe40a5..d9dd54c0e010649fd235beb1ac55683623bed068 100644 (file)
@@ -82,10 +82,7 @@ import org.sonar.server.component.DefaultRubyComponentService;
 import org.sonar.server.db.EmbeddedDatabaseFactory;
 import org.sonar.server.db.migrations.DatabaseMigrations;
 import org.sonar.server.db.migrations.DatabaseMigrator;
-import org.sonar.server.debt.DebtCharacteristicsXMLImporter;
-import org.sonar.server.debt.DebtModelService;
-import org.sonar.server.debt.DebtModelSynchronizer;
-import org.sonar.server.debt.DebtRulesXMLImporter;
+import org.sonar.server.debt.*;
 import org.sonar.server.es.ESIndex;
 import org.sonar.server.es.ESNode;
 import org.sonar.server.issue.*;
@@ -349,6 +346,9 @@ class ServerComponents {
 
     // technical debt
     pico.addSingleton(DebtModelService.class);
+    pico.addSingleton(DebtModelOperations.class);
+    pico.addSingleton(DebtModelLookup.class);
+    pico.addSingleton(DebtModelRestore.class);
     pico.addSingleton(TechnicalDebtModelSynchronizer.class);
     pico.addSingleton(DebtModelSynchronizer.class);
     pico.addSingleton(TechnicalDebtModelRepository.class);
index 1180215e3a24ebe7e790a1dd38fab54232ebc5e2..3f6224862dfb10e7031c17b800e5a378b16c2420 100644 (file)
@@ -23,9 +23,10 @@ package org.sonar.server.debt;
 import com.google.common.base.Charsets;
 import com.google.common.io.Resources;
 import org.junit.Test;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.server.debt.DebtCharacteristic;
 
 import java.io.IOException;
+import java.util.List;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
@@ -36,42 +37,50 @@ public class DebtCharacteristicsXMLImporterTest {
   public void import_characteristics() {
     String xml = getFileContent("import_characteristics.xml");
 
-    DebtCharacteristicsXMLImporter.DebtModel debtModel = new DebtCharacteristicsXMLImporter().importXML(xml);
-
-    assertThat(debtModel.rootCharacteristics()).hasSize(2);
-    assertThat(debtModel.rootCharacteristics().get(0).key()).isEqualTo("PORTABILITY");
-    assertThat(debtModel.rootCharacteristics().get(1).key()).isEqualTo("MAINTAINABILITY");
-
-    DefaultCharacteristic portability = debtModel.characteristicByKey("PORTABILITY");
-    assertThat(portability.order()).isEqualTo(1);
-    assertThat(portability.children()).hasSize(2);
-    assertThat(portability.children().get(0).key()).isEqualTo("COMPILER_RELATED_PORTABILITY");
-    assertThat(debtModel.characteristicByKey("COMPILER_RELATED_PORTABILITY").parent().key()).isEqualTo("PORTABILITY");
-    assertThat(portability.children().get(1).key()).isEqualTo("HARDWARE_RELATED_PORTABILITY");
-    assertThat(debtModel.characteristicByKey("HARDWARE_RELATED_PORTABILITY").parent().key()).isEqualTo("PORTABILITY");
-
-    DefaultCharacteristic maintainability = debtModel.characteristicByKey("MAINTAINABILITY");
-    assertThat(maintainability.order()).isEqualTo(2);
-    assertThat(maintainability.children()).hasSize(1);
-    assertThat(maintainability.children().get(0).key()).isEqualTo("READABILITY");
-    assertThat(debtModel.characteristicByKey("READABILITY").parent().key()).isEqualTo("MAINTAINABILITY");
+    DebtModel debtModel = new DebtCharacteristicsXMLImporter().importXML(xml);
+    List<DebtCharacteristic> rootCharacteristics = debtModel.rootCharacteristics();
+
+    assertThat(rootCharacteristics).hasSize(2);
+    assertThat(rootCharacteristics.get(0).key()).isEqualTo("PORTABILITY");
+    assertThat(rootCharacteristics.get(0).name()).isEqualTo("Portability");
+    assertThat(rootCharacteristics.get(0).order()).isEqualTo(1);
+
+    assertThat(rootCharacteristics.get(1).key()).isEqualTo("MAINTAINABILITY");
+    assertThat(rootCharacteristics.get(1).name()).isEqualTo("Maintainability");
+    assertThat(rootCharacteristics.get(1).order()).isEqualTo(2);
+
+    List<DebtCharacteristic> portabilitySubCharacteristics = debtModel.subCharacteristics("PORTABILITY");
+    assertThat(portabilitySubCharacteristics).hasSize(2);
+    assertThat(portabilitySubCharacteristics.get(0).key()).isEqualTo("COMPILER_RELATED_PORTABILITY");
+    assertThat(portabilitySubCharacteristics.get(0).name()).isEqualTo("Compiler related portability");
+    assertThat(portabilitySubCharacteristics.get(1).key()).isEqualTo("HARDWARE_RELATED_PORTABILITY");
+    assertThat(portabilitySubCharacteristics.get(1).name()).isEqualTo("Hardware related portability");
+
+    List<DebtCharacteristic> maintainabilitySubCharacteristics = debtModel.subCharacteristics("MAINTAINABILITY");
+    assertThat(maintainabilitySubCharacteristics).hasSize(1);
+    assertThat(maintainabilitySubCharacteristics.get(0).key()).isEqualTo("READABILITY");
+    assertThat(maintainabilitySubCharacteristics.get(0).name()).isEqualTo("Readability");
   }
 
   @Test
   public void import_badly_formatted_xml() {
     String xml = getFileContent("import_badly_formatted_xml.xml");
 
-    DebtCharacteristicsXMLImporter.DebtModel debtModel = new DebtCharacteristicsXMLImporter().importXML(xml);
+    DebtModel debtModel = new DebtCharacteristicsXMLImporter().importXML(xml);
+    List<DebtCharacteristic> rootCharacteristics = debtModel.rootCharacteristics();
 
     // characteristics
-    assertThat(debtModel.rootCharacteristics()).hasSize(2);
-    DefaultCharacteristic efficiency = debtModel.characteristicByKey("EFFICIENCY");
-    assertThat(efficiency.name()).isEqualTo("Efficiency");
-
-    // sub-characteristics
-    assertThat(efficiency.children()).hasSize(1);
-    DefaultCharacteristic memoryEfficiency = debtModel.characteristicByKey("MEMORY_EFFICIENCY");
-    assertThat(memoryEfficiency.name()).isEqualTo("Memory use");
+    assertThat(rootCharacteristics).hasSize(2);
+    assertThat(rootCharacteristics.get(0).key()).isEqualTo("USABILITY");
+    assertThat(rootCharacteristics.get(0).name()).isEqualTo("Usability");
+
+    assertThat(rootCharacteristics.get(1).key()).isEqualTo("EFFICIENCY");
+    assertThat(rootCharacteristics.get(1).name()).isEqualTo("Efficiency");
+
+    // sub-characteristic
+    assertThat(debtModel.subCharacteristics("EFFICIENCY")).hasSize(1);
+    assertThat(debtModel.subCharacteristics("EFFICIENCY").get(0).key()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(debtModel.subCharacteristics("EFFICIENCY").get(0).name()).isEqualTo("Memory use");
   }
 
   @Test
@@ -81,7 +90,7 @@ public class DebtCharacteristicsXMLImporterTest {
     try {
       new DebtCharacteristicsXMLImporter().importXML(xml);
       fail();
-    } catch (Exception e){
+    } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class);
     }
   }
diff --git a/sonar-server/src/test/java/org/sonar/server/debt/DebtModelLookupTest.java b/sonar-server/src/test/java/org/sonar/server/debt/DebtModelLookupTest.java
new file mode 100644 (file)
index 0000000..6c72ca0
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.debt;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DebtModelLookupTest {
+
+  @Mock
+  CharacteristicDao dao;
+
+  CharacteristicDto characteristicDto = new CharacteristicDto()
+    .setId(1)
+    .setKey("MEMORY_EFFICIENCY")
+    .setName("Memory use")
+    .setOrder(2)
+    .setEnabled(true);
+
+  DebtModelLookup service;
+
+  @Before
+  public void setUp() throws Exception {
+    service = new DebtModelLookup(dao);
+  }
+
+  @Test
+  public void find_root_characteristics() {
+    when(dao.selectEnabledRootCharacteristics()).thenReturn(newArrayList(characteristicDto));
+    assertThat(service.rootCharacteristics()).hasSize(1);
+  }
+
+  @Test
+  public void find_characteristics() {
+    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(characteristicDto));
+    assertThat(service.characteristics()).hasSize(1);
+  }
+
+  @Test
+  public void find_characteristic_by_id() {
+    when(dao.selectById(1)).thenReturn(characteristicDto);
+
+    DebtCharacteristic characteristic = service.characteristicById(1);
+    assertThat(characteristic.id()).isEqualTo(1);
+    assertThat(characteristic.key()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(characteristic.name()).isEqualTo("Memory use");
+    assertThat(characteristic.order()).isEqualTo(2);
+    assertThat(characteristic.parentId()).isNull();
+
+    assertThat(service.characteristicById(111)).isNull();
+  }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/debt/DebtModelOperationsTest.java b/sonar-server/src/test/java/org/sonar/server/debt/DebtModelOperationsTest.java
new file mode 100644 (file)
index 0000000..cc84004
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ * 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.debt;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.BatchSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.rule.RuleDao;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.MockUserSession;
+
+import java.util.Date;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DebtModelOperationsTest {
+
+  @Mock
+  CharacteristicDao dao;
+
+  @Mock
+  RuleDao ruleDao;
+
+  @Mock
+  MyBatis mybatis;
+
+  @Mock
+  SqlSession session;
+
+  @Mock
+  System2 system2;
+
+  Date now = DateUtils.parseDate("2014-03-19");
+
+  CharacteristicDto characteristicDto = new CharacteristicDto()
+    .setId(1)
+    .setKey("MEMORY_EFFICIENCY")
+    .setName("Memory use")
+    .setOrder(2)
+    .setEnabled(true);
+
+  CharacteristicDto subCharacteristicDto = new CharacteristicDto()
+    .setId(2)
+    .setKey("EFFICIENCY")
+    .setName("Efficiency")
+    .setParentId(1)
+    .setEnabled(true);
+
+  int currentId;
+
+  DebtModelOperations service;
+
+  @Before
+  public void setUp() throws Exception {
+    when(system2.now()).thenReturn(now.getTime());
+
+    MockUserSession.set().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+
+    currentId = 10;
+    // Associate an id when inserting an object to simulate the db id generator
+    doAnswer(new Answer() {
+      public Object answer(InvocationOnMock invocation) {
+        Object[] args = invocation.getArguments();
+        CharacteristicDto dto = (CharacteristicDto) args[0];
+        dto.setId(++currentId);
+        return null;
+      }
+    }).when(dao).insert(any(CharacteristicDto.class), any(SqlSession.class));
+
+    when(mybatis.openSession()).thenReturn(session);
+    service = new DebtModelOperations(mybatis, dao, ruleDao,system2);
+  }
+
+  @Test
+  public void create_sub_characteristic() {
+    when(dao.selectById(1, session)).thenReturn(characteristicDto);
+
+    DebtCharacteristic result = service.create("Compilation name", 1);
+
+    assertThat(result.id()).isEqualTo(currentId);
+    assertThat(result.key()).isEqualTo("COMPILATION_NAME");
+    assertThat(result.name()).isEqualTo("Compilation name");
+    assertThat(result.parentId()).isEqualTo(1);
+    assertThat(result.createdAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void fail_to_create_sub_characteristic_when_parent_id_is_not_a_root_characteristic() {
+    when(dao.selectById(1, session)).thenReturn(subCharacteristicDto);
+
+    try {
+      service.create("Compilation", 1);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("A sub characteristic can not have a sub characteristic as parent.");
+    }
+  }
+
+  @Test
+  public void fail_to_create_sub_characteristic_when_parent_does_not_exists() {
+    when(dao.selectById(1, session)).thenReturn(null);
+
+    try {
+      service.create("Compilation", 1);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Characteristic with id 1 does not exists.");
+    }
+  }
+
+  @Test
+  public void fail_to_create_sub_characteristic_when_name_already_used() {
+    when(dao.selectByName("Compilation", session)).thenReturn(new CharacteristicDto());
+    when(dao.selectById(1, session)).thenReturn(characteristicDto);
+
+    try {
+      service.create("Compilation", 1);
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.l10nKey()).isEqualTo("errors.is_already_used");
+      assertThat(e.l10nParams().iterator().next()).isEqualTo("Compilation");
+    }
+  }
+
+  @Test
+  public void fail_to_create_sub_characteristic_when_wrong_permission() {
+    MockUserSession.set().setGlobalPermissions(GlobalPermissions.DASHBOARD_SHARING);
+
+    try {
+      service.create("Compilation", 1);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(ForbiddenException.class);
+    }
+  }
+
+  @Test
+  public void create_characteristic() {
+    when(dao.selectMaxCharacteristicOrder(session)).thenReturn(2);
+
+    DebtCharacteristic result = service.create("Portability", null);
+
+    assertThat(result.id()).isEqualTo(currentId);
+    assertThat(result.key()).isEqualTo("PORTABILITY");
+    assertThat(result.name()).isEqualTo("Portability");
+    assertThat(result.order()).isEqualTo(3);
+    assertThat(result.createdAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void create_first_characteristic() {
+    when(dao.selectMaxCharacteristicOrder(session)).thenReturn(0);
+
+    DebtCharacteristic result = service.create("Portability", null);
+
+    assertThat(result.id()).isEqualTo(currentId);
+    assertThat(result.key()).isEqualTo("PORTABILITY");
+    assertThat(result.name()).isEqualTo("Portability");
+    assertThat(result.order()).isEqualTo(1);
+    assertThat(result.createdAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void rename_characteristic() {
+    when(dao.selectById(10, session)).thenReturn(subCharacteristicDto);
+
+    DebtCharacteristic result = service.rename(10, "New Efficiency");
+
+    assertThat(result.key()).isEqualTo("EFFICIENCY");
+    assertThat(result.name()).isEqualTo("New Efficiency");
+    assertThat(result.updatedAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void not_rename_characteristic_when_renaming_with_same_name() {
+    when(dao.selectById(10, session)).thenReturn(subCharacteristicDto);
+
+    service.rename(10, "Efficiency");
+
+    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
+  }
+
+  @Test
+  public void fail_to_rename_unknown_characteristic() {
+    when(dao.selectById(10, session)).thenReturn(null);
+
+    try {
+      service.rename(10, "New Efficiency");
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Characteristic with id 10 does not exists.");
+    }
+  }
+
+  @Test
+  public void move_up() {
+    when(dao.selectById(10, session)).thenReturn(new CharacteristicDto().setId(10).setOrder(2));
+    when(dao.selectPrevious(2, session)).thenReturn(new CharacteristicDto().setId(2).setOrder(1));
+
+    DebtCharacteristic result = service.moveUp(10);
+
+    ArgumentCaptor<CharacteristicDto> argument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao, times(2)).update(argument.capture(), eq(session));
+
+    assertThat(result.order()).isEqualTo(1);
+    assertThat(argument.getAllValues().get(0).getOrder()).isEqualTo(2);
+    assertThat(argument.getAllValues().get(0).getUpdatedAt()).isEqualTo(now);
+    assertThat(argument.getAllValues().get(1).getOrder()).isEqualTo(1);
+    assertThat(argument.getAllValues().get(1).getUpdatedAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void do_nothing_when_move_up_and_already_on_top() {
+    CharacteristicDto dto = new CharacteristicDto().setId(10).setOrder(1);
+    when(dao.selectById(10, session)).thenReturn(dto);
+    when(dao.selectPrevious(1, session)).thenReturn(null);
+
+    service.moveUp(10);
+
+    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
+  }
+
+  @Test
+  public void move_down() {
+    when(dao.selectById(10, session)).thenReturn(new CharacteristicDto().setId(10).setOrder(2));
+    when(dao.selectNext(2, session)).thenReturn(new CharacteristicDto().setId(2).setOrder(3));
+
+    DebtCharacteristic result = service.moveDown(10);
+
+    ArgumentCaptor<CharacteristicDto> argument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao, times(2)).update(argument.capture(), eq(session));
+
+    assertThat(result.order()).isEqualTo(3);
+    assertThat(argument.getAllValues().get(0).getOrder()).isEqualTo(2);
+    assertThat(argument.getAllValues().get(0).getUpdatedAt()).isEqualTo(now);
+    assertThat(argument.getAllValues().get(1).getOrder()).isEqualTo(3);
+    assertThat(argument.getAllValues().get(1).getUpdatedAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void do_nothing_when_move_down_and_already_on_bottom() {
+    CharacteristicDto dto = new CharacteristicDto().setId(10).setOrder(5);
+    when(dao.selectById(10, session)).thenReturn(dto);
+    when(dao.selectNext(5, session)).thenReturn(null);
+
+    service.moveDown(10);
+
+    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
+  }
+
+  @Test
+  public void delete_sub_characteristic() {
+    BatchSession batchSession = mock(BatchSession.class);
+    when(mybatis.openBatchSession()).thenReturn(batchSession);
+
+    when(ruleDao.selectByCharacteristicOrSubCharacteristicId(2, batchSession)).thenReturn(newArrayList(
+      new RuleDto()
+        .setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("5min")
+        .setDefaultCharacteristicId(10).setDefaultRemediationFunction("LINEAR_OFFSET").setDefaultRemediationFactor("4h").setDefaultRemediationOffset("15min")
+    ));
+    when(dao.selectById(2, batchSession)).thenReturn(subCharacteristicDto);
+
+    service.delete(2);
+
+    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
+    verify(ruleDao).update(ruleArgument.capture(), eq(batchSession));
+    RuleDto ruleDto = ruleArgument.getValue();
+
+    // Overridden debt data are disabled
+    assertThat(ruleDto.getCharacteristicId()).isEqualTo(-1);
+    assertThat(ruleDto.getRemediationFunction()).isNull();
+    assertThat(ruleDto.getRemediationFactor()).isNull();
+    assertThat(ruleDto.getRemediationOffset()).isNull();
+    assertThat(ruleDto.getUpdatedAt()).isEqualTo(now);
+
+    // Default debt data should not be touched
+    assertThat(ruleDto.getDefaultCharacteristicId()).isEqualTo(10);
+    assertThat(ruleDto.getDefaultRemediationFunction()).isEqualTo("LINEAR_OFFSET");
+    assertThat(ruleDto.getDefaultRemediationFactor()).isEqualTo("4h");
+    assertThat(ruleDto.getDefaultRemediationOffset()).isEqualTo("15min");
+
+    ArgumentCaptor<CharacteristicDto> characteristicArgument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao).update(characteristicArgument.capture(), eq(batchSession));
+    CharacteristicDto characteristicDto = characteristicArgument.getValue();
+
+    // Sub characteristic is disable
+    assertThat(characteristicDto.getId()).isEqualTo(2);
+    assertThat(characteristicDto.isEnabled()).isFalse();
+    assertThat(characteristicDto.getUpdatedAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void delete_characteristic() {
+    BatchSession batchSession = mock(BatchSession.class);
+    when(mybatis.openBatchSession()).thenReturn(batchSession);
+
+    when(ruleDao.selectByCharacteristicOrSubCharacteristicId(1, batchSession)).thenReturn(newArrayList(
+      new RuleDto().setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("5min")
+    ));
+    when(dao.selectCharacteristicsByParentId(1, batchSession)).thenReturn(newArrayList(
+      subCharacteristicDto
+    ));
+    when(dao.selectById(1, batchSession)).thenReturn(characteristicDto);
+
+    service.delete(1);
+
+    verify(ruleDao).update(any(RuleDto.class), eq(batchSession));
+
+    ArgumentCaptor<CharacteristicDto> characteristicArgument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao, times(2)).update(characteristicArgument.capture(), eq(batchSession));
+    CharacteristicDto subCharacteristicDto = characteristicArgument.getAllValues().get(0);
+    CharacteristicDto characteristicDto = characteristicArgument.getAllValues().get(1);
+
+    // Sub characteristic is disable
+    assertThat(subCharacteristicDto.getId()).isEqualTo(2);
+    assertThat(subCharacteristicDto.isEnabled()).isFalse();
+    assertThat(subCharacteristicDto.getUpdatedAt()).isEqualTo(now);
+
+    // Characteristic is disable
+    assertThat(characteristicDto.getId()).isEqualTo(1);
+    assertThat(characteristicDto.isEnabled()).isFalse();
+    assertThat(characteristicDto.getUpdatedAt()).isEqualTo(now);
+  }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/debt/DebtModelRestoreTest.java b/sonar-server/src/test/java/org/sonar/server/debt/DebtModelRestoreTest.java
new file mode 100644 (file)
index 0000000..e90865b
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * 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.debt;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.rule.RuleDao;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+import org.sonar.server.user.MockUserSession;
+
+import java.io.Reader;
+import java.util.Collections;
+import java.util.Date;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DebtModelRestoreTest {
+
+  @Mock
+  MyBatis myBatis;
+
+  @Mock
+  SqlSession session;
+
+  @Mock
+  TechnicalDebtModelRepository debtModelPluginRepository;
+
+  @Mock
+  CharacteristicDao dao;
+
+  @Mock
+  RuleDao ruleDao;
+
+  @Mock
+  DebtModelOperations debtModelOperations;
+
+  @Mock
+  DebtCharacteristicsXMLImporter characteristicsXMLImporter;
+
+  @Mock
+  System2 system2;
+
+  Date now = DateUtils.parseDate("2014-03-19");
+
+  int currentId;
+
+  DebtModel defaultModel = new DebtModel();
+
+  DebtModelRestore debtModelRestore;
+
+  @Before
+  public void setUp() throws Exception {
+    MockUserSession.set().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+
+    when(system2.now()).thenReturn(now.getTime());
+
+    currentId = 10;
+    // Associate an id when inserting an object to simulate the db id generator
+    doAnswer(new Answer() {
+      public Object answer(InvocationOnMock invocation) {
+        Object[] args = invocation.getArguments();
+        CharacteristicDto dto = (CharacteristicDto) args[0];
+        dto.setId(currentId++);
+        return null;
+      }
+    }).when(dao).insert(any(CharacteristicDto.class), any(SqlSession.class));
+
+    when(myBatis.openSession()).thenReturn(session);
+
+    Reader defaultModelReader = mock(Reader.class);
+    when(debtModelPluginRepository.createReaderForXMLFile("technical-debt")).thenReturn(defaultModelReader);
+    when(characteristicsXMLImporter.importXML(eq(defaultModelReader))).thenReturn(defaultModel);
+
+    debtModelRestore = new DebtModelRestore(myBatis, dao, ruleDao, debtModelOperations, debtModelPluginRepository, characteristicsXMLImporter, system2);
+  }
+
+  @Test
+  public void create_characteristics_when_restoring_characteristics() throws Exception {
+    debtModelRestore.restoreCharacteristics(
+      new DebtModel()
+        .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+        .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER_RELATED_PORTABILITY").setName("Compiler"), "PORTABILITY"),
+      Collections.<CharacteristicDto>emptyList(),
+      now,
+      session
+    );
+
+    ArgumentCaptor<CharacteristicDto> characteristicArgument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao, times(2)).insert(characteristicArgument.capture(), eq(session));
+    assertThat(characteristicArgument.getAllValues().get(0).getId()).isEqualTo(10);
+    assertThat(characteristicArgument.getAllValues().get(0).getKey()).isEqualTo("PORTABILITY");
+    assertThat(characteristicArgument.getAllValues().get(0).getName()).isEqualTo("Portability");
+    assertThat(characteristicArgument.getAllValues().get(0).getParentId()).isNull();
+    assertThat(characteristicArgument.getAllValues().get(0).getOrder()).isEqualTo(1);
+    assertThat(characteristicArgument.getAllValues().get(0).getCreatedAt()).isEqualTo(now);
+    assertThat(characteristicArgument.getAllValues().get(0).getUpdatedAt()).isNull();
+
+    assertThat(characteristicArgument.getAllValues().get(1).getId()).isEqualTo(11);
+    assertThat(characteristicArgument.getAllValues().get(1).getKey()).isEqualTo("COMPILER_RELATED_PORTABILITY");
+    assertThat(characteristicArgument.getAllValues().get(1).getName()).isEqualTo("Compiler");
+    assertThat(characteristicArgument.getAllValues().get(1).getParentId()).isEqualTo(10);
+    assertThat(characteristicArgument.getAllValues().get(1).getOrder()).isNull();
+    assertThat(characteristicArgument.getAllValues().get(1).getCreatedAt()).isEqualTo(now);
+    assertThat(characteristicArgument.getAllValues().get(1).getUpdatedAt()).isNull();
+  }
+
+  @Test
+  public void update_characteristics_when_restoring_characteristics() throws Exception {
+    Date oldDate = DateUtils.parseDate("2014-01-01");
+
+    debtModelRestore.restoreCharacteristics(
+      new DebtModel()
+        .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+        .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER_RELATED_PORTABILITY").setName("Compiler"), "PORTABILITY"),
+      newArrayList(
+        new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2).setCreatedAt(oldDate).setUpdatedAt(oldDate),
+        new CharacteristicDto().setId(2).setKey("COMPILER_RELATED_PORTABILITY").setName("Compiler updated").setParentId(1).setCreatedAt(oldDate).setUpdatedAt(oldDate)
+      ),
+      now,
+      session
+    );
+
+    ArgumentCaptor<CharacteristicDto> characteristicArgument = ArgumentCaptor.forClass(CharacteristicDto.class);
+    verify(dao, times(2)).update(characteristicArgument.capture(), eq(session));
+    assertThat(characteristicArgument.getAllValues().get(0).getId()).isEqualTo(1);
+    assertThat(characteristicArgument.getAllValues().get(0).getKey()).isEqualTo("PORTABILITY");
+    assertThat(characteristicArgument.getAllValues().get(0).getName()).isEqualTo("Portability");
+    assertThat(characteristicArgument.getAllValues().get(0).getParentId()).isNull();
+    assertThat(characteristicArgument.getAllValues().get(0).getOrder()).isEqualTo(1);
+    assertThat(characteristicArgument.getAllValues().get(0).getCreatedAt()).isEqualTo(oldDate);
+    assertThat(characteristicArgument.getAllValues().get(0).getUpdatedAt()).isEqualTo(now);
+
+    assertThat(characteristicArgument.getAllValues().get(1).getId()).isEqualTo(2);
+    assertThat(characteristicArgument.getAllValues().get(1).getKey()).isEqualTo("COMPILER_RELATED_PORTABILITY");
+    assertThat(characteristicArgument.getAllValues().get(1).getName()).isEqualTo("Compiler");
+    assertThat(characteristicArgument.getAllValues().get(1).getParentId()).isEqualTo(1);
+    assertThat(characteristicArgument.getAllValues().get(1).getOrder()).isNull();
+    assertThat(characteristicArgument.getAllValues().get(1).getCreatedAt()).isEqualTo(oldDate);
+    assertThat(characteristicArgument.getAllValues().get(1).getUpdatedAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void disable_no_more_existing_characteristics_when_restoring_characteristics() throws Exception {
+    CharacteristicDto dto1 = new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1);
+    CharacteristicDto dto2 = new CharacteristicDto().setId(2).setKey("COMPILER_RELATED_PORTABILITY").setName("Compiler").setParentId(1);
+
+    debtModelRestore.restoreCharacteristics(new DebtModel(), newArrayList(dto1, dto2), now, session);
+
+    verify(debtModelOperations).disableCharacteristic(dto1, now, session);
+    verify(debtModelOperations).disableCharacteristic(dto2, now, session);
+  }
+
+  @Test
+  public void restore_from_provided_model() throws Exception {
+    Date oldDate = DateUtils.parseDate("2014-01-01");
+
+    defaultModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER_RELATED_PORTABILITY").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER_RELATED_PORTABILITY").setName("Compiler updated").setParentId(1).setCreatedAt(oldDate)
+    ));
+
+    when(ruleDao.selectOverridingDebt(session)).thenReturn(newArrayList(
+      new RuleDto().setCharacteristicId(10).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("15min")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelRestore.restoreFromProvidedModel();
+
+    verify(dao).selectEnabledCharacteristics();
+    verify(dao, times(2)).update(any(CharacteristicDto.class), eq(session));
+    verifyNoMoreInteractions(dao);
+
+    verify(ruleDao).selectOverridingDebt(session);
+    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getCharacteristicId()).isNull();
+    assertThat(rule.getRemediationFunction()).isNull();
+    assertThat(rule.getRemediationFactor()).isNull();
+    assertThat(rule.getRemediationOffset()).isNull();
+    assertThat(rule.getUpdatedAt()).isEqualTo(now);
+
+    verify(session).commit();
+  }
+}
index 708a9a4165aad0d50455797f0a14520f14b4dfbc..33205fe22757839282722f04862fc44f96baad2a 100644 (file)
  */
 package org.sonar.server.debt;
 
-import org.apache.ibatis.session.SqlSession;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.server.debt.DebtCharacteristic;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.core.persistence.BatchSession;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.rule.RuleDao;
-import org.sonar.core.rule.RuleDto;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.user.MockUserSession;
 
-import java.util.Date;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.verify;
 
 @RunWith(MockitoJUnitRunner.class)
 public class DebtModelServiceTest {
 
   @Mock
-  CharacteristicDao dao;
-
-  @Mock
-  RuleDao ruleDao;
+  DebtModelOperations debtModelOperations;
 
   @Mock
-  MyBatis mybatis;
+  DebtModelLookup debtModelLookup;
 
   @Mock
-  SqlSession session;
-
-  @Mock
-  System2 system2;
-
-  Date now = DateUtils.parseDate("2014-03-19");
-
-  CharacteristicDto characteristicDto = new CharacteristicDto()
-    .setId(1)
-    .setKey("MEMORY_EFFICIENCY")
-    .setName("Memory use")
-    .setOrder(2)
-    .setEnabled(true);
-
-  CharacteristicDto subCharacteristicDto = new CharacteristicDto()
-    .setId(2)
-    .setKey("EFFICIENCY")
-    .setName("Efficiency")
-    .setParentId(1)
-    .setEnabled(true);
-
-  int currentId;
+  DebtModelRestore debtModelRestore;
 
   DebtModelService service;
 
   @Before
   public void setUp() throws Exception {
-    when(system2.now()).thenReturn(now.getTime());
-
-    MockUserSession.set().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
-
-    currentId = 10;
-    // Associate an id when inserting an object to simulate the db id generator
-    doAnswer(new Answer() {
-      public Object answer(InvocationOnMock invocation) {
-        Object[] args = invocation.getArguments();
-        CharacteristicDto dto = (CharacteristicDto) args[0];
-        dto.setId(++currentId);
-        return null;
-      }
-    }).when(dao).insert(any(CharacteristicDto.class), any(SqlSession.class));
-
-    when(mybatis.openSession()).thenReturn(session);
-    service = new DebtModelService(mybatis, dao, ruleDao,system2);
+    service = new DebtModelService(debtModelOperations, debtModelLookup, debtModelRestore);
   }
 
   @Test
   public void find_root_characteristics() {
-    when(dao.selectEnabledRootCharacteristics()).thenReturn(newArrayList(characteristicDto));
-    assertThat(service.rootCharacteristics()).hasSize(1);
+    service.rootCharacteristics();
+    verify(debtModelLookup).rootCharacteristics();
   }
 
   @Test
   public void find_characteristics() {
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(characteristicDto));
-    assertThat(service.characteristics()).hasSize(1);
+    service.characteristics();
+    verify(debtModelLookup).characteristics();
   }
 
   @Test
   public void find_characteristic_by_id() {
-    when(dao.selectById(1)).thenReturn(characteristicDto);
-
-    DebtCharacteristic characteristic = service.characteristicById(1);
-    assertThat(characteristic.id()).isEqualTo(1);
-    assertThat(characteristic.key()).isEqualTo("MEMORY_EFFICIENCY");
-    assertThat(characteristic.name()).isEqualTo("Memory use");
-    assertThat(characteristic.order()).isEqualTo(2);
-    assertThat(characteristic.parentId()).isNull();
-
-    assertThat(service.characteristicById(111)).isNull();
-  }
-
-  @Test
-  public void create_sub_characteristic() {
-    when(dao.selectById(1, session)).thenReturn(characteristicDto);
-
-    DebtCharacteristic result = service.create("Compilation name", 1);
-
-    assertThat(result.id()).isEqualTo(currentId);
-    assertThat(result.key()).isEqualTo("COMPILATION_NAME");
-    assertThat(result.name()).isEqualTo("Compilation name");
-    assertThat(result.parentId()).isEqualTo(1);
-  }
-
-  @Test
-  public void fail_to_create_sub_characteristic_when_parent_id_is_not_a_root_characteristic() {
-    when(dao.selectById(1, session)).thenReturn(subCharacteristicDto);
-
-    try {
-      service.create("Compilation", 1);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("A sub characteristic can not have a sub characteristic as parent.");
-    }
-  }
-
-  @Test
-  public void fail_to_create_sub_characteristic_when_parent_does_not_exists() {
-    when(dao.selectById(1, session)).thenReturn(null);
-
-    try {
-      service.create("Compilation", 1);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Characteristic with id 1 does not exists.");
-    }
-  }
-
-  @Test
-  public void fail_to_create_sub_characteristic_when_name_already_used() {
-    when(dao.selectByName("Compilation", session)).thenReturn(new CharacteristicDto());
-    when(dao.selectById(1, session)).thenReturn(characteristicDto);
-
-    try {
-      service.create("Compilation", 1);
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.l10nKey()).isEqualTo("errors.is_already_used");
-      assertThat(e.l10nParams().iterator().next()).isEqualTo("Compilation");
-    }
-  }
-
-  @Test
-  public void fail_to_create_sub_characteristic_when_wrong_permission() {
-    MockUserSession.set().setGlobalPermissions(GlobalPermissions.DASHBOARD_SHARING);
-
-    try {
-      service.create("Compilation", 1);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(ForbiddenException.class);
-    }
+    service.characteristicById(111);
+    verify(debtModelLookup).characteristicById(111);
   }
 
   @Test
   public void create_characteristic() {
-    when(dao.selectMaxCharacteristicOrder(session)).thenReturn(2);
-
-    DebtCharacteristic result = service.create("Portability", null);
-
-    assertThat(result.id()).isEqualTo(currentId);
-    assertThat(result.key()).isEqualTo("PORTABILITY");
-    assertThat(result.name()).isEqualTo("Portability");
-    assertThat(result.order()).isEqualTo(3);
-  }
-
-  @Test
-  public void create_first_characteristic() {
-    when(dao.selectMaxCharacteristicOrder(session)).thenReturn(0);
-
-    DebtCharacteristic result = service.create("Portability", null);
-
-    assertThat(result.id()).isEqualTo(currentId);
-    assertThat(result.key()).isEqualTo("PORTABILITY");
-    assertThat(result.name()).isEqualTo("Portability");
-    assertThat(result.order()).isEqualTo(1);
+    service.create("Compilation name", 1);
+    verify(debtModelOperations).create("Compilation name", 1);
   }
 
   @Test
   public void rename_characteristic() {
-    when(dao.selectById(10, session)).thenReturn(subCharacteristicDto);
-
-    DebtCharacteristic result = service.rename(10, "New Efficiency");
-
-    assertThat(result.key()).isEqualTo("EFFICIENCY");
-    assertThat(result.name()).isEqualTo("New Efficiency");
-  }
-
-  @Test
-  public void not_rename_characteristic_when_renaming_with_same_name() {
-    when(dao.selectById(10, session)).thenReturn(subCharacteristicDto);
-
-    service.rename(10, "Efficiency");
-
-    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
-  }
-
-  @Test
-  public void fail_to_rename_unknown_characteristic() {
-    when(dao.selectById(10, session)).thenReturn(null);
-
-    try {
-      service.rename(10, "New Efficiency");
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Characteristic with id 10 does not exists.");
-    }
+    service.rename(10, "New Efficiency");
+    verify(debtModelOperations).rename(10, "New Efficiency");
   }
 
   @Test
   public void move_up() {
-    when(dao.selectById(10, session)).thenReturn(new CharacteristicDto().setId(10).setOrder(2));
-    when(dao.selectPrevious(2, session)).thenReturn(new CharacteristicDto().setId(2).setOrder(1));
-
-    DebtCharacteristic result = service.moveUp(10);
-
-    ArgumentCaptor<CharacteristicDto> argument = ArgumentCaptor.forClass(CharacteristicDto.class);
-    verify(dao, times(2)).update(argument.capture(), eq(session));
-
-    assertThat(result.order()).isEqualTo(1);
-    assertThat(argument.getAllValues().get(0).getOrder()).isEqualTo(2);
-    assertThat(argument.getAllValues().get(1).getOrder()).isEqualTo(1);
-  }
-
-  @Test
-  public void do_nothing_when_move_up_and_already_on_top() {
-    CharacteristicDto dto = new CharacteristicDto().setId(10).setOrder(1);
-    when(dao.selectById(10, session)).thenReturn(dto);
-    when(dao.selectPrevious(1, session)).thenReturn(null);
-
     service.moveUp(10);
-
-    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
+    verify(debtModelOperations).moveUp(10);
   }
 
   @Test
   public void move_down() {
-    when(dao.selectById(10, session)).thenReturn(new CharacteristicDto().setId(10).setOrder(2));
-    when(dao.selectNext(2, session)).thenReturn(new CharacteristicDto().setId(2).setOrder(3));
-
-    DebtCharacteristic result = service.moveDown(10);
-
-    ArgumentCaptor<CharacteristicDto> argument = ArgumentCaptor.forClass(CharacteristicDto.class);
-    verify(dao, times(2)).update(argument.capture(), eq(session));
-
-    assertThat(result.order()).isEqualTo(3);
-    assertThat(argument.getAllValues().get(0).getOrder()).isEqualTo(2);
-    assertThat(argument.getAllValues().get(1).getOrder()).isEqualTo(3);
-  }
-
-  @Test
-  public void do_nothing_when_move_down_and_already_on_bottom() {
-    CharacteristicDto dto = new CharacteristicDto().setId(10).setOrder(5);
-    when(dao.selectById(10, session)).thenReturn(dto);
-    when(dao.selectNext(5, session)).thenReturn(null);
-
     service.moveDown(10);
-
-    verify(dao, never()).update(any(CharacteristicDto.class), eq(session));
+    verify(debtModelOperations).moveDown(10);
   }
 
   @Test
-  public void delete_sub_characteristic() {
-    BatchSession batchSession = mock(BatchSession.class);
-    when(mybatis.openBatchSession()).thenReturn(batchSession);
-
-    when(ruleDao.selectByCharacteristicOrSubCharacteristicId(2, batchSession)).thenReturn(newArrayList(
-      new RuleDto()
-        .setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("5min")
-        .setDefaultCharacteristicId(10).setDefaultRemediationFunction("LINEAR_OFFSET").setDefaultRemediationFactor("4h").setDefaultRemediationOffset("15min")
-    ));
-    when(dao.selectById(2, batchSession)).thenReturn(subCharacteristicDto);
-
+  public void delete_characteristic() {
     service.delete(2);
-
-    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
-    verify(ruleDao).update(ruleArgument.capture(), eq(batchSession));
-    RuleDto ruleDto = ruleArgument.getValue();
-
-    // Overridden debt data are disabled
-    assertThat(ruleDto.getCharacteristicId()).isEqualTo(-1);
-    assertThat(ruleDto.getRemediationFunction()).isNull();
-    assertThat(ruleDto.getRemediationFactor()).isNull();
-    assertThat(ruleDto.getRemediationOffset()).isNull();
-    assertThat(ruleDto.getUpdatedAt()).isEqualTo(now);
-
-    // Default debt data should not be touched
-    assertThat(ruleDto.getDefaultCharacteristicId()).isEqualTo(10);
-    assertThat(ruleDto.getDefaultRemediationFunction()).isEqualTo("LINEAR_OFFSET");
-    assertThat(ruleDto.getDefaultRemediationFactor()).isEqualTo("4h");
-    assertThat(ruleDto.getDefaultRemediationOffset()).isEqualTo("15min");
-
-    ArgumentCaptor<CharacteristicDto> characteristicArgument = ArgumentCaptor.forClass(CharacteristicDto.class);
-    verify(dao).update(characteristicArgument.capture(), eq(batchSession));
-    CharacteristicDto characteristicDto = characteristicArgument.getValue();
-
-    // Sub characteristic is disable
-    assertThat(characteristicDto.getId()).isEqualTo(2);
-    assertThat(characteristicDto.isEnabled()).isFalse();
-    assertThat(characteristicDto.getUpdatedAt()).isEqualTo(now);
+    verify(debtModelOperations).delete(2);
   }
 
   @Test
-  public void delete_characteristic() {
-    BatchSession batchSession = mock(BatchSession.class);
-    when(mybatis.openBatchSession()).thenReturn(batchSession);
-
-    when(ruleDao.selectByCharacteristicOrSubCharacteristicId(1, batchSession)).thenReturn(newArrayList(
-      new RuleDto().setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("5min")
-    ));
-    when(dao.selectCharacteristicsByParentId(1, batchSession)).thenReturn(newArrayList(
-      subCharacteristicDto
-    ));
-    when(dao.selectById(1, batchSession)).thenReturn(characteristicDto);
-
-    service.delete(1);
-
-    verify(ruleDao).update(any(RuleDto.class), eq(batchSession));
-
-    ArgumentCaptor<CharacteristicDto> characteristicArgument = ArgumentCaptor.forClass(CharacteristicDto.class);
-    verify(dao, times(2)).update(characteristicArgument.capture(), eq(batchSession));
-    CharacteristicDto subCharacteristicDto = characteristicArgument.getAllValues().get(0);
-    CharacteristicDto characteristicDto = characteristicArgument.getAllValues().get(1);
-
-    // Sub characteristic is disable
-    assertThat(subCharacteristicDto.getId()).isEqualTo(2);
-    assertThat(subCharacteristicDto.isEnabled()).isFalse();
-    assertThat(subCharacteristicDto.getUpdatedAt()).isEqualTo(now);
-
-    // Characteristic is disable
-    assertThat(characteristicDto.getId()).isEqualTo(1);
-    assertThat(characteristicDto.isEnabled()).isFalse();
-    assertThat(characteristicDto.getUpdatedAt()).isEqualTo(now);
+  public void restore_provided_model() {
+    service.restore();
+    verify(debtModelRestore).restoreFromProvidedModel();
   }
 
 }
index 0ab0f3517323d7081baa0d71c8e23470c513a87d..ca05e80d095054c57b881dc16ebf4ed97e737ebd 100644 (file)
@@ -30,7 +30,8 @@ import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
@@ -40,6 +41,7 @@ import java.io.Reader;
 import java.util.Collections;
 import java.util.List;
 
+import static com.google.common.collect.Lists.newArrayList;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
@@ -65,15 +67,14 @@ public class DebtModelSynchronizerTest {
 
   Integer currentId = 1;
 
-  private DebtCharacteristicsXMLImporter.DebtModel defaultModel;
+  DebtModel defaultModel = new DebtModel();
 
-  private DebtModelSynchronizer manager;
+  DebtModelSynchronizer manager;
 
   @Before
   public void initAndMerge() throws Exception {
     when(myBatis.openSession()).thenReturn(session);
 
-    defaultModel = new DebtCharacteristicsXMLImporter.DebtModel();
     Reader defaultModelReader = mock(Reader.class);
     when(technicalDebtModelRepository.createReaderForXMLFile("technical-debt")).thenReturn(defaultModelReader);
     when(xmlImporter.importXML(eq(defaultModelReader))).thenReturn(defaultModel);
@@ -93,9 +94,10 @@ public class DebtModelSynchronizerTest {
 
   @Test
   public void create_default_model_on_first_execution_when_no_plugin() throws Exception {
-    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("PORTABILITY");
-    new DefaultCharacteristic().setKey("COMPILER_RELATED_PORTABILITY").setParent(rootCharacteristic);
-    defaultModel.addRootCharacteristic(rootCharacteristic);
+    DebtCharacteristic characteristic = new DefaultDebtCharacteristic().setKey("PORTABILITY");
+    DebtCharacteristic subCharacteristic = new DefaultDebtCharacteristic().setKey("COMPILER_RELATED_PORTABILITY");
+    defaultModel.addRootCharacteristic(characteristic);
+    defaultModel.addSubCharacteristic(subCharacteristic, "PORTABILITY");
 
     when(technicalDebtModelRepository.getContributingPluginList()).thenReturn(Collections.<String>emptyList());
     when(dao.selectEnabledCharacteristics()).thenReturn(Lists.<CharacteristicDto>newArrayList());
@@ -114,12 +116,13 @@ public class DebtModelSynchronizerTest {
 
   @Test
   public void not_create_default_model_if_already_exists() throws Exception {
-    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("PORTABILITY");
-    new DefaultCharacteristic().setKey("COMPILER_RELATED_PORTABILITY").setParent(rootCharacteristic);
-    defaultModel.addRootCharacteristic(rootCharacteristic);
+    DebtCharacteristic characteristic = new DefaultDebtCharacteristic().setKey("PORTABILITY");
+    DebtCharacteristic subCharacteristic = new DefaultDebtCharacteristic().setKey("COMPILER_RELATED_PORTABILITY");
+    defaultModel.addRootCharacteristic(characteristic);
+    defaultModel.addSubCharacteristic(subCharacteristic, "PORTABILITY");
 
     when(technicalDebtModelRepository.getContributingPluginList()).thenReturn(Collections.<String>emptyList());
-    when(dao.selectEnabledCharacteristics()).thenReturn(Lists.newArrayList(new CharacteristicDto()));
+    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(new CharacteristicDto()));
 
     manager.synchronize();