]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5056 Backup debt model
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 25 Mar 2014 13:55:13 +0000 (14:55 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 25 Mar 2014 13:55:13 +0000 (14:55 +0100)
35 files changed:
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepository.java [deleted file]
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelSynchronizer.java [deleted file]
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCache.java [deleted file]
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java [deleted file]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelSynchronizerTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCacheTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java [deleted file]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest/csharp-model.xml [deleted file]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest/java-model.xml [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/TechnicalDebtModel.java
sonar-server/src/main/java/org/sonar/server/debt/DebtCharacteristicsXMLImporter.java
sonar-server/src/main/java/org/sonar/server/debt/DebtModel.java [deleted file]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelBackup.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelRestore.java [deleted file]
sonar-server/src/main/java/org/sonar/server/debt/DebtModelService.java
sonar-server/src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java
sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
sonar-server/src/main/java/org/sonar/server/rule/DeprecatedRulesDefinition.java
sonar-server/src/main/java/org/sonar/server/startup/RegisterDebtModel.java
sonar-server/src/test/java/org/sonar/server/debt/DebtCharacteristicsXMLImporterTest.java
sonar-server/src/test/java/org/sonar/server/debt/DebtModelBackupTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/debt/DebtModelRestoreTest.java [deleted file]
sonar-server/src/test/java/org/sonar/server/debt/DebtModelServiceTest.java
sonar-server/src/test/java/org/sonar/server/debt/DebtModelXMLExporterTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/debt/DebtRulesXMLImporterTest.java
sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionTest.java
sonar-server/src/test/java/org/sonar/server/startup/RegisterDebtModelTest.java
sonar-server/src/test/resources/org/sonar/server/debt/DebtModelPluginRepositoryTest/csharp-model.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/debt/DebtModelPluginRepositoryTest/java-model.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/debt/DebtModelXMLExporterTest/export_xml.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/debt/DebtRulesXMLImporterTest/read_integer.xml [new file with mode: 0644]

diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepository.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepository.java
deleted file mode 100644 (file)
index 89031c8..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.technicaldebt;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Maps;
-import org.picocontainer.Startable;
-import org.sonar.api.Plugin;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-/**
- * <p>This class is used to find which technical debt model XML files exist in the Sonar instance.</p>
- * <p>
- * Those XML files are provided by language plugins that embed their own contribution to the definition of the Technical debt model.
- * They must be located in the classpath of those language plugins, more specifically in the "com.sonar.sqale" package, and
- * 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";
-
-  private static final String XML_FILE_SUFFIX = "-model.xml";
-  private static final String XML_FILE_PREFIX = "com/sonar/sqale/";
-
-  private String xmlFilePrefix;
-
-  private PluginRepository pluginRepository;
-  private Map<String, ClassLoader> contributingPluginKeyToClassLoader;
-
-  public TechnicalDebtModelRepository(PluginRepository pluginRepository) {
-    this.pluginRepository = pluginRepository;
-    this.xmlFilePrefix = XML_FILE_PREFIX;
-  }
-
-  @VisibleForTesting
-  TechnicalDebtModelRepository(PluginRepository pluginRepository, String xmlFilePrefix) {
-    this.pluginRepository = pluginRepository;
-    this.xmlFilePrefix = xmlFilePrefix;
-  }
-
-  @VisibleForTesting
-  TechnicalDebtModelRepository(Map<String, ClassLoader> contributingPluginKeyToClassLoader, String xmlFilePrefix) {
-    this.contributingPluginKeyToClassLoader = contributingPluginKeyToClassLoader;
-    this.xmlFilePrefix = xmlFilePrefix;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void start() {
-    findAvailableXMLFiles();
-  }
-
-  protected void findAvailableXMLFiles() {
-    if (contributingPluginKeyToClassLoader == null) {
-      contributingPluginKeyToClassLoader = Maps.newTreeMap();
-      // Add default model
-      contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
-      for (PluginMetadata metadata : pluginRepository.getMetadata()) {
-        String pluginKey = metadata.getKey();
-        Plugin plugin = pluginRepository.getPlugin(pluginKey);
-        if (plugin != null) {
-          ClassLoader classLoader = plugin.getClass().getClassLoader();
-          if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
-            contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
-          }
-        }
-      }
-    }
-    contributingPluginKeyToClassLoader = Collections.unmodifiableMap(contributingPluginKeyToClassLoader);
-  }
-
-  @VisibleForTesting
-  String getXMLFilePath(String model) {
-    return xmlFilePrefix + model + XML_FILE_SUFFIX;
-  }
-
-  /**
-   * Returns the list of plugins that can contribute to the technical debt model.
-   *
-   * @return the list of plugin keys
-   */
-  public Collection<String> getContributingPluginList() {
-    return newArrayList(contributingPluginKeyToClassLoader.keySet());
-  }
-
-  /**
-   * Creates a new {@link java.io.Reader} for the XML file that contains the model contributed by the given plugin.
-   *
-   * @param pluginKey the key of the plugin that contributes the XML file
-   * @return the reader, that must be closed once its use is finished.
-   */
-  public Reader createReaderForXMLFile(String pluginKey) {
-    ClassLoader classLoader = contributingPluginKeyToClassLoader.get(pluginKey);
-    String xmlFilePath = getXMLFilePath(pluginKey);
-    return new InputStreamReader(classLoader.getResourceAsStream(xmlFilePath));
-  }
-
-  @VisibleForTesting
-  Map<String, ClassLoader> getContributingPluginKeyToClassLoader(){
-    return contributingPluginKeyToClassLoader;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void stop() {
-    // Nothing to do
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelSynchronizer.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelSynchronizer.java
deleted file mode 100644 (file)
index f4a76c5..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.technicaldebt;
-
-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.utils.ValidationMessages;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-
-import java.io.Reader;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-/**
- * @deprecated since 4.3
- */
-@Deprecated
-public class TechnicalDebtModelSynchronizer implements ServerExtension {
-
-  private static final Logger LOG = LoggerFactory.getLogger(TechnicalDebtModelSynchronizer.class);
-
-  private final MyBatis mybatis;
-  private final CharacteristicDao dao;
-  private final TechnicalDebtModelRepository languageModelFinder;
-  private final TechnicalDebtXMLImporter importer;
-
-  public TechnicalDebtModelSynchronizer(MyBatis mybatis, CharacteristicDao dao, TechnicalDebtModelRepository modelRepository, TechnicalDebtXMLImporter importer) {
-    this.mybatis = mybatis;
-    this.dao = dao;
-    this.languageModelFinder = modelRepository;
-    this.importer = importer;
-  }
-
-  public List<CharacteristicDto> synchronize(ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
-    SqlSession session = mybatis.openSession();
-
-    List<CharacteristicDto> model = newArrayList();
-    try {
-      model = synchronize(messages, rulesCache, session);
-      session.commit();
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
-    return model;
-  }
-
-  public List<CharacteristicDto> synchronize(ValidationMessages messages, TechnicalDebtRuleCache rulesCache, SqlSession session) {
-    DefaultTechnicalDebtModel defaultModel = loadModelFromXml(TechnicalDebtModelRepository.DEFAULT_MODEL, messages, rulesCache);
-    List<CharacteristicDto> model = loadOrCreateModelFromDb(defaultModel, session);
-    messages.log(LOG);
-
-    return model;
-  }
-
-  private List<CharacteristicDto> loadOrCreateModelFromDb(DefaultTechnicalDebtModel defaultModel, SqlSession session) {
-    List<CharacteristicDto> characteristicDtos = loadModel();
-    if (characteristicDtos.isEmpty()) {
-      return createTechnicalDebtModel(defaultModel, session);
-    }
-    return characteristicDtos;
-  }
-
-  private List<CharacteristicDto> loadModel() {
-    return dao.selectEnabledCharacteristics();
-  }
-
-  private List<CharacteristicDto> createTechnicalDebtModel(DefaultTechnicalDebtModel defaultModel, SqlSession session) {
-    List<CharacteristicDto> characteristics = newArrayList();
-    for (DefaultCharacteristic rootCharacteristic : defaultModel.rootCharacteristics()) {
-      CharacteristicDto rootCharacteristicDto = CharacteristicDto.toDto(rootCharacteristic, null);
-      dao.insert(rootCharacteristicDto, session);
-      characteristics.add(rootCharacteristicDto);
-      for (DefaultCharacteristic characteristic : rootCharacteristic.children()) {
-        CharacteristicDto characteristicDto = CharacteristicDto.toDto(characteristic, rootCharacteristicDto.getId());
-        dao.insert(characteristicDto, session);
-        characteristics.add(characteristicDto);
-      }
-    }
-    return characteristics;
-  }
-
-  public DefaultTechnicalDebtModel loadModelFromXml(String pluginKey, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
-    Reader xmlFileReader = null;
-    try {
-      xmlFileReader = languageModelFinder.createReaderForXMLFile(pluginKey);
-      return importer.importXML(xmlFileReader, messages, rulesCache);
-    } finally {
-      IOUtils.closeQuietly(xmlFileReader);
-    }
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCache.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCache.java
deleted file mode 100644 (file)
index 6e06fcb..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.technicaldebt;
-
-import com.google.common.collect.Maps;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-
-import javax.annotation.CheckForNull;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @deprecated since 4.3
- */
-@Deprecated
-public class TechnicalDebtRuleCache {
-
-  private final RuleFinder ruleFinder;
-
-  private Map<String, Map<String, Rule>> cachedRules;
-  private Map<Integer, Rule> cachedRulesId;
-
-  public TechnicalDebtRuleCache(RuleFinder ruleFinder) {
-    this.ruleFinder = ruleFinder;
-  }
-
-  @CheckForNull
-  public Rule getByRuleKey(RuleKey ruleKey) {
-    initRules();
-    return lookUpRuleInCache(ruleKey.repository(), ruleKey.rule());
-  }
-
-  @CheckForNull
-  public Rule getByRuleId(Integer ruleId) {
-    initRules();
-    return cachedRulesId.get(ruleId);
-  }
-
-  public boolean exists(Integer ruleId) {
-    initRules();
-    return getByRuleId(ruleId) != null;
-  }
-
-  public boolean exists(RuleKey ruleKey) {
-    return getByRuleKey(ruleKey) != null;
-  }
-
-  private void initRules(){
-    if(cachedRules == null) {
-      loadRules();
-    }
-  }
-
-  private void loadRules() {
-    cachedRules = Maps.newHashMap();
-    cachedRulesId = Maps.newHashMap();
-    Collection<Rule> rules = ruleFinder.findAll(RuleQuery.create());
-    for (Rule rule : rules) {
-      if(!cachedRules.containsKey(rule.getRepositoryKey())) {
-        cachedRules.put(rule.getRepositoryKey(), new HashMap<String, Rule>());
-      }
-      Map<String, Rule> cachedRepository = cachedRules.get(rule.getRepositoryKey());
-      if(!cachedRepository.containsKey(rule.getKey())) {
-        cachedRepository.put(rule.getKey(), rule);
-        cachedRulesId.put(rule.getId(), rule);
-      }
-    }
-  }
-
-  @CheckForNull
-  private Rule lookUpRuleInCache(String repository, String ruleKey) {
-    Map<String, Rule> cachedRepository = cachedRules.get(repository);
-    if(cachedRepository != null) {
-      return cachedRepository.get(ruleKey);
-    }
-    return null;
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java
deleted file mode 100644 (file)
index b0a6603..0000000
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.technicaldebt;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang.math.NumberUtils;
-import org.codehaus.stax2.XMLInputFactory2;
-import org.codehaus.staxmate.SMInputFactory;
-import org.codehaus.staxmate.in.SMHierarchicCursor;
-import org.codehaus.staxmate.in.SMInputCursor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.api.utils.internal.WorkDuration;
-
-import javax.annotation.CheckForNull;
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-/**
- * @deprecated since 4.3
- */
-@Deprecated
-public class TechnicalDebtXMLImporter implements ServerExtension {
-
-  private static final Logger LOG = LoggerFactory.getLogger(TechnicalDebtXMLImporter.class);
-
-  public static final String CHARACTERISTIC = "chc";
-  public static final String CHARACTERISTIC_KEY = "key";
-  public static final String CHARACTERISTIC_NAME = "name";
-  public static final String PROPERTY = "prop";
-
-  public static final String PROPERTY_KEY = "key";
-  public static final String PROPERTY_VALUE = "val";
-  public static final String PROPERTY_TEXT_VALUE = "txt";
-
-  public static final String REPOSITORY_KEY = "rule-repo";
-  public static final String RULE_KEY = "rule-key";
-
-  public static final String PROPERTY_FUNCTION = "remediationFunction";
-  public static final String PROPERTY_FACTOR = "remediationFactor";
-  public static final String PROPERTY_OFFSET = "offset";
-
-  public DefaultTechnicalDebtModel importXML(String xml, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) {
-    return importXML(new StringReader(xml), messages, technicalDebtRuleCache);
-  }
-
-  public DefaultTechnicalDebtModel importXML(Reader xml, ValidationMessages messages, TechnicalDebtRuleCache repositoryCache) {
-    DefaultTechnicalDebtModel model = new DefaultTechnicalDebtModel();
-    try {
-      SMInputFactory inputFactory = initStax();
-      SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml);
-
-      // advance to <sqale>
-      cursor.advance();
-      SMInputCursor chcCursor = cursor.childElementCursor(CHARACTERISTIC);
-
-      while (chcCursor.getNext() != null) {
-        processCharacteristic(model, null, chcCursor, messages, repositoryCache);
-      }
-
-      cursor.getStreamReader().closeCompletely();
-
-    } catch (XMLStreamException e) {
-      LOG.error("XML is not valid", e);
-      messages.addErrorText("XML is not valid: " + e.getMessage());
-    }
-    return model;
-  }
-
-  private SMInputFactory initStax() {
-    XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
-    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
-    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
-    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
-    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
-    return new SMInputFactory(xmlFactory);
-  }
-
-  private DefaultCharacteristic processCharacteristic(DefaultTechnicalDebtModel model, DefaultCharacteristic parent, SMInputCursor chcCursor, ValidationMessages messages,
-                                                      TechnicalDebtRuleCache technicalDebtRuleCache) throws XMLStreamException {
-    DefaultCharacteristic characteristic = new DefaultCharacteristic();
-    SMInputCursor cursor = chcCursor.childElementCursor();
-    while (cursor.getNext() != null) {
-      String node = cursor.getLocalName();
-      if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
-        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);
-
-      } else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) {
-        characteristic.setName(cursor.collectDescendantText().trim(), false);
-
-        // <chc> can contain characteristics or requirements
-      } else if (StringUtils.equals(node, CHARACTERISTIC)) {
-        processCharacteristic(model, characteristic, cursor, messages, technicalDebtRuleCache);
-
-      } else if (StringUtils.equals(node, REPOSITORY_KEY)) {
-        DefaultRequirement requirement = processRequirement(cursor, messages, technicalDebtRuleCache);
-        if (requirement != null) {
-          addRequirement(requirement, parent, messages);
-        }
-      }
-    }
-
-    if (StringUtils.isNotBlank(characteristic.key()) && characteristic.isRoot()) {
-      characteristic.setOrder(model.rootCharacteristics().size() + 1);
-      model.addRootCharacteristic(characteristic);
-      return characteristic;
-    }
-    return null;
-  }
-
-  private void addRequirement(DefaultRequirement requirement, DefaultCharacteristic parent, ValidationMessages messages) {
-    DefaultCharacteristic root = parent.parent();
-    if (root == null) {
-      messages.addWarningText("Requirement '" + requirement.ruleKey() + "' is ignored because it's defined directly under a root characteristic.");
-    } else {
-      requirement.setCharacteristic(parent);
-      requirement.setRootCharacteristic(root);
-    }
-  }
-
-  private DefaultRequirement processRequirement(SMInputCursor cursor, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache)
-    throws XMLStreamException {
-
-    DefaultRequirement requirement = new DefaultRequirement();
-    String ruleRepositoryKey = cursor.collectDescendantText().trim();
-    String ruleKey = null;
-    Properties properties = new Properties();
-    while (cursor.getNext() != null) {
-      String node = cursor.getLocalName();
-      if (StringUtils.equals(node, PROPERTY)) {
-        properties.add(processProperty(cursor, messages));
-      } else if (StringUtils.equals(node, RULE_KEY)) {
-        ruleKey = cursor.collectDescendantText().trim();
-      }
-    }
-    fillRule(requirement, ruleRepositoryKey, ruleKey, messages, technicalDebtRuleCache);
-    if (requirement.ruleKey() == null) {
-      return null;
-    }
-    return processFunctionsOnRequirement(requirement, properties, messages);
-  }
-
-  private void fillRule(DefaultRequirement requirement, String ruleRepositoryKey, String ruleKey, ValidationMessages messages,
-                        TechnicalDebtRuleCache technicalDebtRuleCache) {
-    if (StringUtils.isNotBlank(ruleRepositoryKey) && StringUtils.isNotBlank(ruleKey)) {
-      Rule rule = technicalDebtRuleCache.getByRuleKey(RuleKey.of(ruleRepositoryKey, ruleKey));
-      if (rule != null) {
-        requirement.setRuleKey(RuleKey.of(ruleRepositoryKey, ruleKey));
-      } else {
-        messages.addWarningText("Rule not found: [repository=" + ruleRepositoryKey + ", key=" + ruleKey + "]");
-      }
-    }
-  }
-
-  private Property processProperty(SMInputCursor cursor, ValidationMessages messages) throws XMLStreamException {
-    SMInputCursor c = cursor.childElementCursor();
-    String key = null;
-    int value = 0;
-    String textValue = null;
-    while (c.getNext() != null) {
-      String node = c.getLocalName();
-      if (StringUtils.equals(node, PROPERTY_KEY)) {
-        key = c.collectDescendantText().trim();
-
-      } else if (StringUtils.equals(node, PROPERTY_VALUE)) {
-        String s = c.collectDescendantText().trim();
-        try {
-          // The value is still a double for the moment
-          Double valueDouble = NumberUtils.createDouble(s);
-          value = valueDouble.intValue();
-        } catch (NumberFormatException ex) {
-          messages.addErrorText(String.format("Cannot import value '%s' for field %s - Expected a numeric value instead", s, key));
-        }
-      } else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) {
-        textValue = c.collectDescendantText().trim();
-      }
-    }
-    return new Property(key, value, textValue);
-  }
-
-  @CheckForNull
-  private DefaultRequirement processFunctionsOnRequirement(DefaultRequirement requirement, Properties properties, ValidationMessages messages) {
-    Property function = properties.function();
-    Property factor = properties.factor();
-    Property offset = properties.offset();
-
-    if (function != null) {
-      // Requirements should always have values, so we init it with default values
-      requirement.setFactorValue(0);
-      requirement.setFactorUnit(WorkDuration.UNIT.DAYS);
-      requirement.setOffsetValue(0);
-      requirement.setOffsetUnit(WorkDuration.UNIT.DAYS);
-
-      String functionKey = function.getTextValue();
-      if ("linear_threshold".equals(functionKey)) {
-        function.setTextValue(DefaultRequirement.FUNCTION_LINEAR);
-        offset.setValue(0);
-        offset.setTextValue("d");
-        messages.addWarningText(String.format("Linear with threshold function is no longer used, function of the requirement '%s' is replaced by linear.", requirement.ruleKey()));
-      } else if ("constant_resource".equals(functionKey)) {
-        messages.addWarningText(String.format("Constant/file function is no longer used, requirements '%s' are ignored.", requirement.ruleKey()));
-        return null;
-      }
-
-      requirement.setFunction(function.getTextValue());
-      if (factor != null) {
-        requirement.setFactorValue(factor.getValue());
-        if (!Strings.isNullOrEmpty(factor.getTextValue())) {
-          requirement.setFactorUnit(DefaultRequirement.toUnit(factor.getTextValue()));
-        }
-      }
-      if (offset != null) {
-        requirement.setOffsetValue(offset.getValue());
-        if (!Strings.isNullOrEmpty(offset.getTextValue())) {
-          requirement.setOffsetUnit(DefaultRequirement.toUnit(offset.getTextValue()));
-        }
-      }
-      return requirement;
-    }
-    return null;
-  }
-
-  private static class Properties {
-    List<Property> properties;
-
-    public Properties() {
-      this.properties = newArrayList();
-    }
-
-    public Properties add(Property property) {
-      this.properties.add(property);
-      return this;
-    }
-
-    public Property function() {
-      return find(PROPERTY_FUNCTION);
-    }
-
-    public Property factor() {
-      return find(PROPERTY_FACTOR);
-    }
-
-    public Property offset() {
-      return find(PROPERTY_OFFSET);
-    }
-
-    private Property find(final String key) {
-      return Iterables.find(properties, new Predicate<Property>() {
-        @Override
-        public boolean apply(Property input) {
-          return input.getKey().equals(key);
-        }
-      }, null);
-    }
-
-  }
-
-  private static class Property {
-    String key;
-    int value;
-    String textValue;
-
-    private Property(String key, int value, String textValue) {
-      this.key = key;
-      this.value = value;
-      this.textValue = textValue;
-    }
-
-    private Property setValue(int value) {
-      this.value = value;
-      return this;
-    }
-
-    private Property setTextValue(String textValue) {
-      this.textValue = textValue;
-      return this;
-    }
-
-    private String getKey() {
-      return key;
-    }
-
-    private int getValue() {
-      return value;
-    }
-
-    private String getTextValue() {
-      return textValue;
-    }
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest.java
deleted file mode 100644 (file)
index f74e95f..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.technicaldebt;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.Resources;
-import org.apache.commons.io.IOUtils;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.SonarPlugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.Reader;
-import java.net.MalformedURLException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TechnicalDebtModelRepositoryTest {
-
-  private static final String TEST_XML_PREFIX_PATH = "org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest/";
-
-  private TechnicalDebtModelRepository modelFinder;
-
-  @Test
-  public void test_component_initialization() throws Exception {
-    // we do have the "csharp-model.xml" file in src/test/resources
-    PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class);
-    when(csharpPluginMetadata.getKey()).thenReturn("csharp");
-
-    // but we don' have the "php-model.xml" one
-    PluginMetadata phpPluginMetadata = mock(PluginMetadata.class);
-    when(phpPluginMetadata.getKey()).thenReturn("php");
-
-    PluginRepository repository = mock(PluginRepository.class);
-    when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
-    FakePlugin fakePlugin = new FakePlugin();
-    when(repository.getPlugin(anyString())).thenReturn(fakePlugin);
-    modelFinder = new TechnicalDebtModelRepository(repository, TEST_XML_PREFIX_PATH);
-
-    // when
-    modelFinder.start();
-
-    // assert
-    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
-    assertThat(contributingPluginList.size()).isEqualTo(2);
-    assertThat(contributingPluginList).containsOnly("technical-debt", "csharp");
-  }
-
-  @Test
-  public void contributing_plugin_list() throws Exception {
-    initModel();
-    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
-    assertThat(contributingPluginList.size()).isEqualTo(2);
-    assertThat(contributingPluginList).contains("csharp", "java");
-  }
-
-  @Test
-  public void get_content_for_xml_file() throws Exception {
-    initModel();
-    Reader xmlFileReader = null;
-    try {
-      xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
-      assertNotNull(xmlFileReader);
-      List<String> lines = IOUtils.readLines(xmlFileReader);
-      assertThat(lines.size()).isEqualTo(25);
-      assertThat(lines.get(0)).isEqualTo("<sqale>");
-    } catch (Exception e) {
-      fail("Should be able to read the XML file.");
-    } finally {
-      IOUtils.closeQuietly(xmlFileReader);
-    }
-  }
-
-  @Test
-  public void return_xml_file_path_for_plugin() throws Exception {
-    initModel();
-    assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
-  }
-
-  @Test
-  public void contain_default_model() throws Exception {
-    modelFinder = new TechnicalDebtModelRepository(mock(PluginRepository.class));
-    modelFinder.start();
-    assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
-  }
-
-  private void initModel() throws MalformedURLException {
-    Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
-    contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
-    contributingPluginKeyToClassLoader.put("java", newClassLoader());
-    modelFinder = new TechnicalDebtModelRepository(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
-  }
-
-  private ClassLoader newClassLoader() throws MalformedURLException {
-    ClassLoader loader = mock(ClassLoader.class);
-    when(loader.getResourceAsStream(anyString())).thenAnswer(new Answer<InputStream>() {
-      public InputStream answer(InvocationOnMock invocation) throws Throwable {
-        return new FileInputStream(Resources.getResource((String) invocation.getArguments()[0]).getPath());
-      }
-    });
-    return loader;
-  }
-
-  class FakePlugin extends SonarPlugin {
-    public List getExtensions() {
-      return null;
-    }
-  }
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelSynchronizerTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelSynchronizerTest.java
deleted file mode 100644 (file)
index e2c1237..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.technicaldebt;
-
-import com.google.common.collect.Lists;
-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.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-
-import java.io.Reader;
-import java.util.Collections;
-import java.util.List;
-
-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 TechnicalDebtModelSynchronizerTest {
-
-  @Mock
-  MyBatis myBatis;
-
-  @Mock
-  SqlSession session;
-
-  @Mock
-  TechnicalDebtModelRepository technicalDebtModelRepository;
-
-  @Mock
-  TechnicalDebtRuleCache ruleCache;
-
-  @Mock
-  CharacteristicDao dao;
-
-  @Mock
-  TechnicalDebtXMLImporter xmlImporter;
-
-  Integer currentId = 1;
-
-  private DefaultTechnicalDebtModel defaultModel;
-
-  private TechnicalDebtModelSynchronizer manager;
-
-  @Before
-  public void initAndMerge() throws Exception {
-    when(myBatis.openSession()).thenReturn(session);
-
-    defaultModel = new DefaultTechnicalDebtModel();
-    Reader defaultModelReader = mock(Reader.class);
-    when(technicalDebtModelRepository.createReaderForXMLFile("technical-debt")).thenReturn(defaultModelReader);
-    when(xmlImporter.importXML(eq(defaultModelReader), any(ValidationMessages.class), eq(ruleCache))).thenReturn(defaultModel);
-
-    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));
-
-
-    manager = new TechnicalDebtModelSynchronizer(myBatis, dao, technicalDebtModelRepository, xmlImporter);
-  }
-
-  @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);
-
-    when(technicalDebtModelRepository.getContributingPluginList()).thenReturn(Collections.<String>emptyList());
-    when(dao.selectEnabledCharacteristics()).thenReturn(Lists.<CharacteristicDto>newArrayList());
-
-    manager.synchronize(ValidationMessages.create(), ruleCache);
-
-    verify(dao).selectEnabledCharacteristics();
-    ArgumentCaptor<CharacteristicDto> characteristicCaptor = ArgumentCaptor.forClass(CharacteristicDto.class);
-    verify(dao, times(2)).insert(characteristicCaptor.capture(), eq(session));
-
-    List<CharacteristicDto> result = characteristicCaptor.getAllValues();
-    assertThat(result.get(0).getKey()).isEqualTo("PORTABILITY");
-    assertThat(result.get(1).getKey()).isEqualTo("COMPILER_RELATED_PORTABILITY");
-    verifyNoMoreInteractions(dao);
-  }
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCacheTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCacheTest.java
deleted file mode 100644 (file)
index 8e0afd5..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.technicaldebt;
-
-import com.google.common.collect.Lists;
-import org.fest.assertions.Assertions;
-import org.junit.Test;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-
-import java.util.Collections;
-
-public class TechnicalDebtRuleCacheTest {
-
-  @Test
-  public void lazy_load_rules_on_first_call() throws Exception {
-
-    RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
-    Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Collections.EMPTY_LIST);
-
-    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
-    technicalDebtRuleCache.getByRuleKey(RuleKey.of("repo1", "rule1"));
-    technicalDebtRuleCache.getByRuleKey(RuleKey.of("repo1", "rule1"));
-
-    Mockito.verify(ruleFinder, Mockito.times(1)).findAll(Matchers.any(RuleQuery.class));
-  }
-
-  @Test
-  public void return_matching_rule() throws Exception {
-
-    Rule rule1 = Rule.create("repo1", "rule1");
-    Rule rule2 = Rule.create("repo2", "rule2");
-
-    RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
-    Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1, rule2));
-
-    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
-    Rule actualRule1 = technicalDebtRuleCache.getByRuleKey(RuleKey.of("repo1", "rule1"));
-    Rule actualRule2 = technicalDebtRuleCache.getByRuleKey(RuleKey.of("repo2", "rule2"));
-
-    Assertions.assertThat(actualRule1).isEqualTo(rule1);
-    Assertions.assertThat(actualRule2).isEqualTo(rule2);
-  }
-
-  @Test
-  public void return_if_rule_exists() throws Exception {
-
-    Rule rule1 = Rule.create("repo1", "rule1");
-
-    RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
-    Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1));
-
-    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
-
-    Assertions.assertThat(technicalDebtRuleCache.exists(RuleKey.of("repo1", "rule1"))).isTrue();
-    Assertions.assertThat(technicalDebtRuleCache.exists(RuleKey.of("repo2", "rule2"))).isFalse();
-  }
-
-  @Test
-  public void return_if_rule_id_exists() throws Exception {
-
-    Rule rule1 = Rule.create("repo1", "rule1");
-    rule1.setId(1);
-
-    RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
-    Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1));
-
-    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
-
-    Assertions.assertThat(technicalDebtRuleCache.exists(1)).isTrue();
-    Assertions.assertThat(technicalDebtRuleCache.exists(2)).isFalse();
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java
deleted file mode 100644 (file)
index 4ff4e1f..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.technicaldebt;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.io.Resources;
-import org.junit.Test;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.api.utils.internal.WorkDuration;
-
-import java.io.IOException;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class TechnicalDebtXMLImporterTest {
-
-  @Test
-  public void import_characteristics() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("import_characteristics.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    assertThat(sqale.rootCharacteristics()).hasSize(2);
-    assertThat(sqale.rootCharacteristics().get(0).key()).isEqualTo("PORTABILITY");
-    assertThat(sqale.rootCharacteristics().get(1).key()).isEqualTo("MAINTAINABILITY");
-
-    DefaultCharacteristic portability = sqale.characteristicByKey("PORTABILITY");
-    assertThat(portability.order()).isEqualTo(1);
-    assertThat(portability.children()).hasSize(2);
-    assertThat(portability.children().get(0).key()).isEqualTo("COMPILER_RELATED_PORTABILITY");
-    assertThat(sqale.characteristicByKey("COMPILER_RELATED_PORTABILITY").parent().key()).isEqualTo("PORTABILITY");
-    assertThat(portability.children().get(1).key()).isEqualTo("HARDWARE_RELATED_PORTABILITY");
-    assertThat(sqale.characteristicByKey("HARDWARE_RELATED_PORTABILITY").parent().key()).isEqualTo("PORTABILITY");
-
-    DefaultCharacteristic maintainability = sqale.characteristicByKey("MAINTAINABILITY");
-    assertThat(maintainability.order()).isEqualTo(2);
-    assertThat(maintainability.children()).hasSize(1);
-    assertThat(maintainability.children().get(0).key()).isEqualTo("READABILITY");
-    assertThat(sqale.characteristicByKey("READABILITY").parent().key()).isEqualTo("MAINTAINABILITY");
-  }
-
-  @Test
-  public void use_default_unit_when_no_unit() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("use_default_unit_when_no_unit.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    DefaultCharacteristic memoryEfficiency = sqale.characteristicByKey("MEMORY_EFFICIENCY");
-    DefaultRequirement requirement = memoryEfficiency.requirements().get(0);
-    assertThat(requirement.factorValue()).isEqualTo(3);
-    assertThat(requirement.factorUnit()).isEqualTo(WorkDuration.UNIT.DAYS);
-    assertThat(requirement.offsetValue()).isEqualTo(1);
-    assertThat(requirement.offsetUnit()).isEqualTo(WorkDuration.UNIT.DAYS);
-  }
-
-  @Test
-  public void import_xml_with_linear_function() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("shouldImportXML_with_linear.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    checkXmlCorrectlyImported(sqale, messages);
-  }
-
-  @Test
-  public void import_xml_with_linear_with_offset() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("shouldImportXML_with_linear_with_offset.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    checkXmlCorrectlyImported(sqale, 1, WorkDuration.UNIT.HOURS, messages);
-  }
-
-  @Test
-  public void convert_deprecated_linear_with_threshold_function_by_linear_function() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("shouldImportXML_with_deprecated_linear_with_threshold.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    checkXmlCorrectlyImported(sqale, 0, WorkDuration.UNIT.DAYS, messages);
-    assertThat(messages.getWarnings()).hasSize(1);
-  }
-
-  @Test
-  public void ignore_deprecated_constant_per_file_function() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("shouldImportXML_with_deprecated_constant_per_file.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    assertThat(messages.getWarnings()).hasSize(1);
-
-    // characteristics
-    assertThat(sqale.rootCharacteristics()).hasSize(1);
-    DefaultCharacteristic efficiency = sqale.characteristicByKey("EFFICIENCY");
-    assertThat(efficiency.requirements()).isEmpty();
-  }
-
-  @Test
-  public void ignore_requirement_on_root_characteristics() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("ignore_requirement_on_root_characteristics.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    assertThat(messages.getWarnings()).hasSize(1);
-
-    assertThat(sqale.characteristics()).hasSize(1);
-    DefaultCharacteristic efficiency = sqale.characteristicByKey("EFFICIENCY");
-    assertThat(efficiency.requirements()).isEmpty();
-    assertThat(messages.getWarnings().get(0)).contains("checkstyle");
-  }
-
-  @Test
-  public void shouldBadlyFormattedImportXML() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-    String xml = getFileContent("shouldImportXML_badly-formatted.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    checkXmlCorrectlyImported(sqale, messages);
-  }
-
-  @Test
-  public void ignore_requirement_with_not_found_rule() {
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-    String xml = getFileContent("shouldLogWarningIfRuleNotFound.xml");
-    ValidationMessages messages = ValidationMessages.create();
-
-    DefaultTechnicalDebtModel sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    assertThat(messages.getWarnings()).hasSize(1);
-    assertThat(messages.getWarnings().get(0)).isEqualTo("Rule not found: [repository=findbugs, key=Foo]");
-
-    // characteristics
-    assertThat(sqale.rootCharacteristics()).hasSize(1);
-    DefaultCharacteristic efficiency = sqale.characteristicByKey("EFFICIENCY");
-    assertThat(efficiency.requirements()).isEmpty();
-    assertThat(messages.getWarnings().get(0)).contains("findbugs");
-  }
-
-  @Test
-  public void shouldNotifyOnUnexpectedValueTypeInXml() throws Exception {
-
-    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
-
-    String xml = getFileContent("shouldRejectXML_with_invalid_value.xml");
-    ValidationMessages messages = ValidationMessages.create();
-
-    new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
-
-    assertThat(messages.getErrors()).hasSize(1);
-    assertThat(messages.getErrors().get(0)).isEqualTo("Cannot import value 'abc' for field factor - Expected a numeric value instead");
-  }
-
-  private TechnicalDebtRuleCache mockRuleCache() {
-    RuleFinder finder = Mockito.mock(RuleFinder.class);
-    Mockito.when(finder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(Rule.create("checkstyle", "Regexp", "Regular expression")));
-    return new TechnicalDebtRuleCache(finder);
-  }
-
-  private void checkXmlCorrectlyImported(DefaultTechnicalDebtModel sqale, ValidationMessages messages) {
-    checkXmlCorrectlyImported(sqale, 0, WorkDuration.UNIT.DAYS, messages);
-  }
-
-  private void checkXmlCorrectlyImported(DefaultTechnicalDebtModel sqale, Integer offsetValue, WorkDuration.UNIT offsetUnit, ValidationMessages messages) {
-    assertThat(messages.getErrors()).isEmpty();
-
-    // characteristics
-    assertThat(sqale.rootCharacteristics()).hasSize(2);
-    DefaultCharacteristic efficiency = sqale.characteristicByKey("EFFICIENCY");
-    assertThat(efficiency.name()).isEqualTo("Efficiency");
-
-    // sub-characteristics
-    assertThat(efficiency.children()).hasSize(1);
-    DefaultCharacteristic memoryEfficiency = sqale.characteristicByKey("MEMORY_EFFICIENCY");
-    assertThat(memoryEfficiency.name()).isEqualTo("Memory use");
-
-    // requirement
-    assertThat(memoryEfficiency.requirements()).hasSize(1);
-    DefaultRequirement requirement = memoryEfficiency.requirements().get(0);
-    assertThat(requirement.ruleKey().repository()).isEqualTo("checkstyle");
-    assertThat(requirement.ruleKey().rule()).isEqualTo("Regexp");
-    assertThat(requirement.function()).isEqualTo("linear");
-    assertThat(requirement.factorValue()).isEqualTo(3);
-    assertThat(requirement.factorUnit()).isEqualTo(WorkDuration.UNIT.HOURS);
-    assertThat(requirement.offsetValue()).isEqualTo(offsetValue);
-    assertThat(requirement.offsetUnit()).isEqualTo(offsetUnit);
-    assertThat(requirement.characteristic().key()).isEqualTo("MEMORY_EFFICIENCY");
-    assertThat(requirement.rootCharacteristic().key()).isEqualTo("EFFICIENCY");
-  }
-
-  private String getFileContent(String file) {
-    try {
-      return Resources.toString(Resources.getResource(TechnicalDebtXMLImporterTest.class, "TechnicalDebtXMLImporterTest/" + file), Charsets.UTF_8);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-}
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest/csharp-model.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest/csharp-model.xml
deleted file mode 100644 (file)
index e4569a2..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<sqale>
-  <chc>
-    <key>USABILITY</key>
-    <name>Usability</name>
-    <desc>Estimate usability</desc>
-  </chc>
-  <chc>
-    <key>EFFICIENCY</key>
-    <name>Efficiency</name>
-    <chc>
-      <rule-repo>gendarme</rule-repo>
-      <rule-key>EnsureLocalDisposalRule</rule-key>
-      <prop>
-        <key>remediationFactor</key>
-        <val>0.125</val>
-        <txt>d</txt>
-      </prop>
-      <prop>
-        <key>remediationFunction</key>
-        <txt>linear</txt>
-      </prop>
-    </chc>
-  </chc>
-
-</sqale>
\ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest/java-model.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelRepositoryTest/java-model.xml
deleted file mode 100644 (file)
index 0b37f56..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<sqale>
-  <chc>
-    <key>USABILITY</key>
-    <name>Usability</name>
-    <desc>Estimate usability</desc>
-  </chc>
-  <chc>
-    <key>EFFICIENCY</key>
-    <name>Efficiency</name>
-    <chc>
-      <rule-repo>squid-cobol</rule-repo>
-      <rule-key>CheckLoop</rule-key>
-      <prop>
-        <key>remediationFactor</key>
-        <val>0.125</val>
-        <txt>d</txt>
-      </prop>
-      <prop>
-        <key>remediationFunction</key>
-        <txt>linear</txt>
-      </prop>
-    </chc>
-  </chc>
-
-</sqale>
\ No newline at end of file
index 4cca21cd19fb9ac095bda2358f0ac5a16aff363c..a18212ec83ec480e587f82c9e8407413eea6917f 100644 (file)
@@ -29,6 +29,7 @@ import java.util.List;
 
 /**
  * @since 4.1
+ * Used by Views plugin
  */
 public interface TechnicalDebtModel {
 
index d19fc9a163cedaef3e301869b84dba1baf6b407a..d0855fe34d8a2e629be69961d909d4b013ad473f 100644 (file)
@@ -25,7 +25,7 @@ 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.ServerComponent;
 import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
 
 import javax.annotation.CheckForNull;
@@ -36,11 +36,12 @@ import javax.xml.stream.XMLStreamException;
 import java.io.Reader;
 import java.io.StringReader;
 
-public class DebtCharacteristicsXMLImporter implements ServerExtension {
+import static org.sonar.server.debt.DebtModelXMLExporter.*;
 
-  public static final String CHARACTERISTIC = "chc";
-  public static final String CHARACTERISTIC_KEY = "key";
-  public static final String CHARACTERISTIC_NAME = "name";
+/**
+ * Import characteristics from an xml
+ */
+public class DebtCharacteristicsXMLImporter implements ServerComponent {
 
   public DebtModel importXML(String xml) {
     return importXML(new StringReader(xml));
@@ -102,4 +103,5 @@ public class DebtCharacteristicsXMLImporter implements ServerExtension {
       }
     }
   }
+
 }
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
deleted file mode 100644 (file)
index be3fa01..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-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/DebtModelBackup.java b/sonar-server/src/main/java/org/sonar/server/debt/DebtModelBackup.java
new file mode 100644 (file)
index 0000000..ec9ac28
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * 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.commons.lang.builder.EqualsBuilder;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
+import org.sonar.api.server.rule.DebtRemediationFunction;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.ValidationMessages;
+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.user.UserSession;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.Reader;
+import java.util.Date;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.sonar.server.debt.DebtModelXMLExporter.DebtModel;
+import static org.sonar.server.debt.DebtModelXMLExporter.RuleDebt;
+
+public class DebtModelBackup implements ServerComponent {
+
+  private final MyBatis mybatis;
+  private final CharacteristicDao dao;
+  private final RuleDao ruleDao;
+  private final DebtModelOperations debtModelOperations;
+  private final DebtModelPluginRepository debtModelPluginRepository;
+  private final DebtCharacteristicsXMLImporter characteristicsXMLImporter;
+  private final DebtRulesXMLImporter rulesXMLImporter;
+  private final DebtModelXMLExporter debtModelXMLExporter;
+  private final System2 system2;
+
+  public DebtModelBackup(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, DebtModelOperations debtModelOperations, DebtModelPluginRepository debtModelPluginRepository,
+                         DebtCharacteristicsXMLImporter characteristicsXMLImporter, DebtRulesXMLImporter rulesXMLImporter,
+                         DebtModelXMLExporter debtModelXMLExporter) {
+    this(mybatis, dao, ruleDao, debtModelOperations, debtModelPluginRepository, characteristicsXMLImporter, rulesXMLImporter, debtModelXMLExporter,
+      System2.INSTANCE);
+  }
+
+  @VisibleForTesting
+  DebtModelBackup(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, DebtModelOperations debtModelOperations, DebtModelPluginRepository debtModelPluginRepository,
+                  DebtCharacteristicsXMLImporter characteristicsXMLImporter, DebtRulesXMLImporter rulesXMLImporter,
+                  DebtModelXMLExporter debtModelXMLExporter, System2 system2) {
+    this.mybatis = mybatis;
+    this.dao = dao;
+    this.ruleDao = ruleDao;
+    this.debtModelOperations = debtModelOperations;
+    this.debtModelPluginRepository = debtModelPluginRepository;
+    this.characteristicsXMLImporter = characteristicsXMLImporter;
+    this.rulesXMLImporter = rulesXMLImporter;
+    this.debtModelXMLExporter = debtModelXMLExporter;
+    this.system2 = system2;
+  }
+
+  public String backup() {
+    return backupFromLanguage(null);
+  }
+
+  public String backup(String languageKey) {
+    return backupFromLanguage(languageKey);
+  }
+
+  private String backupFromLanguage(@Nullable String languageKey) {
+    checkPermission();
+
+    SqlSession session = mybatis.openSession();
+    try {
+      DebtModel debtModel = new DebtModel();
+      List<CharacteristicDto> characteristicDtos = dao.selectEnabledCharacteristics(session);
+      for (CharacteristicDto characteristicDto : characteristicDtos) {
+        if (characteristicDto.getParentId() == null) {
+          debtModel.addRootCharacteristic(toDebtCharacteristic(characteristicDto));
+          for (CharacteristicDto sub : subCharacteristics(characteristicDto.getId(), characteristicDtos)) {
+            debtModel.addSubCharacteristic(toDebtCharacteristic(sub), characteristicDto.getKey());
+          }
+        }
+      }
+
+      List<RuleDebt> rules = newArrayList();
+      for (RuleDto rule : ruleDao.selectEnablesAndNonManual(session)) {
+        if ((languageKey == null || languageKey.equals(rule.getLanguage())) && rule.hasCharacteristic()) {
+          Integer characteristicId = rule.getCharacteristicId() != null ? rule.getCharacteristicId() : rule.getDefaultCharacteristicId();
+          rules.add(toRuleDebt(rule, debtModel.characteristicById(characteristicId).key()));
+        }
+      }
+      return debtModelXMLExporter.export(debtModel, rules);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  /**
+   * Restore from provided model
+   */
+  public void restore() {
+    restoreProvided(loadModelFromPlugin(DebtModelPluginRepository.DEFAULT_MODEL), null);
+  }
+
+  /**
+   * Restore from plugins providing rules for a given language
+   */
+  public void restore(String languageKey) {
+    restoreProvided(loadModelFromPlugin(DebtModelPluginRepository.DEFAULT_MODEL), languageKey);
+  }
+
+  private void restoreProvided(DebtModel modelToImport, @Nullable String languageKey) {
+    checkPermission();
+
+    Date updateDate = new Date(system2.now());
+    SqlSession session = mybatis.openSession();
+    try {
+      restoreCharacteristics(modelToImport, updateDate, session);
+      for (RuleDto rule : ruleDao.selectEnablesAndNonManual(session)) {
+        if (languageKey == null || languageKey.equals(rule.getLanguage())) {
+          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
+        }
+      }
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  /**
+   * Restore model from a given XML model
+   */
+  public ValidationMessages restoreFromXml(String xml) {
+    DebtModel debtModel = characteristicsXMLImporter.importXML(xml);
+    ValidationMessages validationMessages = ValidationMessages.create();
+    List<RuleDebt> ruleDebts = rulesXMLImporter.importXML(xml, validationMessages);
+    restore(debtModel, ruleDebts, null, validationMessages);
+    return validationMessages;
+  }
+
+  /**
+   * Restore model from a given XML model and a given language
+   */
+  public ValidationMessages restoreFromXml(String xml, String languageKey) {
+    DebtModel debtModel = characteristicsXMLImporter.importXML(xml);
+    ValidationMessages validationMessages = ValidationMessages.create();
+    List<RuleDebt> ruleDebts = rulesXMLImporter.importXML(xml, validationMessages);
+    restore(debtModel, ruleDebts, languageKey, validationMessages);
+    return validationMessages;
+  }
+
+  private void restore(DebtModel modelToImport, List<RuleDebt> ruleDebts, @Nullable String languageKey, ValidationMessages validationMessages) {
+    checkPermission();
+
+    Date updateDate = new Date(system2.now());
+    SqlSession session = mybatis.openSession();
+    try {
+      List<CharacteristicDto> characteristicDtos = restoreCharacteristics(modelToImport, updateDate, session);
+      restoreRules(characteristicDtos, languageKey, ruleDebts, validationMessages, updateDate, session);
+
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  private void restoreRules(List<CharacteristicDto> characteristicDtos, @Nullable String languageKey, List<RuleDebt> ruleDebts,
+                            ValidationMessages validationMessages, Date updateDate, SqlSession session) {
+    for (RuleDto rule : ruleDao.selectEnablesAndNonManual(session)) {
+      if (languageKey == null || languageKey.equals(rule.getLanguage())) {
+        RuleDebt ruleDebt = ruleDebtByRule(rule, ruleDebts);
+        if (ruleDebt == null) {
+          rule.setCharacteristicId(rule.getDefaultCharacteristicId() != null ? RuleDto.DISABLED_CHARACTERISTIC_ID : null);
+          rule.setRemediationFunction(null);
+          rule.setRemediationFactor(null);
+          rule.setRemediationOffset(null);
+        } else {
+          CharacteristicDto characteristicDto = characteristicByKey(ruleDebt.characteristicKey(), characteristicDtos, false);
+          // Characteristic cannot be null as it has been created just before
+
+          boolean isSameCharacteristic = characteristicDto.getId().equals(rule.getDefaultCharacteristicId());
+          boolean isSameFunction = isSameRemediationFunction(ruleDebt, rule);
+          rule.setCharacteristicId((!isSameCharacteristic ? characteristicDto.getId() : null));
+          rule.setRemediationFunction((!isSameFunction ? ruleDebt.function().name() : null));
+          rule.setRemediationFactor((!isSameFunction ? ruleDebt.factor() : null));
+          rule.setRemediationOffset((!isSameFunction ? ruleDebt.offset() : null));
+        }
+
+        ruleDebts.remove(ruleDebt);
+        rule.setUpdatedAt(updateDate);
+        ruleDao.update(rule, session);
+        // TODO index rules in E/S
+      }
+    }
+
+    for (RuleDebt ruleDebt : ruleDebts) {
+      validationMessages.addWarningText(String.format("The rule '%s' does not exist.", ruleDebt.ruleKey()));
+    }
+  }
+
+  @VisibleForTesting
+  List<CharacteristicDto> restoreCharacteristics(DebtModel targetModel, Date updateDate, SqlSession session) {
+    List<CharacteristicDto> sourceCharacteristics = dao.selectEnabledCharacteristics(session);
+
+    List<CharacteristicDto> result = newArrayList();
+
+    // Restore not existing characteristics
+    for (DebtCharacteristic characteristic : targetModel.rootCharacteristics()) {
+      CharacteristicDto rootCharacteristicDto = restoreCharacteristic(characteristic, null, sourceCharacteristics, updateDate, session);
+      result.add(rootCharacteristicDto);
+      for (DebtCharacteristic subCharacteristic : targetModel.subCharacteristics(characteristic.key())) {
+        result.add(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);
+      }
+    }
+    return result;
+  }
+
+  private CharacteristicDto restoreCharacteristic(DebtCharacteristic targetCharacteristic, @Nullable Integer parentId, List<CharacteristicDto> sourceCharacteristics,
+                                                  Date updateDate, SqlSession session) {
+    CharacteristicDto sourceCharacteristic = characteristicByKey(targetCharacteristic.key(), sourceCharacteristics, true);
+    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 static boolean isSameRemediationFunction(RuleDebt ruleDebt, RuleDto rule) {
+    return new EqualsBuilder()
+      .append(ruleDebt.function().name(), rule.getDefaultRemediationFunction())
+      .append(ruleDebt.factor(), rule.getDefaultRemediationFactor())
+      .append(ruleDebt.offset(), rule.getDefaultRemediationOffset())
+      .isEquals();
+  }
+
+  private DebtModel loadModelFromPlugin(String pluginKey) {
+    Reader xmlFileReader = null;
+    try {
+      xmlFileReader = debtModelPluginRepository.createReaderForXMLFile(pluginKey);
+      return characteristicsXMLImporter.importXML(xmlFileReader);
+    } finally {
+      IOUtils.closeQuietly(xmlFileReader);
+    }
+  }
+
+  @CheckForNull
+  private static RuleDebt ruleDebtByRule(final RuleDto rule, List<RuleDebt> ruleDebts) {
+    if (ruleDebts.isEmpty()) {
+      return null;
+    }
+    return Iterables.find(ruleDebts, new Predicate<RuleDebt>() {
+      @Override
+      public boolean apply(RuleDebt input) {
+        return rule.getRepositoryKey().equals(input.ruleKey().repository()) && rule.getRuleKey().equals(input.ruleKey().rule());
+      }
+    }, null);
+  }
+
+  private static CharacteristicDto characteristicByKey(final String key, List<CharacteristicDto> characteristicDtos, boolean canByNull) {
+    CharacteristicDto dto = Iterables.find(characteristicDtos, new Predicate<CharacteristicDto>() {
+      @Override
+      public boolean apply(CharacteristicDto input) {
+        return key.equals(input.getKey());
+      }
+    }, null);
+    if (dto == null && !canByNull) {
+      throw new IllegalStateException(String.format("Characteristic with key '%s' has not been found ", key));
+    }
+    return dto;
+  }
+
+  private static List<CharacteristicDto> subCharacteristics(final Integer parentId, List<CharacteristicDto> allCharacteristics) {
+    return newArrayList(Iterables.filter(allCharacteristics, new Predicate<CharacteristicDto>() {
+      @Override
+      public boolean apply(CharacteristicDto input) {
+        return parentId.equals(input.getParentId());
+      }
+    }));
+  }
+
+  private static RuleDebt toRuleDebt(RuleDto rule, String characteristicKey) {
+    RuleDebt ruleDebt = new RuleDebt().setRuleKey(RuleKey.of(rule.getRepositoryKey(), rule.getRuleKey()));
+    String function = rule.getRemediationFunction() != null ? rule.getRemediationFunction() : rule.getDefaultRemediationFunction();
+    String factor = rule.getRemediationFactor() != null ? rule.getRemediationFactor() : rule.getDefaultRemediationFactor();
+    String offset = rule.getRemediationOffset() != null ? rule.getRemediationOffset() : rule.getDefaultRemediationOffset();
+    ruleDebt.setCharacteristicKey(characteristicKey);
+    ruleDebt.setFunction(DebtRemediationFunction.Type.valueOf(function));
+    ruleDebt.setFactor(factor);
+    ruleDebt.setOffset(offset);
+    return ruleDebt;
+  }
+
+  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 static DebtCharacteristic toDebtCharacteristic(CharacteristicDto characteristic) {
+    return new DefaultDebtCharacteristic()
+      .setId(characteristic.getId())
+      .setKey(characteristic.getKey())
+      .setName(characteristic.getName())
+      .setOrder(characteristic.getOrder())
+      .setParentId(characteristic.getParentId())
+      .setCreatedAt(characteristic.getCreatedAt())
+      .setUpdatedAt(characteristic.getUpdatedAt());
+  }
+
+  private void checkPermission() {
+    UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+  }
+
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java b/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
new file mode 100644 (file)
index 0000000..a770b93
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.collect.Maps;
+import org.picocontainer.Startable;
+import org.sonar.api.Plugin;
+import org.sonar.api.ServerExtension;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * <p>This class is used to find which technical debt model XML files exist in the Sonar instance.</p>
+ * <p>
+ * Those XML files are provided by language plugins that embed their own contribution to the definition of the Technical debt model.
+ * They must be located in the classpath of those language plugins, more specifically in the "com.sonar.sqale" package, and
+ * they must be named "<pluginKey>-model.xml".
+ * </p>
+ */
+public class DebtModelPluginRepository implements ServerExtension, Startable {
+
+  public static final String DEFAULT_MODEL = "technical-debt";
+
+  private static final String XML_FILE_SUFFIX = "-model.xml";
+  private static final String XML_FILE_PREFIX = "com/sonar/sqale/";
+
+  private String xmlFilePrefix;
+
+  private PluginRepository pluginRepository;
+  private Map<String, ClassLoader> contributingPluginKeyToClassLoader;
+
+  public DebtModelPluginRepository(PluginRepository pluginRepository) {
+    this.pluginRepository = pluginRepository;
+    this.xmlFilePrefix = XML_FILE_PREFIX;
+  }
+
+  @VisibleForTesting
+  DebtModelPluginRepository(PluginRepository pluginRepository, String xmlFilePrefix) {
+    this.pluginRepository = pluginRepository;
+    this.xmlFilePrefix = xmlFilePrefix;
+  }
+
+  @VisibleForTesting
+  DebtModelPluginRepository(Map<String, ClassLoader> contributingPluginKeyToClassLoader, String xmlFilePrefix) {
+    this.contributingPluginKeyToClassLoader = contributingPluginKeyToClassLoader;
+    this.xmlFilePrefix = xmlFilePrefix;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void start() {
+    findAvailableXMLFiles();
+  }
+
+  protected void findAvailableXMLFiles() {
+    if (contributingPluginKeyToClassLoader == null) {
+      contributingPluginKeyToClassLoader = Maps.newTreeMap();
+      // Add default model
+      contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
+      for (PluginMetadata metadata : pluginRepository.getMetadata()) {
+        String pluginKey = metadata.getKey();
+        Plugin plugin = pluginRepository.getPlugin(pluginKey);
+        if (plugin != null) {
+          ClassLoader classLoader = plugin.getClass().getClassLoader();
+          if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
+            contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
+          }
+        }
+      }
+    }
+    contributingPluginKeyToClassLoader = Collections.unmodifiableMap(contributingPluginKeyToClassLoader);
+  }
+
+  @VisibleForTesting
+  String getXMLFilePath(String model) {
+    return xmlFilePrefix + model + XML_FILE_SUFFIX;
+  }
+
+  /**
+   * Returns the list of plugins that can contribute to the technical debt model.
+   *
+   * @return the list of plugin keys
+   */
+  public Collection<String> getContributingPluginList() {
+    return newArrayList(contributingPluginKeyToClassLoader.keySet());
+  }
+
+  /**
+   * Creates a new {@link java.io.Reader} for the XML file that contains the model contributed by the given plugin.
+   *
+   * @param pluginKey the key of the plugin that contributes the XML file
+   * @return the reader, that must be closed once its use is finished.
+   */
+  public Reader createReaderForXMLFile(String pluginKey) {
+    ClassLoader classLoader = contributingPluginKeyToClassLoader.get(pluginKey);
+    String xmlFilePath = getXMLFilePath(pluginKey);
+    return new InputStreamReader(classLoader.getResourceAsStream(xmlFilePath));
+  }
+
+  @VisibleForTesting
+  Map<String, ClassLoader> getContributingPluginKeyToClassLoader(){
+    return contributingPluginKeyToClassLoader;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+
+}
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
deleted file mode 100644 (file)
index 205850d..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.debt;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-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.commons.lang.builder.EqualsBuilder;
-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.api.utils.ValidationMessages;
-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.rule.RuleRepositories;
-import org.sonar.server.user.UserSession;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.io.Reader;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-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 RuleRepositories ruleRepositories;
-  private final DebtCharacteristicsXMLImporter characteristicsXMLImporter;
-  private final DebtRulesXMLImporter rulesXMLImporter;
-  private final System2 system2;
-
-  public DebtModelRestore(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, DebtModelOperations debtModelOperations, TechnicalDebtModelRepository debtModelPluginRepository,
-                          RuleRepositories ruleRepositories, DebtCharacteristicsXMLImporter characteristicsXMLImporter, DebtRulesXMLImporter rulesXMLImporter) {
-    this(mybatis, dao, ruleDao, debtModelOperations, debtModelPluginRepository, ruleRepositories, characteristicsXMLImporter, rulesXMLImporter, System2.INSTANCE);
-  }
-
-  @VisibleForTesting
-  DebtModelRestore(MyBatis mybatis, CharacteristicDao dao, RuleDao ruleDao, DebtModelOperations debtModelOperations, TechnicalDebtModelRepository debtModelPluginRepository,
-                   RuleRepositories ruleRepositories, DebtCharacteristicsXMLImporter characteristicsXMLImporter, DebtRulesXMLImporter rulesXMLImporter,
-                   System2 system2) {
-    this.mybatis = mybatis;
-    this.dao = dao;
-    this.ruleDao = ruleDao;
-    this.debtModelOperations = debtModelOperations;
-    this.debtModelPluginRepository = debtModelPluginRepository;
-    this.ruleRepositories = ruleRepositories;
-    this.characteristicsXMLImporter = characteristicsXMLImporter;
-    this.rulesXMLImporter = rulesXMLImporter;
-    this.system2 = system2;
-  }
-
-  /**
-   * Restore from provided model
-   */
-  public ValidationMessages restore() {
-    ValidationMessages validationMessages = ValidationMessages.create();
-    restore(loadModelFromPlugin(TechnicalDebtModelRepository.DEFAULT_MODEL), Collections.<DebtRulesXMLImporter.RuleDebt>emptyList(),
-      Collections.<RuleRepositories.Repository>emptyList(), false, validationMessages);
-    return validationMessages;
-  }
-
-  /**
-   * Restore from plugins providing rules for a given language
-   */
-  public ValidationMessages restore(String languageKey) {
-    ValidationMessages validationMessages = ValidationMessages.create();
-    restore(loadModelFromPlugin(TechnicalDebtModelRepository.DEFAULT_MODEL), Collections.<DebtRulesXMLImporter.RuleDebt>emptyList(),
-      ruleRepositories.repositoriesForLang(languageKey), false, validationMessages);
-    return validationMessages;
-  }
-
-  /**
-   * Restore model from a given XML model
-   */
-  public ValidationMessages restoreFromXml(String xml) {
-    DebtModel debtModel = characteristicsXMLImporter.importXML(xml);
-    ValidationMessages validationMessages = ValidationMessages.create();
-    List<DebtRulesXMLImporter.RuleDebt> ruleDebts = rulesXMLImporter.importXML(xml, validationMessages);
-    restore(debtModel, ruleDebts, Collections.<RuleRepositories.Repository>emptyList(), true, validationMessages);
-    return validationMessages;
-  }
-
-  /**
-   * Restore model from a given XML model and a given language
-   */
-  public ValidationMessages restoreFromXml(String xml, String languageKey) {
-    DebtModel debtModel = characteristicsXMLImporter.importXML(xml);
-    ValidationMessages validationMessages = ValidationMessages.create();
-    List<DebtRulesXMLImporter.RuleDebt> ruleDebts = rulesXMLImporter.importXML(xml, validationMessages);
-    restore(debtModel, ruleDebts, ruleRepositories.repositoriesForLang(languageKey), true, validationMessages);
-    return validationMessages;
-  }
-
-  private void restore(DebtModel modelToImport, List<DebtRulesXMLImporter.RuleDebt> ruleDebts, Collection<RuleRepositories.Repository> repositories,
-                       boolean disableCharacteristicWhenRuleNotFound, ValidationMessages validationMessages) {
-    checkPermission();
-
-    Date updateDate = new Date(system2.now());
-    SqlSession session = mybatis.openSession();
-    try {
-      List<CharacteristicDto> persisted = dao.selectEnabledCharacteristics();
-      List<CharacteristicDto> characteristicDtos = restoreCharacteristics(modelToImport, persisted, updateDate, session);
-      restoreRules(characteristicDtos, repositories, ruleDebts, disableCharacteristicWhenRuleNotFound, validationMessages, updateDate, session);
-
-      session.commit();
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
-  }
-
-  private void restoreRules(List<CharacteristicDto> characteristicDtos, Collection<RuleRepositories.Repository> repositories, List<DebtRulesXMLImporter.RuleDebt> ruleDebts,
-                            boolean disableCharacteristicWhenRuleNotFound, ValidationMessages validationMessages, Date updateDate, SqlSession session) {
-    List<String> repositoryKeys = newArrayList(Iterables.transform(repositories, new Function<RuleRepositories.Repository, String>() {
-      @Override
-      public String apply(RuleRepositories.Repository input) {
-        return input.getKey();
-      }
-    }));
-    for (RuleDto rule : ruleDao.selectEnablesAndNonManual(session)) {
-      if (repositories.isEmpty() || repositoryKeys.contains(rule.getRepositoryKey())) {
-        DebtRulesXMLImporter.RuleDebt ruleDebt = ruleDebtByRule(rule, ruleDebts);
-        if (ruleDebt == null) {
-          rule.setCharacteristicId(disableCharacteristicWhenRuleNotFound ? RuleDto.DISABLED_CHARACTERISTIC_ID : null);
-          rule.setRemediationFunction(null);
-          rule.setRemediationFactor(null);
-          rule.setRemediationOffset(null);
-        } else {
-          CharacteristicDto characteristicDto = characteristicByKey(ruleDebt.characteristicKey(), characteristicDtos, false);
-          // Characteristic cannot be null as it has been created just before
-
-          boolean isSameCharacteristic = characteristicDto.getId().equals(rule.getDefaultCharacteristicId());
-          boolean isSameFunction = isSameRemediationFunction(ruleDebt, rule);
-          rule.setCharacteristicId((!isSameCharacteristic ? characteristicDto.getId() : null));
-          rule.setRemediationFunction((!isSameFunction ? ruleDebt.function().name() : null));
-          rule.setRemediationFactor((!isSameFunction ? ruleDebt.factor() : null));
-          rule.setRemediationOffset((!isSameFunction ? ruleDebt.offset() : null));
-        }
-
-        ruleDebts.remove(ruleDebt);
-        rule.setUpdatedAt(updateDate);
-        ruleDao.update(rule, session);
-        // TODO index rules in E/S
-      }
-    }
-
-    for (DebtRulesXMLImporter.RuleDebt ruleDebt : ruleDebts) {
-      validationMessages.addWarningText(String.format("The rule '%s' does not exist.", ruleDebt.ruleKey()));
-    }
-  }
-
-  static boolean isSameRemediationFunction(DebtRulesXMLImporter.RuleDebt ruleDebt, RuleDto rule) {
-    return new EqualsBuilder()
-      .append(ruleDebt.function().name(), rule.getDefaultRemediationFunction())
-      .append(ruleDebt.factor(), rule.getDefaultRemediationFactor())
-      .append(ruleDebt.offset(), rule.getDefaultRemediationOffset())
-      .isEquals();
-  }
-
-  @VisibleForTesting
-  List<CharacteristicDto> restoreCharacteristics(DebtModel targetModel, List<CharacteristicDto> sourceCharacteristics, Date updateDate, SqlSession session) {
-    List<CharacteristicDto> result = newArrayList();
-
-    // Restore not existing characteristics
-    for (DebtCharacteristic characteristic : targetModel.rootCharacteristics()) {
-      CharacteristicDto rootCharacteristicDto = restoreCharacteristic(characteristic, null, sourceCharacteristics, updateDate, session);
-      result.add(rootCharacteristicDto);
-      for (DebtCharacteristic subCharacteristic : targetModel.subCharacteristics(characteristic.key())) {
-        result.add(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);
-      }
-    }
-    return result;
-  }
-
-  private CharacteristicDto restoreCharacteristic(DebtCharacteristic targetCharacteristic, @Nullable Integer parentId, List<CharacteristicDto> sourceCharacteristics,
-                                                  Date updateDate, SqlSession session) {
-    CharacteristicDto sourceCharacteristic = characteristicByKey(targetCharacteristic.key(), sourceCharacteristics, true);
-    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 loadModelFromPlugin(String pluginKey) {
-    Reader xmlFileReader = null;
-    try {
-      xmlFileReader = debtModelPluginRepository.createReaderForXMLFile(pluginKey);
-      return characteristicsXMLImporter.importXML(xmlFileReader);
-    } finally {
-      IOUtils.closeQuietly(xmlFileReader);
-    }
-  }
-
-  private CharacteristicDto characteristicByKey(final String key, List<CharacteristicDto> existingModel, boolean canByNull) {
-    CharacteristicDto dto = Iterables.find(existingModel, new Predicate<CharacteristicDto>() {
-      @Override
-      public boolean apply(CharacteristicDto input) {
-        return key.equals(input.getKey());
-      }
-    }, null);
-    if (dto == null && !canByNull) {
-      throw new IllegalStateException(String.format("Characteristic with key '%s' has not been found ", key));
-    }
-    return dto;
-  }
-
-  @CheckForNull
-  private DebtRulesXMLImporter.RuleDebt ruleDebtByRule(final RuleDto rule, List<DebtRulesXMLImporter.RuleDebt> ruleDebts) {
-    if (ruleDebts.isEmpty()) {
-      return null;
-    }
-    return Iterables.find(ruleDebts, new Predicate<DebtRulesXMLImporter.RuleDebt>() {
-      @Override
-      public boolean apply(DebtRulesXMLImporter.RuleDebt input) {
-        return rule.getRepositoryKey().equals(input.ruleKey().repository()) && rule.getRuleKey().equals(input.ruleKey().rule());
-      }
-    }, 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 a0777d8f528c7d8fb7619e27270f69a78bd97542..40b1e27f48ff262ff27fdf9a979d87c286d24751 100644 (file)
@@ -37,12 +37,12 @@ public class DebtModelService implements DebtModel {
 
   private final DebtModelOperations debtModelOperations;
   private final DebtModelLookup debtModelLookup;
-  private final DebtModelRestore debtModelRestore;
+  private final DebtModelBackup debtModelBackup;
 
-  public DebtModelService(DebtModelOperations debtModelOperations, DebtModelLookup debtModelLookup, DebtModelRestore debtModelRestore) {
+  public DebtModelService(DebtModelOperations debtModelOperations, DebtModelLookup debtModelLookup, DebtModelBackup debtModelBackup) {
     this.debtModelOperations = debtModelOperations;
     this.debtModelLookup = debtModelLookup;
-    this.debtModelRestore = debtModelRestore;
+    this.debtModelBackup = debtModelBackup;
   }
 
   public List<DebtCharacteristic> characteristics() {
@@ -85,29 +85,37 @@ public class DebtModelService implements DebtModel {
   /**
    * Restore from provided model
    */
-  public ValidationMessages restore(){
-    return debtModelRestore.restore();
+  public void restore() {
+    debtModelBackup.restore();
   }
 
   /**
    * Restore from plugins providing rules for a given language
    */
-  public ValidationMessages restoreFromLanguage(String languageKey) {
-    return debtModelRestore.restore(languageKey);
+  public void restoreFromLanguage(String languageKey) {
+    debtModelBackup.restore(languageKey);
   }
 
   /**
    * Restore from XML
    */
-  public ValidationMessages restoreFromXml(String xml){
-    return debtModelRestore.restoreFromXml(xml);
+  public ValidationMessages restoreFromXml(String xml) {
+    return debtModelBackup.restoreFromXml(xml);
   }
 
   /**
    * Restore from XML and a given language
    */
   public ValidationMessages restoreFromXmlAndLanguage(String xml, String languageKey) {
-    return debtModelRestore.restoreFromXml(xml, languageKey);
+    return debtModelBackup.restoreFromXml(xml, languageKey);
+  }
+
+  public String backup() {
+    return debtModelBackup.backup();
+  }
+
+  public String backupFromLanguage(String languageKey) {
+    return debtModelBackup.backup(languageKey);
   }
 
 }
diff --git a/sonar-server/src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java b/sonar-server/src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java
new file mode 100644 (file)
index 0000000..feefe21
--- /dev/null
@@ -0,0 +1,321 @@
+/*
+ * 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 com.google.common.collect.Ordering;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.debt.DebtCharacteristic;
+import org.sonar.api.server.rule.DebtRemediationFunction;
+import org.xml.sax.InputSource;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.xml.transform.*;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * Export characteristics and rule debt definitions to XML
+ */
+public class DebtModelXMLExporter implements ServerComponent {
+
+  private static final String ROOT = "sqale";
+  private static final String DEFAULT_INDENT = "2";
+
+  public static final String CHARACTERISTIC = "chc";
+  public static final String CHARACTERISTIC_KEY = "key";
+  public static final String CHARACTERISTIC_NAME = "name";
+  public static final String PROPERTY = "prop";
+  public static final String PROPERTY_KEY = "key";
+  public static final String PROPERTY_VALUE = "val";
+  public static final String PROPERTY_TEXT_VALUE = "txt";
+
+  public static final String REPOSITORY_KEY = "rule-repo";
+  public static final String RULE_KEY = "rule-key";
+
+  public static final String PROPERTY_FUNCTION = "remediationFunction";
+  public static final String PROPERTY_FACTOR = "remediationFactor";
+  public static final String PROPERTY_OFFSET = "offset";
+
+  protected String export(DebtModel debtModel, List<RuleDebt> allRules) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("<" + ROOT + ">");
+    for (DebtCharacteristic characteristic : debtModel.rootCharacteristics()) {
+      appendCharacteristic(debtModel, characteristic, allRules, sb);
+    }
+    sb.append("</" + ROOT + ">");
+    String xml = sb.toString();
+    xml = prettyFormatXml(xml);
+    return xml;
+  }
+
+  private void appendCharacteristic(DebtModel debtModel, DebtCharacteristic characteristic, List<RuleDebt> allRules, StringBuilder xml) {
+    xml.append("<" + CHARACTERISTIC + ">");
+    if (StringUtils.isNotBlank(characteristic.key())) {
+      xml.append("<" + CHARACTERISTIC_KEY + ">");
+      xml.append(StringEscapeUtils.escapeXml(characteristic.key()));
+      xml.append("</" + CHARACTERISTIC_KEY + "><" + CHARACTERISTIC_NAME + ">");
+      xml.append(StringEscapeUtils.escapeXml(characteristic.name()));
+      xml.append("</" + CHARACTERISTIC_NAME + ">");
+    }
+
+    if (characteristic.parentId() != null) {
+      List<RuleDebt> rules = rules(allRules, characteristic.key());
+      for (RuleDebt ruleDto : rules) {
+        appendRule(ruleDto, xml);
+      }
+    } else {
+      for (DebtCharacteristic child : debtModel.subCharacteristics(characteristic.key())) {
+        appendCharacteristic(debtModel, child, allRules, xml);
+      }
+    }
+    xml.append("</" + CHARACTERISTIC + ">");
+  }
+
+  private void appendRule(RuleDebt rule, StringBuilder xml) {
+    xml.append("<" + CHARACTERISTIC + ">");
+    xml.append("<" + REPOSITORY_KEY + ">");
+    xml.append(StringEscapeUtils.escapeXml(rule.ruleKey().repository()));
+    xml.append("</" + REPOSITORY_KEY + "><" + RULE_KEY + ">");
+    xml.append(StringEscapeUtils.escapeXml(rule.ruleKey().rule()));
+    xml.append("</" + RULE_KEY + ">");
+
+    String factor = rule.factor();
+    String offset = rule.offset();
+
+    appendProperty(PROPERTY_FUNCTION, null, rule.function().name(), xml);
+    if (factor != null) {
+      String[] values = getValues(factor);
+      appendProperty(PROPERTY_FACTOR, values[0], values[1], xml);
+    }
+    if (offset != null) {
+      String[] values = getValues(offset);
+      appendProperty(PROPERTY_OFFSET, values[0], values[1], xml);
+    }
+    xml.append("</" + CHARACTERISTIC + ">");
+  }
+
+  private static String[] getValues(String factorOrOffset) {
+    String[] result = new String[2];
+    Pattern pattern = Pattern.compile("(\\d+)(\\w+)");
+    Matcher matcher = pattern.matcher(factorOrOffset);
+    if (matcher.find()) {
+      String value = matcher.group(1);
+      String unit = matcher.group(2);
+      result[0] = value;
+      result[1] = unit;
+    }
+    return result;
+  }
+
+  private void appendProperty(String key, @Nullable String val, String text, StringBuilder xml) {
+    xml.append("<" + PROPERTY + "><" + PROPERTY_KEY + ">");
+    xml.append(StringEscapeUtils.escapeXml(key));
+    xml.append("</" + PROPERTY_KEY + ">");
+    if (val != null) {
+      xml.append("<" + PROPERTY_VALUE + ">");
+      xml.append(val);
+      xml.append("</" + PROPERTY_VALUE + ">");
+    }
+    if (StringUtils.isNotEmpty(text)) {
+      xml.append("<" + PROPERTY_TEXT_VALUE + ">");
+      xml.append(StringEscapeUtils.escapeXml(text));
+      xml.append("</" + PROPERTY_TEXT_VALUE + ">");
+    }
+    xml.append("</" + PROPERTY + ">");
+  }
+
+  private String prettyFormatXml(String xml) {
+    try {
+      Transformer serializer = SAXTransformerFactory.newInstance().newTransformer();
+      serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+      serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+      serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", DEFAULT_INDENT);
+      Source xmlSource = new SAXSource(new InputSource(new ByteArrayInputStream(xml.getBytes())));
+      StreamResult res = new StreamResult(new ByteArrayOutputStream());
+      serializer.transform(xmlSource, res);
+      return new String(((ByteArrayOutputStream) res.getOutputStream()).toByteArray());
+    } catch (TransformerConfigurationException ignored) {
+      // Ignore, raw XML will be returned
+    } catch (TransformerException ignored) {
+      // Ignore, raw XML will be returned
+    }
+    return xml;
+  }
+
+  private List<RuleDebt> rules(List<RuleDebt> rules, final String parentKey) {
+    return newArrayList(Iterables.filter(rules, new Predicate<RuleDebt>() {
+      @Override
+      public boolean apply(RuleDebt input) {
+        return parentKey.equals(input.characteristicKey());
+      }
+    }));
+  }
+
+  public static 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;
+    }
+
+    /**
+     * @return root characteristics sorted by order
+     */
+    public List<DebtCharacteristic> rootCharacteristics() {
+      return sortByOrder(newArrayList(characteristicsByRootKey.get(null)));
+    }
+
+    /**
+     * @return root characteristics sorted by name
+     */
+    public List<DebtCharacteristic> subCharacteristics(String characteristicKey) {
+      return sortByName(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);
+    }
+
+    @CheckForNull
+    public DebtCharacteristic characteristicById(final Integer id) {
+      return Iterables.find(characteristicsByRootKey.values(), new Predicate<DebtCharacteristic>() {
+        @Override
+        public boolean apply(DebtCharacteristic input) {
+          return id.equals(input.id());
+        }
+      }, null);
+    }
+
+    private List<DebtCharacteristic> sortByOrder(List<DebtCharacteristic> characteristics) {
+      Collections.sort(characteristics, new Ordering<DebtCharacteristic>() {
+        public int compare(@Nullable DebtCharacteristic left, @Nullable DebtCharacteristic right) {
+          if (left == null || left.order() == null || right == null || right.order() == null) {
+            return -1;
+          }
+          return left.order() - right.order();
+        }
+      });
+      return characteristics;
+    }
+
+    private List<DebtCharacteristic> sortByName(List<DebtCharacteristic> characteristics) {
+      Collections.sort(characteristics, new Ordering<DebtCharacteristic>() {
+        public int compare(@Nullable DebtCharacteristic left, @Nullable DebtCharacteristic right) {
+          if (left == null || right == null) {
+            return -1;
+          }
+          return StringUtils.defaultString(left.name()).compareTo(StringUtils.defaultString(right.name()));
+        }
+      });
+      return characteristics;
+    }
+  }
+
+  public static class RuleDebt {
+    private RuleKey ruleKey;
+    private String characteristicKey;
+    private DebtRemediationFunction.Type type;
+    private String factor;
+    private String offset;
+
+    public RuleKey ruleKey() {
+      return ruleKey;
+    }
+
+    public RuleDebt setRuleKey(RuleKey ruleKey) {
+      this.ruleKey = ruleKey;
+      return this;
+    }
+
+    public String characteristicKey() {
+      return characteristicKey;
+    }
+
+    public RuleDebt setCharacteristicKey(String characteristicKey) {
+      this.characteristicKey = characteristicKey;
+      return this;
+    }
+
+    public DebtRemediationFunction.Type function() {
+      return type;
+    }
+
+    public RuleDebt setFunction(DebtRemediationFunction.Type type) {
+      this.type = type;
+      return this;
+    }
+
+    @CheckForNull
+    public String factor() {
+      return factor;
+    }
+
+    public RuleDebt setFactor(@Nullable String factor) {
+      this.factor = factor;
+      return this;
+    }
+
+    @CheckForNull
+    public String offset() {
+      return offset;
+    }
+
+    public RuleDebt setOffset(@Nullable String offset) {
+      this.offset = offset;
+      return this;
+    }
+  }
+
+}
index 762b11deacba385c00d43d4bd5750f654ad1ca51..32cc6ae4b8fd22de3c0f7ba1dab043007dd69605 100644 (file)
@@ -29,7 +29,7 @@ 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.ServerComponent;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.server.rule.DebtRemediationFunction;
 import org.sonar.api.utils.Duration;
@@ -45,23 +45,12 @@ import java.io.StringReader;
 import java.util.List;
 
 import static com.google.common.collect.Lists.newArrayList;
+import static org.sonar.server.debt.DebtModelXMLExporter.*;
 
-public class DebtRulesXMLImporter implements ServerExtension {
-
-  public static final String CHARACTERISTIC = "chc";
-  public static final String CHARACTERISTIC_KEY = "key";
-  public static final String PROPERTY = "prop";
-
-  public static final String PROPERTY_KEY = "key";
-  public static final String PROPERTY_VALUE = "val";
-  public static final String PROPERTY_TEXT_VALUE = "txt";
-
-  public static final String REPOSITORY_KEY = "rule-repo";
-  public static final String RULE_KEY = "rule-key";
-
-  public static final String PROPERTY_FUNCTION = "remediationFunction";
-  public static final String PROPERTY_FACTOR = "remediationFactor";
-  public static final String PROPERTY_OFFSET = "offset";
+/**
+ * Import rules debt definitions from an XML
+ */
+public class DebtRulesXMLImporter implements ServerComponent {
 
   public List<RuleDebt> importXML(String xml, ValidationMessages validationMessages) {
     return importXML(new StringReader(xml), validationMessages);
@@ -107,12 +96,12 @@ public class DebtRulesXMLImporter implements ServerExtension {
         process(ruleDebts, parentKey, currentCharacteristicKey, validationMessages, cursor);
       } else if (StringUtils.equals(node, REPOSITORY_KEY)) {
         RuleDebt ruleDebt = processRule(validationMessages, cursor);
-        if (ruleDebt != null) {
+        if (ruleDebt != null && parentKey != null) {
           if (rootKey != null) {
-            ruleDebt.characteristicKey = parentKey;
+            ruleDebt.setCharacteristicKey(parentKey);
             ruleDebts.add(ruleDebt);
           } else {
-            validationMessages.addWarningText("Rule '" + ruleDebt.ruleKey + "' is ignored because it's defined directly under a root characteristic.");
+            validationMessages.addWarningText("Rule '" + ruleDebt.ruleKey() + "' is ignored because it's defined directly under a root characteristic.");
           }
         }
       }
@@ -263,59 +252,4 @@ public class DebtRulesXMLImporter implements ServerExtension {
     }
   }
 
-  public static class RuleDebt {
-    private RuleKey ruleKey;
-    private String characteristicKey;
-    private DebtRemediationFunction.Type type;
-    private String factor;
-    private String offset;
-
-    public RuleKey ruleKey() {
-      return ruleKey;
-    }
-
-    public RuleDebt setRuleKey(RuleKey ruleKey) {
-      this.ruleKey = ruleKey;
-      return this;
-    }
-
-    public String characteristicKey() {
-      return characteristicKey;
-    }
-
-    public RuleDebt setCharacteristicKey(String characteristicKey) {
-      this.characteristicKey = characteristicKey;
-      return this;
-    }
-
-    public DebtRemediationFunction.Type function() {
-      return type;
-    }
-
-    public RuleDebt setFunction(DebtRemediationFunction.Type type) {
-      this.type = type;
-      return this;
-    }
-
-    @CheckForNull
-    public String factor() {
-      return factor;
-    }
-
-    public RuleDebt setFactor(@Nullable String factor) {
-      this.factor = factor;
-      return this;
-    }
-
-    @CheckForNull
-    public String offset() {
-      return offset;
-    }
-
-    public RuleDebt setOffset(@Nullable String offset) {
-      this.offset = offset;
-      return this;
-    }
-  }
-
 }
index 31b4a0234d8981b02e9c4d442d772548328afe3c..9cd0c8c7aa77a1d6341b21b6b18559b194d12f6f 100644 (file)
@@ -63,9 +63,6 @@ import org.sonar.core.qualitygate.db.QualityGateConditionDao;
 import org.sonar.core.qualitygate.db.QualityGateDao;
 import org.sonar.core.resource.DefaultResourcePermissions;
 import org.sonar.core.rule.DefaultRuleFinder;
-import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
-import org.sonar.core.technicaldebt.TechnicalDebtModelSynchronizer;
-import org.sonar.core.technicaldebt.TechnicalDebtXMLImporter;
 import org.sonar.core.test.TestPlanPerspectiveLoader;
 import org.sonar.core.test.TestablePerspectiveLoader;
 import org.sonar.core.timemachine.Periods;
@@ -266,7 +263,7 @@ class ServerComponents {
     pico.addSingleton(AddTagsWsHandler.class);
     pico.addSingleton(RemoveTagsWsHandler.class);
     pico.addSingleton(RulesDefinitionXmlLoader.class);
-    
+
     // rule tags
     pico.addSingleton(ESRuleTags.class);
     pico.addSingleton(RuleTagLookup.class);
@@ -352,10 +349,9 @@ class ServerComponents {
     pico.addSingleton(DebtModelService.class);
     pico.addSingleton(DebtModelOperations.class);
     pico.addSingleton(DebtModelLookup.class);
-    pico.addSingleton(DebtModelRestore.class);
-    pico.addSingleton(TechnicalDebtModelSynchronizer.class);
-    pico.addSingleton(TechnicalDebtModelRepository.class);
-    pico.addSingleton(TechnicalDebtXMLImporter.class);
+    pico.addSingleton(DebtModelBackup.class);
+    pico.addSingleton(DebtModelPluginRepository.class);
+    pico.addSingleton(DebtModelXMLExporter.class);
     pico.addSingleton(DebtRulesXMLImporter.class);
     pico.addSingleton(DebtCharacteristicsXMLImporter.class);
 
index 59b8b8f0a581ace6490e7ab88ff2fc3facdc835b..d9bd3ea9e3415c19f50bcab15a088220ab467b4c 100644 (file)
@@ -34,7 +34,8 @@ import org.sonar.api.server.rule.RulesDefinition;
 import org.sonar.api.utils.ValidationMessages;
 import org.sonar.check.Cardinality;
 import org.sonar.core.i18n.RuleI18nManager;
-import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
+import org.sonar.server.debt.DebtModelPluginRepository;
+import org.sonar.server.debt.DebtModelXMLExporter;
 import org.sonar.server.debt.DebtRulesXMLImporter;
 
 import javax.annotation.CheckForNull;
@@ -44,6 +45,7 @@ import java.util.Collection;
 import java.util.List;
 
 import static com.google.common.collect.Lists.newArrayList;
+import static org.sonar.server.debt.DebtModelXMLExporter.RuleDebt;
 
 /**
  * Inject deprecated RuleRepository into RuleDefinitions for backward-compatibility.
@@ -57,24 +59,24 @@ public class DeprecatedRulesDefinition implements RulesDefinition {
   private final RuleI18nManager i18n;
   private final RuleRepository[] repositories;
 
-  private final TechnicalDebtModelRepository languageModelFinder;
+  private final DebtModelPluginRepository languageModelFinder;
   private final DebtRulesXMLImporter importer;
 
-  public DeprecatedRulesDefinition(RuleI18nManager i18n, RuleRepository[] repositories, TechnicalDebtModelRepository languageModelFinder, DebtRulesXMLImporter importer) {
+  public DeprecatedRulesDefinition(RuleI18nManager i18n, RuleRepository[] repositories, DebtModelPluginRepository languageModelFinder, DebtRulesXMLImporter importer) {
     this.i18n = i18n;
     this.repositories = repositories;
     this.languageModelFinder = languageModelFinder;
     this.importer = importer;
   }
 
-  public DeprecatedRulesDefinition(RuleI18nManager i18n, TechnicalDebtModelRepository languageModelFinder, DebtRulesXMLImporter importer) {
+  public DeprecatedRulesDefinition(RuleI18nManager i18n, DebtModelPluginRepository languageModelFinder, DebtRulesXMLImporter importer) {
     this(i18n, new RuleRepository[0], languageModelFinder, importer);
   }
 
   @Override
   public void define(Context context) {
     // Load rule debt definitions from xml files provided by plugin
-    List<DebtRulesXMLImporter.RuleDebt> ruleDebts = loadRuleDebtList();
+    List<RuleDebt> ruleDebts = loadRuleDebtList();
 
     for (RuleRepository repository : repositories) {
       // RuleRepository API does not handle difference between new and extended repositories,
@@ -106,8 +108,8 @@ public class DeprecatedRulesDefinition implements RulesDefinition {
     }
   }
 
-  private void updateRuleDebtDefinitions(NewRule newRule, String repoKey, String ruleKey, List<DebtRulesXMLImporter.RuleDebt> ruleDebts){
-    DebtRulesXMLImporter.RuleDebt ruleDebt = findRequirement(ruleDebts, repoKey, ruleKey);
+  private void updateRuleDebtDefinitions(NewRule newRule, String repoKey, String ruleKey, List<RuleDebt> ruleDebts){
+    RuleDebt ruleDebt = findRequirement(ruleDebts, repoKey, ruleKey);
     if (ruleDebt != null) {
       newRule.setDebtCharacteristic(ruleDebt.characteristicKey());
       switch (ruleDebt.function()) {
@@ -153,20 +155,20 @@ public class DeprecatedRulesDefinition implements RulesDefinition {
     return StringUtils.defaultIfBlank(desc, null);
   }
 
-  public List<DebtRulesXMLImporter.RuleDebt> loadRuleDebtList() {
-    List<DebtRulesXMLImporter.RuleDebt> ruleDebtList = newArrayList();
+  public List<DebtModelXMLExporter.RuleDebt> loadRuleDebtList() {
+    List<RuleDebt> ruleDebtList = newArrayList();
     for (String pluginKey : getContributingPluginListWithoutSqale()) {
       ruleDebtList.addAll(loadRuleDebtsFromXml(pluginKey));
     }
     return ruleDebtList;
   }
 
-  public List<DebtRulesXMLImporter.RuleDebt> loadRuleDebtsFromXml(String pluginKey) {
+  public List<RuleDebt> loadRuleDebtsFromXml(String pluginKey) {
     Reader xmlFileReader = null;
     try {
       xmlFileReader = languageModelFinder.createReaderForXMLFile(pluginKey);
       ValidationMessages validationMessages = ValidationMessages.create();
-      List<DebtRulesXMLImporter.RuleDebt> rules = importer.importXML(xmlFileReader, validationMessages);
+      List<RuleDebt> rules = importer.importXML(xmlFileReader, validationMessages);
       validationMessages.log(LOG);
       return rules;
     } finally {
@@ -176,15 +178,15 @@ public class DeprecatedRulesDefinition implements RulesDefinition {
 
   private Collection<String> getContributingPluginListWithoutSqale() {
     Collection<String> pluginList = newArrayList(languageModelFinder.getContributingPluginList());
-    pluginList.remove(TechnicalDebtModelRepository.DEFAULT_MODEL);
+    pluginList.remove(DebtModelPluginRepository.DEFAULT_MODEL);
     return pluginList;
   }
 
   @CheckForNull
-  private DebtRulesXMLImporter.RuleDebt findRequirement(List<DebtRulesXMLImporter.RuleDebt> requirements, final String repoKey, final String ruleKey) {
-    return Iterables.find(requirements, new Predicate<DebtRulesXMLImporter.RuleDebt>() {
+  private RuleDebt findRequirement(List<RuleDebt> requirements, final String repoKey, final String ruleKey) {
+    return Iterables.find(requirements, new Predicate<RuleDebt>() {
       @Override
-      public boolean apply(DebtRulesXMLImporter.RuleDebt input) {
+      public boolean apply(RuleDebt input) {
         return input.ruleKey().equals(RuleKey.of(repoKey, ruleKey));
       }
     }, null);
index 26a5c94444a1aa03ca1e71e1a6326fe94a8e8cde..f482fe1117c5daf25f481862cd9a0514c18e0332 100644 (file)
@@ -24,24 +24,24 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.utils.TimeProfiler;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.server.debt.DebtModelRestore;
+import org.sonar.server.debt.DebtModelBackup;
 
 public class RegisterDebtModel {
 
   private static final Logger LOGGER = LoggerFactory.getLogger(RegisterDebtModel.class);
 
   private final CharacteristicDao dao;
-  private final DebtModelRestore debtModelRestore;
+  private final DebtModelBackup debtModelBackup;
 
-  public RegisterDebtModel(CharacteristicDao dao, DebtModelRestore debtModelRestore) {
+  public RegisterDebtModel(CharacteristicDao dao, DebtModelBackup debtModelBackup) {
     this.dao = dao;
-    this.debtModelRestore = debtModelRestore;
+    this.debtModelBackup = debtModelBackup;
   }
 
   public void start() {
     TimeProfiler profiler = new TimeProfiler(LOGGER).start("Register technical debt model");
     if (dao.selectEnabledCharacteristics().isEmpty()) {
-      debtModelRestore.restore();
+      debtModelBackup.restore();
     }
     profiler.stop();
   }
index 3f6224862dfb10e7031c17b800e5a378b16c2420..a6b0bd3b8bb7f16ad87ec9753d0bdf4ab8c914ce 100644 (file)
@@ -30,6 +30,7 @@ import java.util.List;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
+import static org.sonar.server.debt.DebtModelXMLExporter.DebtModel;
 
 public class DebtCharacteristicsXMLImporterTest {
 
@@ -97,7 +98,7 @@ public class DebtCharacteristicsXMLImporterTest {
 
   private String getFileContent(String file) {
     try {
-      return Resources.toString(Resources.getResource(DebtCharacteristicsXMLImporterTest.class, "DebtCharacteristicsXMLImporterTest/" + file), Charsets.UTF_8);
+      return Resources.toString(Resources.getResource(this.getClass(), this.getClass().getSimpleName() + "/" + file), Charsets.UTF_8);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
diff --git a/sonar-server/src/test/java/org/sonar/server/debt/DebtModelBackupTest.java b/sonar-server/src/test/java/org/sonar/server/debt/DebtModelBackupTest.java
new file mode 100644 (file)
index 0000000..2127ac8
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ * 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.Captor;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
+import org.sonar.api.server.rule.DebtRemediationFunction;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.ValidationMessages;
+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.user.MockUserSession;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+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;
+import static org.mockito.Mockito.*;
+import static org.sonar.server.debt.DebtModelXMLExporter.DebtModel;
+import static org.sonar.server.debt.DebtModelXMLExporter.RuleDebt;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DebtModelBackupTest {
+
+  @Mock
+  MyBatis myBatis;
+
+  @Mock
+  SqlSession session;
+
+  @Mock
+  DebtModelPluginRepository debtModelPluginRepository;
+
+  @Mock
+  CharacteristicDao dao;
+
+  @Mock
+  RuleDao ruleDao;
+
+  @Mock
+  DebtModelOperations debtModelOperations;
+
+  @Mock
+  DebtCharacteristicsXMLImporter characteristicsXMLImporter;
+
+  @Mock
+  DebtRulesXMLImporter rulesXMLImporter;
+
+  @Mock
+  DebtModelXMLExporter debtModelXMLExporter;
+
+  @Mock
+  System2 system2;
+
+  @Captor
+  ArgumentCaptor<CharacteristicDto> characteristicArgument;
+
+  @Captor
+  ArgumentCaptor<RuleDto> ruleArgument;
+
+  @Captor
+  ArgumentCaptor<ArrayList<RuleDebt>> ruleDebtListCaptor;
+
+  Date oldDate = DateUtils.parseDate("2014-01-01");
+  Date now = DateUtils.parseDate("2014-03-19");
+
+  int currentId;
+
+  DebtModel debtModel = new DebtModel();
+  List<DebtModelXMLExporter.RuleDebt> rules = newArrayList();
+
+  DebtModelBackup debtModelBackup;
+
+  @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(debtModel);
+    when(characteristicsXMLImporter.importXML(anyString())).thenReturn(debtModel);
+    when(rulesXMLImporter.importXML(anyString(), any(ValidationMessages.class))).thenReturn(rules);
+
+    debtModelBackup = new DebtModelBackup(myBatis, dao, ruleDao, debtModelOperations, debtModelPluginRepository, characteristicsXMLImporter, rulesXMLImporter,
+      debtModelXMLExporter, system2);
+  }
+
+  @Test
+  public void backup() throws Exception {
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler updated").setParentId(1)
+    ));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      // Rule with overridden debt values
+      new RuleDto().setRepositoryKey("squid").setRuleKey("UselessImportCheck").setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("15min"),
+
+      // Rule with default debt values
+      new RuleDto().setRepositoryKey("squid").setRuleKey("AvoidNPE").setDefaultCharacteristicId(2).setDefaultRemediationFunction("LINEAR").setDefaultRemediationFactor("2h")
+    ));
+
+    debtModelBackup.backup();
+
+    ArgumentCaptor<DebtModel> debtModelArgument = ArgumentCaptor.forClass(DebtModel.class);
+    verify(debtModelXMLExporter).export(debtModelArgument.capture(), ruleDebtListCaptor.capture());
+    assertThat(debtModelArgument.getValue().rootCharacteristics()).hasSize(1);
+    assertThat(debtModelArgument.getValue().subCharacteristics("PORTABILITY")).hasSize(1);
+
+    List<RuleDebt> rules = ruleDebtListCaptor.getValue();
+    assertThat(rules).hasSize(2);
+
+    RuleDebt rule = rules.get(0);
+    assertThat(rule.ruleKey().repository()).isEqualTo("squid");
+    assertThat(rule.ruleKey().rule()).isEqualTo("UselessImportCheck");
+    assertThat(rule.characteristicKey()).isEqualTo("COMPILER");
+    assertThat(rule.function().name()).isEqualTo("LINEAR_OFFSET");
+    assertThat(rule.factor()).isEqualTo("2h");
+    assertThat(rule.offset()).isEqualTo("15min");
+
+    rule = rules.get(1);
+    assertThat(rule.ruleKey().repository()).isEqualTo("squid");
+    assertThat(rule.ruleKey().rule()).isEqualTo("AvoidNPE");
+    assertThat(rule.characteristicKey()).isEqualTo("COMPILER");
+    assertThat(rule.function().name()).isEqualTo("LINEAR");
+    assertThat(rule.factor()).isEqualTo("2h");
+    assertThat(rule.offset()).isNull();
+  }
+
+  @Test
+  public void backup_with_disabled_rules() throws Exception {
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler updated").setParentId(1)
+    ));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      // Debt disabled
+      new RuleDto().setRepositoryKey("squid").setRuleKey("UselessImportCheck").setCharacteristicId(RuleDto.DISABLED_CHARACTERISTIC_ID),
+
+      // Not debt
+      new RuleDto().setRepositoryKey("squid").setRuleKey("AvoidNPE")
+    ));
+
+    debtModelBackup.backup();
+
+    verify(debtModelXMLExporter).export(any(DebtModel.class), ruleDebtListCaptor.capture());
+
+    assertThat(ruleDebtListCaptor.getValue()).isEmpty();
+  }
+
+  @Test
+  public void backup_from_language() throws Exception {
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler updated").setParentId(1)
+    ));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck").setLanguage("java")
+        .setCharacteristicId(2).setRemediationFunction("CONSTANT_ISSUE").setRemediationOffset("15min")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate),
+      // Should be ignored
+      new RuleDto().setId(2).setRepositoryKey("checkstyle").setLanguage("java2")
+        .setCharacteristicId(3).setRemediationFunction("LINEAR").setRemediationFactor("2h")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.backup("java");
+
+    verify(debtModelXMLExporter).export(any(DebtModel.class), ruleDebtListCaptor.capture());
+
+    List<RuleDebt> rules = ruleDebtListCaptor.getValue();
+    assertThat(rules).hasSize(1);
+
+    RuleDebt rule = rules.get(0);
+    assertThat(rule.ruleKey().repository()).isEqualTo("squid");
+    assertThat(rule.ruleKey().rule()).isEqualTo("UselessImportCheck");
+    assertThat(rule.characteristicKey()).isEqualTo("COMPILER");
+    assertThat(rule.function().name()).isEqualTo("CONSTANT_ISSUE");
+    assertThat(rule.factor()).isNull();
+    assertThat(rule.offset()).isEqualTo("15min");
+  }
+
+  @Test
+  public void create_characteristics_when_restoring_characteristics() throws Exception {
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(Collections.<CharacteristicDto>emptyList());
+
+    debtModelBackup.restoreCharacteristics(
+      new DebtModel()
+        .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+        .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY"),
+      now,
+      session
+    );
+
+    verify(dao, times(2)).insert(characteristicArgument.capture(), eq(session));
+
+    CharacteristicDto dto1 = characteristicArgument.getAllValues().get(0);
+    assertThat(dto1.getId()).isEqualTo(10);
+    assertThat(dto1.getKey()).isEqualTo("PORTABILITY");
+    assertThat(dto1.getName()).isEqualTo("Portability");
+    assertThat(dto1.getParentId()).isNull();
+    assertThat(dto1.getOrder()).isEqualTo(1);
+    assertThat(dto1.getCreatedAt()).isEqualTo(now);
+    assertThat(dto1.getUpdatedAt()).isNull();
+
+    CharacteristicDto dto2 = characteristicArgument.getAllValues().get(1);
+    assertThat(dto2.getId()).isEqualTo(11);
+    assertThat(dto2.getKey()).isEqualTo("COMPILER");
+    assertThat(dto2.getName()).isEqualTo("Compiler");
+    assertThat(dto2.getParentId()).isEqualTo(10);
+    assertThat(dto2.getOrder()).isNull();
+    assertThat(dto2.getCreatedAt()).isEqualTo(now);
+    assertThat(dto2.getUpdatedAt()).isNull();
+  }
+
+  @Test
+  public void update_characteristics_when_restoring_characteristics() throws Exception {
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2).setCreatedAt(oldDate).setUpdatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler updated").setParentId(1).setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restoreCharacteristics(
+      new DebtModel()
+        .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+        .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY"),
+      now,
+      session
+    );
+
+    verify(dao, times(2)).update(characteristicArgument.capture(), eq(session));
+
+    CharacteristicDto dto1 = characteristicArgument.getAllValues().get(0);
+    assertThat(dto1.getId()).isEqualTo(1);
+    assertThat(dto1.getKey()).isEqualTo("PORTABILITY");
+    assertThat(dto1.getName()).isEqualTo("Portability");
+    assertThat(dto1.getParentId()).isNull();
+    assertThat(dto1.getOrder()).isEqualTo(1);
+    assertThat(dto1.getCreatedAt()).isEqualTo(oldDate);
+    assertThat(dto1.getUpdatedAt()).isEqualTo(now);
+
+    CharacteristicDto dto2 = characteristicArgument.getAllValues().get(1);
+    assertThat(dto2.getId()).isEqualTo(2);
+    assertThat(dto2.getKey()).isEqualTo("COMPILER");
+    assertThat(dto2.getName()).isEqualTo("Compiler");
+    assertThat(dto2.getParentId()).isEqualTo(1);
+    assertThat(dto2.getOrder()).isNull();
+    assertThat(dto2.getCreatedAt()).isEqualTo(oldDate);
+    assertThat(dto2.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").setName("Compiler").setParentId(1);
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(dto1, dto2));
+
+    debtModelBackup.restoreCharacteristics(new DebtModel(), now, session);
+
+    verify(debtModelOperations).disableCharacteristic(dto1, now, session);
+    verify(debtModelOperations).disableCharacteristic(dto2, now, session);
+  }
+
+  @Test
+  public void restore_from_provided_model() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler updated").setParentId(1).setCreatedAt(oldDate)
+    ));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setRepositoryKey("squid").setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("15min")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restore();
+
+    verify(dao).selectEnabledCharacteristics(session);
+    verify(dao, times(2)).update(any(CharacteristicDto.class), eq(session));
+    verifyNoMoreInteractions(dao);
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    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();
+  }
+
+  @Test
+  public void restore_from_language() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability updated").setOrder(2).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler updated").setParentId(1).setCreatedAt(oldDate)
+    ));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setLanguage("java")
+        .setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("15min")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate),
+      // Should be ignored
+      new RuleDto().setId(2).setRepositoryKey("checkstyle").setLanguage("java2")
+        .setCharacteristicId(3).setRemediationFunction("LINEAR").setRemediationFactor("2h")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restore("java");
+
+    verify(dao).selectEnabledCharacteristics(session);
+    verify(dao, times(2)).update(any(CharacteristicDto.class), eq(session));
+    verifyNoMoreInteractions(dao);
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getId()).isEqualTo(1);
+
+    verify(session).commit();
+  }
+
+  @Test
+  public void restore_from_xml_with_different_characteristic_and_same_function() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
+
+    rules.add(new RuleDebt()
+      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR).setFactor("2h"));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
+        .setDefaultCharacteristicId(10).setDefaultRemediationFunction("LINEAR").setDefaultRemediationFactor("2h")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restoreFromXml("<xml/>");
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getId()).isEqualTo(1);
+    assertThat(rule.getCharacteristicId()).isEqualTo(2);
+    assertThat(rule.getRemediationFunction()).isNull();
+    assertThat(rule.getRemediationFactor()).isNull();
+    assertThat(rule.getRemediationOffset()).isNull();
+    assertThat(rule.getUpdatedAt()).isEqualTo(now);
+
+    verify(session).commit();
+  }
+
+  @Test
+  public void restore_from_xml_with_same_characteristic_and_different_function() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
+
+    rules.add(new RuleDebt()
+      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET).setFactor("12h").setOffset("11min"));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
+        .setDefaultCharacteristicId(2).setDefaultRemediationFunction("LINEAR").setDefaultRemediationFactor("2h")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restoreFromXml("<xml/>");
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getId()).isEqualTo(1);
+    assertThat(rule.getCharacteristicId()).isNull();
+    assertThat(rule.getRemediationFunction()).isEqualTo("LINEAR_OFFSET");
+    assertThat(rule.getRemediationFactor()).isEqualTo("12h");
+    assertThat(rule.getRemediationOffset()).isEqualTo("11min");
+    assertThat(rule.getUpdatedAt()).isEqualTo(now);
+
+    verify(session).commit();
+  }
+
+  @Test
+  public void restore_from_xml_with_same_characteristic_and_same_function() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
+
+    rules.add(new RuleDebt()
+      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET).setFactor("2h").setOffset("15min"));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
+        .setDefaultCharacteristicId(2).setDefaultRemediationFunction("LINEAR_OFFSET").setDefaultRemediationFactor("2h").setDefaultRemediationOffset("15min")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restoreFromXml("<xml/>");
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getId()).isEqualTo(1);
+    assertThat(rule.getCharacteristicId()).isNull();
+    assertThat(rule.getRemediationFunction()).isNull();
+    assertThat(rule.getRemediationFactor()).isNull();
+    assertThat(rule.getRemediationOffset()).isNull();
+    assertThat(rule.getUpdatedAt()).isEqualTo(now);
+
+    verify(session).commit();
+  }
+
+  @Test
+  public void restore_from_xml_disable_rule_debt_when_not_in_xml_and_rule_have_default_debt_values() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
+        .setDefaultCharacteristicId(2).setDefaultRemediationFunction("LINEAR_OFFSET").setDefaultRemediationFactor("2h").setDefaultRemediationOffset("15min")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restoreFromXml("<xml/>");
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getId()).isEqualTo(1);
+    assertThat(rule.getCharacteristicId()).isEqualTo(-1);
+    assertThat(rule.getRemediationFunction()).isNull();
+    assertThat(rule.getRemediationFactor()).isNull();
+    assertThat(rule.getRemediationOffset()).isNull();
+    assertThat(rule.getUpdatedAt()).isEqualTo(now);
+
+    verify(session).commit();
+  }
+
+  @Test
+  public void restore_from_xml_set_no_rule_debt_when_not_in_xml_and_rule_has_no_default_debt_values() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restoreFromXml("<xml/>");
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getId()).isEqualTo(1);
+    // As rule has no debt value, characteristic is set to null
+    assertThat(rule.getCharacteristicId()).isNull();
+    assertThat(rule.getRemediationFunction()).isNull();
+    assertThat(rule.getRemediationFactor()).isNull();
+    assertThat(rule.getRemediationOffset()).isNull();
+    assertThat(rule.getUpdatedAt()).isEqualTo(now);
+
+    verify(session).commit();
+  }
+
+  @Test
+  public void restore_from_xml_and_language() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
+
+    rules.add(new RuleDebt()
+      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR).setFactor("2h"));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
+      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck").setLanguage("java")
+        .setDefaultCharacteristicId(10).setDefaultRemediationFunction("LINEAR").setDefaultRemediationFactor("2h")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate),
+      // Should be ignored
+      new RuleDto().setId(2).setRepositoryKey("checkstyle").setLanguage("java2")
+        .setCharacteristicId(3).setRemediationFunction("LINEAR").setRemediationFactor("2h")
+        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
+    ));
+
+    debtModelBackup.restoreFromXml("<xml/>", "java");
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verify(ruleDao).update(ruleArgument.capture(), eq(session));
+    verifyNoMoreInteractions(ruleDao);
+
+    RuleDto rule = ruleArgument.getValue();
+    assertThat(rule.getId()).isEqualTo(1);
+
+    verify(session).commit();
+  }
+
+  @Test
+  public void add_warning_message_when_rule_from_xml_is_not_found() throws Exception {
+    debtModel
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
+
+    when(dao.selectEnabledCharacteristics(session)).thenReturn(newArrayList(
+      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
+      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
+
+    rules.add(new RuleDebt()
+      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR).setFactor("2h"));
+
+    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(Collections.<RuleDto>emptyList());
+
+    ValidationMessages validationMessages = debtModelBackup.restoreFromXml("<xml/>");
+
+    assertThat(validationMessages.getWarnings()).hasSize(1);
+
+    verify(ruleDao).selectEnablesAndNonManual(session);
+    verifyNoMoreInteractions(ruleDao);
+
+    verify(session).commit();
+  }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java b/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java
new file mode 100644 (file)
index 0000000..0197ce7
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Resources;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.SonarPlugin;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DebtModelPluginRepositoryTest {
+
+  private static final String TEST_XML_PREFIX_PATH = "org/sonar/server/debt/DebtModelPluginRepositoryTest/";
+
+  private DebtModelPluginRepository modelFinder;
+
+  @Test
+  public void test_component_initialization() throws Exception {
+    // we do have the "csharp-model.xml" file in src/test/resources
+    PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class);
+    when(csharpPluginMetadata.getKey()).thenReturn("csharp");
+
+    // but we don' have the "php-model.xml" one
+    PluginMetadata phpPluginMetadata = mock(PluginMetadata.class);
+    when(phpPluginMetadata.getKey()).thenReturn("php");
+
+    PluginRepository repository = mock(PluginRepository.class);
+    when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
+    FakePlugin fakePlugin = new FakePlugin();
+    when(repository.getPlugin(anyString())).thenReturn(fakePlugin);
+    modelFinder = new DebtModelPluginRepository(repository, TEST_XML_PREFIX_PATH);
+
+    // when
+    modelFinder.start();
+
+    // assert
+    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+    assertThat(contributingPluginList.size()).isEqualTo(2);
+    assertThat(contributingPluginList).containsOnly("technical-debt", "csharp");
+  }
+
+  @Test
+  public void contributing_plugin_list() throws Exception {
+    initModel();
+    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+    assertThat(contributingPluginList.size()).isEqualTo(2);
+    assertThat(contributingPluginList).contains("csharp", "java");
+  }
+
+  @Test
+  public void get_content_for_xml_file() throws Exception {
+    initModel();
+    Reader xmlFileReader = null;
+    try {
+      xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
+      assertNotNull(xmlFileReader);
+      List<String> lines = IOUtils.readLines(xmlFileReader);
+      assertThat(lines.size()).isEqualTo(25);
+      assertThat(lines.get(0)).isEqualTo("<sqale>");
+    } catch (Exception e) {
+      fail("Should be able to read the XML file.");
+    } finally {
+      IOUtils.closeQuietly(xmlFileReader);
+    }
+  }
+
+  @Test
+  public void return_xml_file_path_for_plugin() throws Exception {
+    initModel();
+    assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
+  }
+
+  @Test
+  public void contain_default_model() throws Exception {
+    modelFinder = new DebtModelPluginRepository(mock(PluginRepository.class));
+    modelFinder.start();
+    assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
+  }
+
+  private void initModel() throws MalformedURLException {
+    Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
+    contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
+    contributingPluginKeyToClassLoader.put("java", newClassLoader());
+    modelFinder = new DebtModelPluginRepository(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
+  }
+
+  private ClassLoader newClassLoader() throws MalformedURLException {
+    ClassLoader loader = mock(ClassLoader.class);
+    when(loader.getResourceAsStream(anyString())).thenAnswer(new Answer<InputStream>() {
+      public InputStream answer(InvocationOnMock invocation) throws Throwable {
+        return new FileInputStream(Resources.getResource((String) invocation.getArguments()[0]).getPath());
+      }
+    });
+    return loader;
+  }
+
+  class FakePlugin extends SonarPlugin {
+    public List getExtensions() {
+      return null;
+    }
+  }
+
+}
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
deleted file mode 100644 (file)
index 05b8ac5..0000000
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-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.rule.RuleKey;
-import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
-import org.sonar.api.server.rule.DebtRemediationFunction;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.ValidationMessages;
-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.rule.RuleRepositories;
-import org.sonar.server.user.MockUserSession;
-
-import java.io.Reader;
-import java.util.Collections;
-import java.util.Date;
-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;
-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
-  DebtRulesXMLImporter rulesXMLImporter;
-
-  @Mock
-  RuleRepositories ruleRepositories;
-
-  @Mock
-  System2 system2;
-
-  Date now = DateUtils.parseDate("2014-03-19");
-
-  int currentId;
-
-  DebtModel characteristics = new DebtModel();
-  List<DebtRulesXMLImporter.RuleDebt> rules = newArrayList();
-
-  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(characteristics);
-    when(characteristicsXMLImporter.importXML(anyString())).thenReturn(characteristics);
-    when(rulesXMLImporter.importXML(anyString(), any(ValidationMessages.class))).thenReturn(rules);
-
-    debtModelRestore = new DebtModelRestore(myBatis, dao, ruleDao, debtModelOperations, debtModelPluginRepository, ruleRepositories, characteristicsXMLImporter, rulesXMLImporter,
-      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").setName("Compiler"), "PORTABILITY"),
-      Collections.<CharacteristicDto>emptyList(),
-      now,
-      session
-    );
-
-    ArgumentCaptor<CharacteristicDto> characteristicArgument = ArgumentCaptor.forClass(CharacteristicDto.class);
-    verify(dao, times(2)).insert(characteristicArgument.capture(), eq(session));
-
-    CharacteristicDto dto1 = characteristicArgument.getAllValues().get(0);
-    assertThat(dto1.getId()).isEqualTo(10);
-    assertThat(dto1.getKey()).isEqualTo("PORTABILITY");
-    assertThat(dto1.getName()).isEqualTo("Portability");
-    assertThat(dto1.getParentId()).isNull();
-    assertThat(dto1.getOrder()).isEqualTo(1);
-    assertThat(dto1.getCreatedAt()).isEqualTo(now);
-    assertThat(dto1.getUpdatedAt()).isNull();
-
-    CharacteristicDto dto2 = characteristicArgument.getAllValues().get(1);
-    assertThat(dto2.getId()).isEqualTo(11);
-    assertThat(dto2.getKey()).isEqualTo("COMPILER");
-    assertThat(dto2.getName()).isEqualTo("Compiler");
-    assertThat(dto2.getParentId()).isEqualTo(10);
-    assertThat(dto2.getOrder()).isNull();
-    assertThat(dto2.getCreatedAt()).isEqualTo(now);
-    assertThat(dto2.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").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").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));
-
-    CharacteristicDto dto1 = characteristicArgument.getAllValues().get(0);
-    assertThat(dto1.getId()).isEqualTo(1);
-    assertThat(dto1.getKey()).isEqualTo("PORTABILITY");
-    assertThat(dto1.getName()).isEqualTo("Portability");
-    assertThat(dto1.getParentId()).isNull();
-    assertThat(dto1.getOrder()).isEqualTo(1);
-    assertThat(dto1.getCreatedAt()).isEqualTo(oldDate);
-    assertThat(dto1.getUpdatedAt()).isEqualTo(now);
-
-    CharacteristicDto dto2 = characteristicArgument.getAllValues().get(1);
-    assertThat(dto2.getId()).isEqualTo(2);
-    assertThat(dto2.getKey()).isEqualTo("COMPILER");
-    assertThat(dto2.getName()).isEqualTo("Compiler");
-    assertThat(dto2.getParentId()).isEqualTo(1);
-    assertThat(dto2.getOrder()).isNull();
-    assertThat(dto2.getCreatedAt()).isEqualTo(oldDate);
-    assertThat(dto2.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").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");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").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").setName("Compiler updated").setParentId(1).setCreatedAt(oldDate)
-    ));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
-      new RuleDto().setRepositoryKey("squid").setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("15min")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
-    ));
-
-    debtModelRestore.restore();
-
-    verify(dao).selectEnabledCharacteristics();
-    verify(dao, times(2)).update(any(CharacteristicDto.class), eq(session));
-    verifyNoMoreInteractions(dao);
-
-    verify(ruleDao).selectEnablesAndNonManual(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();
-  }
-
-  @Test
-  public void restore_from_language() throws Exception {
-    Date oldDate = DateUtils.parseDate("2014-01-01");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").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").setName("Compiler updated").setParentId(1).setCreatedAt(oldDate)
-    ));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
-      new RuleDto().setId(1).setRepositoryKey("squid")
-        .setCharacteristicId(2).setRemediationFunction("LINEAR_OFFSET").setRemediationFactor("2h").setRemediationOffset("15min")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate),
-      // Should be ignored
-      new RuleDto().setId(2).setRepositoryKey("checkstyle")
-        .setCharacteristicId(3).setRemediationFunction("LINEAR").setRemediationFactor("2h")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
-    ));
-
-    RuleRepositories.Repository squid = mock(RuleRepositories.Repository.class);
-    when(squid.getKey()).thenReturn("squid");
-    when(ruleRepositories.repositoriesForLang("java")).thenReturn(newArrayList(squid));
-
-    debtModelRestore.restore("java");
-
-    verify(dao).selectEnabledCharacteristics();
-    verify(dao, times(2)).update(any(CharacteristicDto.class), eq(session));
-    verifyNoMoreInteractions(dao);
-
-    verify(ruleDao).selectEnablesAndNonManual(session);
-    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
-    verify(ruleDao).update(ruleArgument.capture(), eq(session));
-    verifyNoMoreInteractions(ruleDao);
-
-    RuleDto rule = ruleArgument.getValue();
-    assertThat(rule.getId()).isEqualTo(1);
-
-    verify(session).commit();
-  }
-
-  @Test
-  public void restore_from_xml_with_different_characteristic_and_same_function() throws Exception {
-    Date oldDate = DateUtils.parseDate("2014-01-01");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
-
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(
-      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
-      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
-
-    rules.add(new DebtRulesXMLImporter.RuleDebt()
-      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR).setFactor("2h"));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
-      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
-        .setDefaultCharacteristicId(10).setDefaultRemediationFunction("LINEAR").setDefaultRemediationFactor("2h")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
-    ));
-
-    debtModelRestore.restoreFromXml("<xml/>");
-
-    verify(ruleDao).selectEnablesAndNonManual(session);
-    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
-    verify(ruleDao).update(ruleArgument.capture(), eq(session));
-    verifyNoMoreInteractions(ruleDao);
-
-    RuleDto rule = ruleArgument.getValue();
-    assertThat(rule.getId()).isEqualTo(1);
-    assertThat(rule.getCharacteristicId()).isEqualTo(2);
-    assertThat(rule.getRemediationFunction()).isNull();
-    assertThat(rule.getRemediationFactor()).isNull();
-    assertThat(rule.getRemediationOffset()).isNull();
-    assertThat(rule.getUpdatedAt()).isEqualTo(now);
-
-    verify(session).commit();
-  }
-
-  @Test
-  public void restore_from_xml_with_same_characteristic_and_different_function() throws Exception {
-    Date oldDate = DateUtils.parseDate("2014-01-01");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
-
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(
-      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
-      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
-
-    rules.add(new DebtRulesXMLImporter.RuleDebt()
-      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET).setFactor("12h").setOffset("11min"));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
-      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
-        .setDefaultCharacteristicId(2).setDefaultRemediationFunction("LINEAR").setDefaultRemediationFactor("2h")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
-    ));
-
-    debtModelRestore.restoreFromXml("<xml/>");
-
-    verify(ruleDao).selectEnablesAndNonManual(session);
-    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
-    verify(ruleDao).update(ruleArgument.capture(), eq(session));
-    verifyNoMoreInteractions(ruleDao);
-
-    RuleDto rule = ruleArgument.getValue();
-    assertThat(rule.getId()).isEqualTo(1);
-    assertThat(rule.getCharacteristicId()).isNull();
-    assertThat(rule.getRemediationFunction()).isEqualTo("LINEAR_OFFSET");
-    assertThat(rule.getRemediationFactor()).isEqualTo("12h");
-    assertThat(rule.getRemediationOffset()).isEqualTo("11min");
-    assertThat(rule.getUpdatedAt()).isEqualTo(now);
-
-    verify(session).commit();
-  }
-
-  @Test
-  public void restore_from_xml_with_same_characteristic_and_same_function() throws Exception {
-    Date oldDate = DateUtils.parseDate("2014-01-01");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
-
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(
-      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
-      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
-
-    rules.add(new DebtRulesXMLImporter.RuleDebt()
-      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET).setFactor("2h").setOffset("15min"));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
-      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
-        .setDefaultCharacteristicId(2).setDefaultRemediationFunction("LINEAR_OFFSET").setDefaultRemediationFactor("2h").setDefaultRemediationOffset("15min")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
-    ));
-
-    debtModelRestore.restoreFromXml("<xml/>");
-
-    verify(ruleDao).selectEnablesAndNonManual(session);
-    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
-    verify(ruleDao).update(ruleArgument.capture(), eq(session));
-    verifyNoMoreInteractions(ruleDao);
-
-    RuleDto rule = ruleArgument.getValue();
-    assertThat(rule.getId()).isEqualTo(1);
-    assertThat(rule.getCharacteristicId()).isNull();
-    assertThat(rule.getRemediationFunction()).isNull();
-    assertThat(rule.getRemediationFactor()).isNull();
-    assertThat(rule.getRemediationOffset()).isNull();
-    assertThat(rule.getUpdatedAt()).isEqualTo(now);
-
-    verify(session).commit();
-  }
-
-  @Test
-  public void restore_from_xml_disable_rule_debt_when_not_in_xml() throws Exception {
-    Date oldDate = DateUtils.parseDate("2014-01-01");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
-
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(
-      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
-      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
-      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
-        .setDefaultCharacteristicId(2).setDefaultRemediationFunction("LINEAR_OFFSET").setDefaultRemediationFactor("2h").setDefaultRemediationOffset("15min")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
-    ));
-
-    debtModelRestore.restoreFromXml("<xml/>");
-
-    verify(ruleDao).selectEnablesAndNonManual(session);
-    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
-    verify(ruleDao).update(ruleArgument.capture(), eq(session));
-    verifyNoMoreInteractions(ruleDao);
-
-    RuleDto rule = ruleArgument.getValue();
-    assertThat(rule.getId()).isEqualTo(1);
-    assertThat(rule.getCharacteristicId()).isEqualTo(-1);
-    assertThat(rule.getRemediationFunction()).isNull();
-    assertThat(rule.getRemediationFactor()).isNull();
-    assertThat(rule.getRemediationOffset()).isNull();
-    assertThat(rule.getUpdatedAt()).isEqualTo(now);
-
-    verify(session).commit();
-  }
-
-  @Test
-  public void restore_from_xml_and_language() throws Exception {
-    Date oldDate = DateUtils.parseDate("2014-01-01");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
-
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(
-      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
-      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
-
-    rules.add(new DebtRulesXMLImporter.RuleDebt()
-      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR).setFactor("2h"));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(newArrayList(
-      new RuleDto().setId(1).setRepositoryKey("squid").setRuleKey("UselessImportCheck")
-        .setDefaultCharacteristicId(10).setDefaultRemediationFunction("LINEAR").setDefaultRemediationFactor("2h")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate),
-      // Should be ignored
-      new RuleDto().setId(2).setRepositoryKey("checkstyle")
-        .setCharacteristicId(3).setRemediationFunction("LINEAR").setRemediationFactor("2h")
-        .setCreatedAt(oldDate).setUpdatedAt(oldDate)
-    ));
-
-    RuleRepositories.Repository squid = mock(RuleRepositories.Repository.class);
-    when(squid.getKey()).thenReturn("squid");
-    when(ruleRepositories.repositoriesForLang("java")).thenReturn(newArrayList(squid));
-
-    debtModelRestore.restoreFromXml("<xml/>", "java");
-
-    verify(ruleDao).selectEnablesAndNonManual(session);
-    ArgumentCaptor<RuleDto> ruleArgument = ArgumentCaptor.forClass(RuleDto.class);
-    verify(ruleDao).update(ruleArgument.capture(), eq(session));
-    verifyNoMoreInteractions(ruleDao);
-
-    RuleDto rule = ruleArgument.getValue();
-    assertThat(rule.getId()).isEqualTo(1);
-
-    verify(session).commit();
-  }
-
-  @Test
-  public void add_warning_message_when_rule_from_xml_is_not_found() throws Exception {
-    Date oldDate = DateUtils.parseDate("2014-01-01");
-
-    characteristics
-      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(1))
-      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"), "PORTABILITY");
-
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(
-      new CharacteristicDto().setId(1).setKey("PORTABILITY").setName("Portability").setOrder(1).setCreatedAt(oldDate),
-      new CharacteristicDto().setId(2).setKey("COMPILER").setName("Compiler").setParentId(1).setCreatedAt(oldDate)));
-
-    rules.add(new DebtRulesXMLImporter.RuleDebt()
-      .setRuleKey(RuleKey.of("squid", "UselessImportCheck")).setCharacteristicKey("COMPILER").setFunction(DebtRemediationFunction.Type.LINEAR).setFactor("2h"));
-
-    when(ruleDao.selectEnablesAndNonManual(session)).thenReturn(Collections.<RuleDto>emptyList());
-
-    ValidationMessages validationMessages = debtModelRestore.restoreFromXml("<xml/>");
-
-    assertThat(validationMessages.getWarnings()).hasSize(1);
-
-    verify(ruleDao).selectEnablesAndNonManual(session);
-    verifyNoMoreInteractions(ruleDao);
-
-    verify(session).commit();
-  }
-
-}
index 59051abf5f6fdb088abbbfa98a78bfb34f18e71f..ee08ba6ca95a4b273ea8d2442a234c52da295a62 100644 (file)
@@ -37,13 +37,13 @@ public class DebtModelServiceTest {
   DebtModelLookup debtModelLookup;
 
   @Mock
-  DebtModelRestore debtModelRestore;
+  DebtModelBackup debtModelBackup;
 
   DebtModelService service;
 
   @Before
   public void setUp() throws Exception {
-    service = new DebtModelService(debtModelOperations, debtModelLookup, debtModelRestore);
+    service = new DebtModelService(debtModelOperations, debtModelLookup, debtModelBackup);
   }
 
   @Test
@@ -97,25 +97,36 @@ public class DebtModelServiceTest {
   @Test
   public void restore_provided_model() {
     service.restore();
-    verify(debtModelRestore).restore();
+    verify(debtModelBackup).restore();
   }
 
   @Test
   public void restore_from_language() {
     service.restoreFromLanguage("xoo");
-    verify(debtModelRestore).restore("xoo");
+    verify(debtModelBackup).restore("xoo");
   }
 
   @Test
   public void restore_xml() {
     service.restoreFromXml("<xml/>");
-    verify(debtModelRestore).restoreFromXml("<xml/>");
+    verify(debtModelBackup).restoreFromXml("<xml/>");
   }
 
   @Test
   public void restore_from_xml_and_language() {
     service.restoreFromXmlAndLanguage("<xml/>", "xoo");
-    verify(debtModelRestore).restoreFromXml("<xml/>", "xoo");
+    verify(debtModelBackup).restoreFromXml("<xml/>", "xoo");
   }
 
+  @Test
+  public void backup() {
+    service.backup();
+    verify(debtModelBackup).backup();
+  }
+
+  @Test
+  public void backup_fom_language() {
+    service.backupFromLanguage("xoo");
+    verify(debtModelBackup).backup("xoo");
+  }
 }
diff --git a/sonar-server/src/test/java/org/sonar/server/debt/DebtModelXMLExporterTest.java b/sonar-server/src/test/java/org/sonar/server/debt/DebtModelXMLExporterTest.java
new file mode 100644 (file)
index 0000000..a40df68
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.Charsets;
+import com.google.common.io.Resources;
+import org.apache.commons.lang.SystemUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
+import org.sonar.api.server.rule.DebtRemediationFunction;
+import org.sonar.test.TestUtils;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.sonar.server.debt.DebtModelXMLExporter.DebtModel;
+import static org.sonar.server.debt.DebtModelXMLExporter.RuleDebt;
+
+public class DebtModelXMLExporterTest {
+
+  private DebtModelXMLExporter xmlExporter;
+
+  @Before
+  public void setup() {
+    xmlExporter = new DebtModelXMLExporter();
+  }
+
+  @Test
+  public void export_empty() {
+    assertThat(xmlExporter.export(new DebtModel(), Collections.<RuleDebt>emptyList())).isEqualTo("<sqale/>" + SystemUtils.LINE_SEPARATOR);
+  }
+
+  @Test
+  public void export_xml() throws Exception {
+    DebtModel debtModel = new DebtModel()
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setId(1).setKey("USABILITY").setName("Usability").setOrder(1))
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setId(2).setKey("EFFICIENCY").setName("Efficiency").setOrder(2))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setId(3).setKey("MEMORY_USE").setName("Memory use").setParentId(2), "EFFICIENCY");
+
+    List<RuleDebt> rules = newArrayList(
+      new RuleDebt().setRuleKey(RuleKey.of("checkstyle", "Regexp"))
+        .setCharacteristicKey("MEMORY_USE").setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET).setFactor("3d").setOffset("15min")
+    );
+
+    TestUtils.assertSimilarXml(getFileContent("export_xml.xml"), xmlExporter.export(debtModel, rules));
+  }
+
+  @Test
+  public void sort_root_characteristics_by_order_and_sub_characteristics_by_name() throws Exception {
+    DebtModel debtModel = new DebtModel()
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("EFFICIENCY").setName("Efficiency").setOrder(4))
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("USABILITY").setName("Usability").setOrder(3))
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setKey("PORTABILITY").setName("Portability").setOrder(2))
+
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("RAM_EFFICIENCY").setName("RAM Efficiency"), "EFFICIENCY")
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("CPU_EFFICIENCY").setName("CPU Efficiency"), "EFFICIENCY")
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setKey("OTHER_EFFICIENCY").setName("Other Efficiency"), "EFFICIENCY");
+
+    String xml = xmlExporter.export(debtModel, Collections.<RuleDebt>emptyList());
+
+    // root characteristics are sorted by the column "characteristic_order"
+    Pattern regex = Pattern.compile(".*USABILITY.*PORTABILITY.*EFFICIENCY.*", Pattern.DOTALL);
+    assertThat(regex.matcher(xml).matches());
+
+    // sub characteristics are sorted by name
+    regex = Pattern.compile(".*CPU Efficiency.*Other Efficiency.*RAM Efficiency.*", Pattern.DOTALL);
+    assertThat(regex.matcher(xml).matches());
+  }
+
+  @Test
+  public void pretty_print_exported_xml() throws Exception {
+    DebtModel debtModel = new DebtModel()
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setId(1).setKey("USABILITY").setName("Usability").setOrder(1))
+      .addRootCharacteristic(new DefaultDebtCharacteristic().setId(2).setKey("EFFICIENCY").setName("Efficiency").setOrder(2))
+      .addSubCharacteristic(new DefaultDebtCharacteristic().setId(3).setKey("MEMORY_USE").setName("Memory use").setParentId(2), "EFFICIENCY");
+
+    List<RuleDebt> rules = newArrayList(
+      new RuleDebt().setRuleKey(RuleKey.of("checkstyle", "Regexp"))
+        .setCharacteristicKey("MEMORY_USE").setFunction(DebtRemediationFunction.Type.LINEAR).setFactor("3d")
+    );
+    assertThat(xmlExporter.export(debtModel, rules)).isEqualTo(
+      "<sqale>" + SystemUtils.LINE_SEPARATOR +
+        "  <chc>" + SystemUtils.LINE_SEPARATOR +
+        "    <key>USABILITY</key>" + SystemUtils.LINE_SEPARATOR +
+        "    <name>Usability</name>" + SystemUtils.LINE_SEPARATOR +
+        "  </chc>" + SystemUtils.LINE_SEPARATOR +
+        "  <chc>" + SystemUtils.LINE_SEPARATOR +
+        "    <key>EFFICIENCY</key>" + SystemUtils.LINE_SEPARATOR +
+        "    <name>Efficiency</name>" + SystemUtils.LINE_SEPARATOR +
+        "    <chc>" + SystemUtils.LINE_SEPARATOR +
+        "      <key>MEMORY_USE</key>" + SystemUtils.LINE_SEPARATOR +
+        "      <name>Memory use</name>" + SystemUtils.LINE_SEPARATOR +
+        "      <chc>" + SystemUtils.LINE_SEPARATOR +
+        "        <rule-repo>checkstyle</rule-repo>" + SystemUtils.LINE_SEPARATOR +
+        "        <rule-key>Regexp</rule-key>" + SystemUtils.LINE_SEPARATOR +
+        "        <prop>" + SystemUtils.LINE_SEPARATOR +
+        "          <key>remediationFunction</key>" + SystemUtils.LINE_SEPARATOR +
+        "          <txt>LINEAR</txt>" + SystemUtils.LINE_SEPARATOR +
+        "        </prop>" + SystemUtils.LINE_SEPARATOR +
+        "        <prop>" + SystemUtils.LINE_SEPARATOR +
+        "          <key>remediationFactor</key>" + SystemUtils.LINE_SEPARATOR +
+        "          <val>3</val>" + SystemUtils.LINE_SEPARATOR +
+        "          <txt>d</txt>" + SystemUtils.LINE_SEPARATOR +
+        "        </prop>" + SystemUtils.LINE_SEPARATOR +
+        "      </chc>" + SystemUtils.LINE_SEPARATOR +
+        "    </chc>" + SystemUtils.LINE_SEPARATOR +
+        "  </chc>" + SystemUtils.LINE_SEPARATOR +
+        "</sqale>" + SystemUtils.LINE_SEPARATOR
+    );
+  }
+
+  private String getFileContent(String file) {
+    try {
+      return Resources.toString(Resources.getResource(this.getClass(), this.getClass().getSimpleName() + "/" + file), Charsets.UTF_8);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
index e1e8f82b2bc8a33c5e9e095b43230847c229e783..390636ac0f658e656152d2d7e627fd0befa61292 100644 (file)
@@ -32,6 +32,7 @@ import java.util.List;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
+import static org.sonar.server.debt.DebtModelXMLExporter.RuleDebt;
 
 public class DebtRulesXMLImporterTest {
 
@@ -42,7 +43,7 @@ public class DebtRulesXMLImporterTest {
   public void import_rules() {
     String xml = getFileContent("import_rules.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     
     assertThat(results).hasSize(2);
     assertThat(validationMessages.getErrors()).isEmpty();
@@ -53,10 +54,10 @@ public class DebtRulesXMLImporterTest {
   public void import_linear() {
     String xml = getFileContent("import_linear.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
@@ -68,10 +69,10 @@ public class DebtRulesXMLImporterTest {
   public void import_linear_having_offset_to_zero() {
     String xml = getFileContent("import_linear_having_offset_to_zero.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
@@ -83,10 +84,10 @@ public class DebtRulesXMLImporterTest {
   public void import_linear_with_offset() {
     String xml = getFileContent("import_linear_with_offset.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
     assertThat(ruleDebt.factor()).isEqualTo("3h");
@@ -97,10 +98,10 @@ public class DebtRulesXMLImporterTest {
   public void import_constant_issue() {
     String xml = getFileContent("import_constant_issue.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
     assertThat(ruleDebt.factor()).isNull();
@@ -111,10 +112,10 @@ public class DebtRulesXMLImporterTest {
   public void use_default_unit_when_no_unit() {
     String xml = getFileContent("use_default_unit_when_no_unit.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
     assertThat(ruleDebt.factor()).isEqualTo("3d");
@@ -125,24 +126,39 @@ public class DebtRulesXMLImporterTest {
   public void replace_mn_by_min() {
     String xml = getFileContent("replace_mn_by_min.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
     assertThat(ruleDebt.factor()).isEqualTo("3min");
     assertThat(ruleDebt.offset()).isNull();
   }
 
+  @Test
+  public void read_integer() {
+    String xml = getFileContent("read_integer.xml");
+
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
+    assertThat(results).hasSize(1);
+
+    RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
+    assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
+    assertThat(ruleDebt.factor()).isEqualTo("3h");
+    assertThat(ruleDebt.offset()).isNull();
+  }
+
   @Test
   public void convert_deprecated_linear_with_threshold_function_by_linear_function() {
     String xml = getFileContent("convert_deprecated_linear_with_threshold_function_by_linear_function.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
     assertThat(ruleDebt.factor()).isEqualTo("3h");
@@ -155,10 +171,10 @@ public class DebtRulesXMLImporterTest {
   public void convert_constant_per_issue_with_factor_by_constant_by_issue_with_offset() {
     String xml = getFileContent("convert_constant_per_issue_with_factor_by_constant_by_issue_with_offset.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
     assertThat(ruleDebt.factor()).isNull();
@@ -169,7 +185,7 @@ public class DebtRulesXMLImporterTest {
   public void ignore_deprecated_constant_per_file_function() {
     String xml = getFileContent("ignore_deprecated_constant_per_file_function.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).isEmpty();
 
     assertThat(validationMessages.getWarnings()).isNotEmpty();
@@ -179,7 +195,7 @@ public class DebtRulesXMLImporterTest {
   public void ignore_rule_on_root_characteristics() {
     String xml = getFileContent("ignore_rule_on_root_characteristics.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).isEmpty();
 
     assertThat(validationMessages.getWarnings()).isNotEmpty();
@@ -189,10 +205,10 @@ public class DebtRulesXMLImporterTest {
   public void import_badly_formatted_xml() {
     String xml = getFileContent("import_badly_formatted_xml.xml");
 
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).hasSize(1);
 
-    DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
+    RuleDebt ruleDebt = results.get(0);
     assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
     assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
     assertThat(ruleDebt.function()).isEqualTo(org.sonar.api.server.rule.DebtRemediationFunction.Type.LINEAR);
@@ -203,7 +219,7 @@ public class DebtRulesXMLImporterTest {
   @Test
   public void ignore_invalid_value() throws Exception {
     String xml = getFileContent("ignore_invalid_value.xml");
-    List<DebtRulesXMLImporter.RuleDebt> results = importer.importXML(xml, validationMessages);
+    List<RuleDebt> results = importer.importXML(xml, validationMessages);
     assertThat(results).isEmpty();
 
     assertThat(validationMessages.getErrors()).isNotEmpty();
index cfad5d4927b5f4feb5020b902cfdad949fbb1e5c..78d35bf975dd37ad8db35abbc925043de7db4588 100644 (file)
@@ -33,7 +33,8 @@ import org.sonar.api.server.rule.DebtRemediationFunction;
 import org.sonar.api.server.rule.RulesDefinition;
 import org.sonar.api.utils.ValidationMessages;
 import org.sonar.core.i18n.RuleI18nManager;
-import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
+import org.sonar.server.debt.DebtModelPluginRepository;
+import org.sonar.server.debt.DebtModelXMLExporter;
 import org.sonar.server.debt.DebtRulesXMLImporter;
 
 import java.io.Reader;
@@ -54,7 +55,7 @@ public class DeprecatedRulesDefinitionTest {
   RuleI18nManager i18n;
 
   @Mock
-  TechnicalDebtModelRepository debtModelRepository;
+  DebtModelPluginRepository debtModelRepository;
 
   @Mock
   DebtRulesXMLImporter importer;
@@ -156,8 +157,8 @@ public class DeprecatedRulesDefinitionTest {
   public void define_rule_debt() throws Exception {
     RulesDefinition.Context context = new RulesDefinition.Context();
 
-    List<DebtRulesXMLImporter.RuleDebt> ruleDebts = newArrayList(
-      new DebtRulesXMLImporter.RuleDebt()
+    List<DebtModelXMLExporter.RuleDebt> ruleDebts = newArrayList(
+      new DebtModelXMLExporter.RuleDebt()
         .setCharacteristicKey("MEMORY_EFFICIENCY")
         .setRuleKey(RuleKey.of("checkstyle", "ConstantName"))
         .setFunction(DebtRemediationFunction.Type.LINEAR_OFFSET)
index e4d207bff92856f056889e14a2006f626aadc357..456889bd9a0775cc0dc9e5829f2c9c35bcd4d64c 100644 (file)
@@ -27,7 +27,7 @@ import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
 import org.sonar.core.technicaldebt.db.CharacteristicDto;
-import org.sonar.server.debt.DebtModelRestore;
+import org.sonar.server.debt.DebtModelBackup;
 
 import java.util.Collections;
 
@@ -41,13 +41,13 @@ public class RegisterDebtModelTest {
   CharacteristicDao dao;
 
   @Mock
-  DebtModelRestore debtModelRestore;
+  DebtModelBackup debtModelBackup;
 
   RegisterDebtModel registerDebtModel;
 
   @Before
   public void setUp() throws Exception {
-    registerDebtModel = new RegisterDebtModel(dao, debtModelRestore);
+    registerDebtModel = new RegisterDebtModel(dao, debtModelBackup);
   }
 
   @Test
@@ -56,7 +56,7 @@ public class RegisterDebtModelTest {
 
     registerDebtModel.start();
 
-    verify(debtModelRestore).restore();
+    verify(debtModelBackup).restore();
   }
 
   @Test
@@ -65,6 +65,6 @@ public class RegisterDebtModelTest {
 
     registerDebtModel.start();
 
-    verifyZeroInteractions(debtModelRestore);
+    verifyZeroInteractions(debtModelBackup);
   }
 }
diff --git a/sonar-server/src/test/resources/org/sonar/server/debt/DebtModelPluginRepositoryTest/csharp-model.xml b/sonar-server/src/test/resources/org/sonar/server/debt/DebtModelPluginRepositoryTest/csharp-model.xml
new file mode 100644 (file)
index 0000000..e4569a2
--- /dev/null
@@ -0,0 +1,25 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+    <desc>Estimate usability</desc>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <rule-repo>gendarme</rule-repo>
+      <rule-key>EnsureLocalDisposalRule</rule-key>
+      <prop>
+        <key>remediationFactor</key>
+        <val>0.125</val>
+        <txt>d</txt>
+      </prop>
+      <prop>
+        <key>remediationFunction</key>
+        <txt>linear</txt>
+      </prop>
+    </chc>
+  </chc>
+
+</sqale>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/debt/DebtModelPluginRepositoryTest/java-model.xml b/sonar-server/src/test/resources/org/sonar/server/debt/DebtModelPluginRepositoryTest/java-model.xml
new file mode 100644 (file)
index 0000000..0b37f56
--- /dev/null
@@ -0,0 +1,25 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+    <desc>Estimate usability</desc>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <rule-repo>squid-cobol</rule-repo>
+      <rule-key>CheckLoop</rule-key>
+      <prop>
+        <key>remediationFactor</key>
+        <val>0.125</val>
+        <txt>d</txt>
+      </prop>
+      <prop>
+        <key>remediationFunction</key>
+        <txt>linear</txt>
+      </prop>
+    </chc>
+  </chc>
+
+</sqale>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/debt/DebtModelXMLExporterTest/export_xml.xml b/sonar-server/src/test/resources/org/sonar/server/debt/DebtModelXMLExporterTest/export_xml.xml
new file mode 100644 (file)
index 0000000..58efbf3
--- /dev/null
@@ -0,0 +1,32 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <key>MEMORY_USE</key>
+      <name>Memory use</name>
+      <chc>
+        <rule-repo>checkstyle</rule-repo>
+        <rule-key>Regexp</rule-key>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>LINEAR_OFFSET</txt>
+        </prop>
+        <prop>
+          <key>remediationFactor</key>
+          <val>3</val>
+          <txt>d</txt>
+        </prop>
+        <prop>
+          <key>offset</key>
+          <val>15</val>
+          <txt>min</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+</sqale>
diff --git a/sonar-server/src/test/resources/org/sonar/server/debt/DebtRulesXMLImporterTest/read_integer.xml b/sonar-server/src/test/resources/org/sonar/server/debt/DebtRulesXMLImporterTest/read_integer.xml
new file mode 100644 (file)
index 0000000..483a98b
--- /dev/null
@@ -0,0 +1,29 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+    <desc>Estimate usability</desc>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <key>MEMORY_EFFICIENCY</key>
+      <name>Memory use</name>
+      <chc>
+        <rule-repo>checkstyle</rule-repo>
+        <rule-key>Regexp</rule-key>
+        <prop>
+          <key>remediationFactor</key>
+          <val>3</val>
+          <txt>h</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>linear</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>