]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5056 Synchronize rule debt definitions from plugin debt models
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 6 Mar 2014 19:11:32 +0000 (20:11 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 6 Mar 2014 19:11:32 +0000 (20:11 +0100)
34 files changed:
sonar-core/src/main/java/org/sonar/core/technicaldebt/CharacteristicsXMLImporter.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/DebtCharacteristicsSynchronizer.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/RuleDebtXMLImporter.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelRepository.java
sonar-core/src/main/java/org/sonar/core/technicaldebt/db/CharacteristicDao.java
sonar-core/src/test/java/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/technicaldebt/DebtCharacteristicsSynchronizerTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest/import_badly_formatted_xml.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest/import_characteristics.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/Import_linear_with_offset.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/convert_deprecated_linear_with_threshold_function_by_linear_function.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_deprecated_constant_per_file_function.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_invalid_value.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_rule_on_root_characteristics.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_badly_formatted_xml.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_constant_issue.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_linear.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_rules.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/replace_mn_by_min.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/use_default_unit_when_no_unit.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/rule/DeprecatedRuleDefinitions.java
sonar-server/src/main/java/org/sonar/server/rule/RuleRegistration.java
sonar-server/src/main/java/org/sonar/server/startup/RegisterDebtCharacteristicModel.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRuleDefinitionsTest.java
sonar-server/src/test/java/org/sonar/server/rule/RuleRegistrationTest.java
sonar-server/src/test/java/org/sonar/server/startup/RegisterDebtCharacteristicModelTest.java [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/insert_new_rules-result.xml
sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/remove_rule_debt_definitions_if_characteristic_not_found-result.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/remove_rule_debt_definitions_if_characteristic_not_found.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/shared.xml
sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/update_rule_fields.xml
sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/update_template_rule_language.xml

diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/CharacteristicsXMLImporter.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/CharacteristicsXMLImporter.java
new file mode 100644 (file)
index 0000000..9595f96
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.lang.StringUtils;
+import org.codehaus.stax2.XMLInputFactory2;
+import org.codehaus.staxmate.SMInputFactory;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.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 javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+
+import java.io.Reader;
+import java.io.StringReader;
+
+public class CharacteristicsXMLImporter implements ServerExtension {
+
+  private static final Logger LOG = LoggerFactory.getLogger(CharacteristicsXMLImporter.class);
+
+  public static final String CHARACTERISTIC = "chc";
+  public static final String CHARACTERISTIC_KEY = "key";
+  public static final String CHARACTERISTIC_NAME = "name";
+
+  public DefaultTechnicalDebtModel importXML(String xml, ValidationMessages messages) {
+    return importXML(new StringReader(xml), messages);
+  }
+
+  public DefaultTechnicalDebtModel importXML(Reader xml, ValidationMessages messages) {
+    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);
+      }
+
+      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) 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);
+
+      }
+    }
+
+    if (StringUtils.isNotBlank(characteristic.key()) && characteristic.isRoot()) {
+      characteristic.setOrder(model.rootCharacteristics().size() + 1);
+      model.addRootCharacteristic(characteristic);
+      return characteristic;
+    }
+    return null;
+  }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/DebtCharacteristicsSynchronizer.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/DebtCharacteristicsSynchronizer.java
new file mode 100644 (file)
index 0000000..8e8d95a
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+public class DebtCharacteristicsSynchronizer implements ServerExtension {
+
+  private static final Logger LOG = LoggerFactory.getLogger(DebtCharacteristicsSynchronizer.class);
+
+  private final MyBatis mybatis;
+  private final CharacteristicDao dao;
+  private final TechnicalDebtModelRepository languageModelFinder;
+  private final CharacteristicsXMLImporter importer;
+
+  public DebtCharacteristicsSynchronizer(MyBatis mybatis, CharacteristicDao dao, TechnicalDebtModelRepository modelRepository, CharacteristicsXMLImporter importer) {
+    this.mybatis = mybatis;
+    this.dao = dao;
+    this.languageModelFinder = modelRepository;
+    this.importer = importer;
+  }
+
+  public List<CharacteristicDto> synchronize(ValidationMessages messages) {
+    SqlSession session = mybatis.openSession();
+
+    List<CharacteristicDto> model = newArrayList();
+    try {
+      model = synchronize(messages, session);
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+    return model;
+  }
+
+  public List<CharacteristicDto> synchronize(ValidationMessages messages, SqlSession session) {
+    DefaultTechnicalDebtModel defaultModel = loadModelFromXml(TechnicalDebtModelRepository.DEFAULT_MODEL, messages);
+    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) {
+    Reader xmlFileReader = null;
+    try {
+      xmlFileReader = languageModelFinder.createReaderForXMLFile(pluginKey);
+      return importer.importXML(xmlFileReader, messages);
+    } finally {
+      IOUtils.closeQuietly(xmlFileReader);
+    }
+  }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/RuleDebtXMLImporter.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/RuleDebtXMLImporter.java
new file mode 100644 (file)
index 0000000..8ffb8e6
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.RemediationFunction;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.Duration;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+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;
+
+public class RuleDebtXMLImporter implements ServerExtension {
+
+  private static final Logger LOG = LoggerFactory.getLogger(RuleDebtXMLImporter.class);
+
+  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";
+
+  public List<RuleDebt> importXML(String xml) {
+    return importXML(new StringReader(xml));
+  }
+
+  public List<RuleDebt> importXML(Reader xml) {
+    List<RuleDebt> ruleDebts = newArrayList();
+    try {
+      SMInputFactory inputFactory = initStax();
+      SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml);
+
+      // advance to <sqale>
+      cursor.advance();
+      SMInputCursor rootCursor = cursor.childElementCursor(CHARACTERISTIC);
+      while (rootCursor.getNext() != null) {
+        processCharacteristic(ruleDebts, null, null, rootCursor);
+      }
+
+      cursor.getStreamReader().closeCompletely();
+    } catch (
+      XMLStreamException e
+      )
+
+    {
+      LOG.error("XML is not valid", e);
+    }
+
+    return ruleDebts;
+  }
+
+  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 void processCharacteristic(List<RuleDebt> ruleDebts, @Nullable String rootKey, @Nullable String parentKey, SMInputCursor chcCursor) throws XMLStreamException {
+    String currentCharacteristicKey = null;
+    SMInputCursor cursor = chcCursor.childElementCursor();
+    while (cursor.getNext() != null) {
+      String node = cursor.getLocalName();
+      if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
+        currentCharacteristicKey = cursor.collectDescendantText().trim();
+      } else if (StringUtils.equals(node, CHARACTERISTIC)) {
+        processCharacteristic(ruleDebts, parentKey, currentCharacteristicKey, cursor);
+      } else if (StringUtils.equals(node, REPOSITORY_KEY)) {
+        RuleDebt ruleDebt = processRequirement(cursor);
+        if (ruleDebt != null) {
+          if (rootKey != null) {
+            ruleDebt.characteristicKey = parentKey;
+            ruleDebts.add(ruleDebt);
+          } else {
+            LOG.warn("Rule '" + ruleDebt.ruleKey + "' is ignored because it's defined directly under a root characteristic.");
+          }
+        }
+      }
+    }
+  }
+
+  private RuleDebt processRequirement(SMInputCursor cursor)
+    throws XMLStreamException {
+
+    RuleDebt ruleDebt = new RuleDebt();
+    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));
+      } else if (StringUtils.equals(node, RULE_KEY)) {
+        ruleKey = cursor.collectDescendantText().trim();
+      }
+    }
+    if (StringUtils.isNotBlank(ruleRepositoryKey) && StringUtils.isNotBlank(ruleKey)) {
+      ruleDebt.ruleKey = RuleKey.of(ruleRepositoryKey, ruleKey);
+    } else {
+      return null;
+    }
+    return processFunctionsOnRequirement(ruleDebt, properties);
+  }
+
+  private Property processProperty(SMInputCursor cursor) 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 {
+          Double valueDouble = NumberUtils.createDouble(s);
+          value = valueDouble.intValue();
+        } catch (NumberFormatException ex) {
+          LOG.error(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 RuleDebt processFunctionsOnRequirement(RuleDebt requirement, Properties properties) {
+    Property function = properties.function();
+    Property factor = properties.factor();
+    Property offset = properties.offset();
+
+    if (function != null) {
+      // Init with default values
+      requirement.factor = "0" + Duration.DAY;
+      requirement.offset = "0" + Duration.DAY;
+
+      String functionKey = function.getTextValue();
+      if ("linear_threshold".equals(functionKey)) {
+        function.setTextValue(RemediationFunction.LINEAR.name().toLowerCase());
+        offset.setValue(0);
+        offset.setTextValue(Duration.DAY);
+        LOG.warn(String.format("Linear with threshold function is no longer used, remediation function of '%s' is replaced by linear.", requirement.ruleKey));
+      } else if ("constant_resource".equals(functionKey)) {
+        LOG.warn(String.format("Constant/file function is no longer used, technical debt definitions on '%s' are ignored.", requirement.ruleKey));
+        return null;
+      }
+
+      requirement.function = RemediationFunction.valueOf(function.getTextValue().toUpperCase());
+      if (factor != null) {
+        requirement.factor = Integer.toString(factor.getValue());
+        requirement.factor += !Strings.isNullOrEmpty(factor.getTextValue()) ? factor.getTextValue() : Duration.DAY;
+      }
+      if (offset != null) {
+        requirement.offset = Integer.toString(offset.getValue());
+        requirement.offset += !Strings.isNullOrEmpty(offset.getTextValue()) ? offset.getTextValue() : Duration.DAY;
+      }
+      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 "mn".equals(textValue) ? Duration.MINUTE : textValue;
+    }
+  }
+
+  public static class RuleDebt {
+    private RuleKey ruleKey;
+    private String characteristicKey;
+    private RemediationFunction function;
+    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 RemediationFunction function() {
+      return function;
+    }
+
+    public RuleDebt setFunction(RemediationFunction function) {
+      this.function = function;
+      return this;
+    }
+
+    public String factor() {
+      return factor;
+    }
+
+    public RuleDebt setFactor(String factor) {
+      this.factor = factor;
+      return this;
+    }
+
+    public String offset() {
+      return offset;
+    }
+
+    public RuleDebt setOffset(String offset) {
+      this.offset = offset;
+      return this;
+    }
+  }
+
+}
index 4ebddca0627c0d926af6ee3a6a64e01020bd127b..d103004a595b341b92286d37adcb208a4c37819c 100644 (file)
@@ -107,8 +107,7 @@ public class TechnicalDebtModelRepository implements ServerExtension, Startable
    * @return the list of plugin keys
    */
   public Collection<String> getContributingPluginList() {
-    Collection<String> contributingPlugins = newArrayList(contributingPluginKeyToClassLoader.keySet());
-    return contributingPlugins;
+    return newArrayList(contributingPluginKeyToClassLoader.keySet());
   }
 
   /**
index c94f61d580b9e3ea5f02cb96af703ac6aa0ad536..c608784ba68eec0d26ded05d9e16b4797dfe3766 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.api.ServerComponent;
 import org.sonar.core.persistence.MyBatis;
 
 import javax.annotation.CheckForNull;
+
 import java.util.List;
 
 public class CharacteristicDao implements BatchComponent, ServerComponent {
@@ -41,14 +42,17 @@ public class CharacteristicDao implements BatchComponent, ServerComponent {
    */
   public List<CharacteristicDto> selectEnabledCharacteristics() {
     SqlSession session = mybatis.openSession();
-    CharacteristicMapper mapper = session.getMapper(CharacteristicMapper.class);
     try {
-      return mapper.selectEnabledCharacteristics();
+      return selectEnabledCharacteristics(session);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  public List<CharacteristicDto> selectEnabledCharacteristics(SqlSession session) {
+    return session.getMapper(CharacteristicMapper.class).selectEnabledCharacteristics();
+  }
+
   /**
    * @return only enabled root characteristics, order by order
    */
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest.java
new file mode 100644 (file)
index 0000000..6c5e26e
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.io.Resources;
+import org.junit.Test;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.utils.ValidationMessages;
+
+import java.io.IOException;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class CharacteristicsXMLImporterTest {
+
+  @Test
+  public void import_characteristics() {
+    String xml = getFileContent("import_characteristics.xml");
+
+    ValidationMessages messages = ValidationMessages.create();
+    DefaultTechnicalDebtModel debtModel = new CharacteristicsXMLImporter().importXML(xml, messages);
+
+    assertThat(debtModel.rootCharacteristics()).hasSize(2);
+    assertThat(debtModel.rootCharacteristics().get(0).key()).isEqualTo("PORTABILITY");
+    assertThat(debtModel.rootCharacteristics().get(1).key()).isEqualTo("MAINTAINABILITY");
+
+    DefaultCharacteristic portability = debtModel.characteristicByKey("PORTABILITY");
+    assertThat(portability.order()).isEqualTo(1);
+    assertThat(portability.children()).hasSize(2);
+    assertThat(portability.children().get(0).key()).isEqualTo("COMPILER_RELATED_PORTABILITY");
+    assertThat(debtModel.characteristicByKey("COMPILER_RELATED_PORTABILITY").parent().key()).isEqualTo("PORTABILITY");
+    assertThat(portability.children().get(1).key()).isEqualTo("HARDWARE_RELATED_PORTABILITY");
+    assertThat(debtModel.characteristicByKey("HARDWARE_RELATED_PORTABILITY").parent().key()).isEqualTo("PORTABILITY");
+
+    DefaultCharacteristic maintainability = debtModel.characteristicByKey("MAINTAINABILITY");
+    assertThat(maintainability.order()).isEqualTo(2);
+    assertThat(maintainability.children()).hasSize(1);
+    assertThat(maintainability.children().get(0).key()).isEqualTo("READABILITY");
+    assertThat(debtModel.characteristicByKey("READABILITY").parent().key()).isEqualTo("MAINTAINABILITY");
+  }
+
+  @Test
+  public void import_badly_formatted_xml() {
+    String xml = getFileContent("import_badly_formatted_xml.xml");
+
+    ValidationMessages messages = ValidationMessages.create();
+    DefaultTechnicalDebtModel debtModel = new CharacteristicsXMLImporter().importXML(xml, messages);
+
+    checkXmlCorrectlyImported(debtModel, messages);
+  }
+
+  private void checkXmlCorrectlyImported(DefaultTechnicalDebtModel sqale, 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");
+  }
+
+  private String getFileContent(String file) {
+    try {
+      return Resources.toString(Resources.getResource(CharacteristicsXMLImporterTest.class, "CharacteristicsXMLImporterTest/" + file), Charsets.UTF_8);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/DebtCharacteristicsSynchronizerTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/DebtCharacteristicsSynchronizerTest.java
new file mode 100644 (file)
index 0000000..657758b
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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 DebtCharacteristicsSynchronizerTest {
+
+  @Mock
+  MyBatis myBatis;
+
+  @Mock
+  SqlSession session;
+
+  @Mock
+  TechnicalDebtModelRepository technicalDebtModelRepository;
+
+  @Mock
+  CharacteristicDao dao;
+
+  @Mock
+  CharacteristicsXMLImporter xmlImporter;
+
+  Integer currentId = 1;
+
+  private DefaultTechnicalDebtModel defaultModel;
+
+  private DebtCharacteristicsSynchronizer 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))).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 DebtCharacteristicsSynchronizer(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());
+
+    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);
+  }
+
+  @Test
+  public void not_create_default_model_if_already_exists() throws Exception {
+    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("PORTABILITY");
+    new DefaultCharacteristic().setKey("COMPILER_RELATED_PORTABILITY").setParent(rootCharacteristic);
+    defaultModel.addRootCharacteristic(rootCharacteristic);
+
+    when(technicalDebtModelRepository.getContributingPluginList()).thenReturn(Collections.<String>emptyList());
+    when(dao.selectEnabledCharacteristics()).thenReturn(Lists.newArrayList(new CharacteristicDto()));
+
+    manager.synchronize(ValidationMessages.create());
+
+    verify(dao, never()).insert(any(CharacteristicDto.class), eq(session));
+  }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest.java
new file mode 100644 (file)
index 0000000..f040046
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.io.Resources;
+import org.junit.Test;
+import org.sonar.api.rule.RemediationFunction;
+import org.sonar.api.rule.RuleKey;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RuleDebtXMLImporterTest {
+
+  RuleDebtXMLImporter importer = new RuleDebtXMLImporter();
+
+  @Test
+  public void import_rules() {
+    String xml = getFileContent("import_rules.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(2);
+  }
+
+  @Test
+  public void import_linear() {
+    String xml = getFileContent("import_linear.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(1);
+
+    RuleDebtXMLImporter.RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
+    assertThat(ruleDebt.function()).isEqualTo(RemediationFunction.LINEAR);
+    assertThat(ruleDebt.factor()).isEqualTo("3h");
+    assertThat(ruleDebt.offset()).isEqualTo("0d");
+  }
+
+  @Test
+  public void import_linear_with_offset() {
+    String xml = getFileContent("import_linear_with_offset.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(1);
+
+    RuleDebtXMLImporter.RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.function()).isEqualTo(RemediationFunction.LINEAR_OFFSET);
+    assertThat(ruleDebt.factor()).isEqualTo("3h");
+    assertThat(ruleDebt.offset()).isEqualTo("1min");
+  }
+
+  @Test
+  public void import_constant_issue() {
+    String xml = getFileContent("import_constant_issue.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(1);
+
+    RuleDebtXMLImporter.RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.function()).isEqualTo(RemediationFunction.CONSTANT_ISSUE);
+    assertThat(ruleDebt.factor()).isEqualTo("0d");
+    assertThat(ruleDebt.offset()).isEqualTo("3d");
+  }
+
+  @Test
+  public void use_default_unit_when_no_unit() {
+    String xml = getFileContent("use_default_unit_when_no_unit.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(1);
+
+    RuleDebtXMLImporter.RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.function()).isEqualTo(RemediationFunction.LINEAR);
+    assertThat(ruleDebt.factor()).isEqualTo("3d");
+    assertThat(ruleDebt.offset()).isEqualTo("1d");
+  }
+
+  @Test
+  public void replace_mn_by_min() {
+    String xml = getFileContent("replace_mn_by_min.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(1);
+
+    RuleDebtXMLImporter.RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.function()).isEqualTo(RemediationFunction.LINEAR);
+    assertThat(ruleDebt.factor()).isEqualTo("3min");
+    assertThat(ruleDebt.offset()).isEqualTo("0d");
+  }
+
+  @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<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(1);
+
+    RuleDebtXMLImporter.RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.function()).isEqualTo(RemediationFunction.LINEAR);
+    assertThat(ruleDebt.factor()).isEqualTo("3h");
+    assertThat(ruleDebt.offset()).isEqualTo("0d");
+  }
+
+  @Test
+  public void ignore_deprecated_constant_per_file_function() {
+    String xml = getFileContent("ignore_deprecated_constant_per_file_function.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).isEmpty();
+  }
+
+  @Test
+  public void ignore_rule_on_root_characteristics() {
+    String xml = getFileContent("ignore_rule_on_root_characteristics.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).isEmpty();
+  }
+
+  @Test
+  public void import_badly_formatted_xml() {
+    String xml = getFileContent("import_badly_formatted_xml.xml");
+
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).hasSize(1);
+
+    RuleDebtXMLImporter.RuleDebt ruleDebt = results.get(0);
+    assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
+    assertThat(ruleDebt.function()).isEqualTo(RemediationFunction.LINEAR);
+    assertThat(ruleDebt.factor()).isEqualTo("3h");
+    assertThat(ruleDebt.offset()).isEqualTo("0d");
+  }
+
+  @Test
+  public void ignore_invalid_value() throws Exception {
+    String xml = getFileContent("ignore_invalid_value.xml");
+    List<RuleDebtXMLImporter.RuleDebt> results = importer.importXML(xml);
+    assertThat(results).isEmpty();
+  }
+
+  private String getFileContent(String file) {
+    try {
+      return Resources.toString(Resources.getResource(RuleDebtXMLImporterTest.class, "RuleDebtXMLImporterTest/" + file), Charsets.UTF_8);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest/import_badly_formatted_xml.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest/import_badly_formatted_xml.xml
new file mode 100644 (file)
index 0000000..3abb870
--- /dev/null
@@ -0,0 +1,43 @@
+<!--
+  ~ SonarQube, open source software quality management tool.
+  ~ Copyright (C) 2008-2013 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.
+  -->
+
+<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>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest/import_characteristics.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/CharacteristicsXMLImporterTest/import_characteristics.xml
new file mode 100644 (file)
index 0000000..bc7f756
--- /dev/null
@@ -0,0 +1,22 @@
+<sqale>
+  <chc>
+    <key>PORTABILITY</key>
+    <name>Portability</name>
+    <chc>
+      <key>COMPILER_RELATED_PORTABILITY</key>
+      <name>Compiler related portability</name>
+    </chc>
+    <chc>
+      <key>HARDWARE_RELATED_PORTABILITY</key>
+      <name>Hardware related portability</name>
+    </chc>
+  </chc>
+  <chc>
+    <key>MAINTAINABILITY</key>
+    <name>Maintainability</name>
+    <chc>
+      <key>READABILITY</key>
+      <name>Readability</name>
+    </chc>
+  </chc>
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/Import_linear_with_offset.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/Import_linear_with_offset.xml
new file mode 100644 (file)
index 0000000..d1af28f
--- /dev/null
@@ -0,0 +1,54 @@
+<!--
+  ~ SonarQube, open source software quality management tool.
+  ~ Copyright (C) 2008-2013 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.
+  -->
+
+<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.0</val>
+          <txt>h</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>linear_offset</txt>
+        </prop>
+        <prop>
+          <key>offset</key>
+          <val>1.0</val>
+          <txt>min</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/convert_deprecated_linear_with_threshold_function_by_linear_function.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/convert_deprecated_linear_with_threshold_function_by_linear_function.xml
new file mode 100644 (file)
index 0000000..9ebc69b
--- /dev/null
@@ -0,0 +1,36 @@
+<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>remediationFunction</key>
+          <!-- Should be replaced by linear -->
+          <txt>linear_threshold</txt>
+        </prop>
+        <prop>
+          <key>remediationFactor</key>
+          <val>3.0</val>
+          <txt>h</txt>
+        </prop>
+        <!-- Should be ignored -->
+        <prop>
+          <key>offset</key>
+          <val>1.0</val>
+          <txt>h</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_deprecated_constant_per_file_function.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_deprecated_constant_per_file_function.xml
new file mode 100644 (file)
index 0000000..4b8ae3f
--- /dev/null
@@ -0,0 +1,25 @@
+<sqale>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <key>MEMORY_EFFICIENCY</key>
+      <name>Memory use</name>
+      <!-- Should be ignored -->
+      <chc>
+        <rule-repo>checkstyle</rule-repo>
+        <rule-key>Regexp</rule-key>
+        <prop>
+          <key>remediationFactor</key>
+          <val>3.0</val>
+          <txt>h</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>constant_resource</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_invalid_value.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_invalid_value.xml
new file mode 100644 (file)
index 0000000..bb6bdbb
--- /dev/null
@@ -0,0 +1,28 @@
+<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>factor</key>
+          <val>abc</val>
+        </prop>
+        <prop>
+          <key>function</key>
+          <txt>linear</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_rule_on_root_characteristics.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/ignore_rule_on_root_characteristics.xml
new file mode 100644 (file)
index 0000000..bcf3ed8
--- /dev/null
@@ -0,0 +1,19 @@
+<sqale>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <rule-repo>checkstyle</rule-repo>
+      <rule-key>Regexp</rule-key>
+      <prop>
+        <key>remediationFactor</key>
+        <val>3.0</val>
+        <txt>h</txt>
+      </prop>
+      <prop>
+        <key>remediationFunction</key>
+        <txt>linear</txt>
+      </prop>
+    </chc>
+  </chc>
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_badly_formatted_xml.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_badly_formatted_xml.xml
new file mode 100644 (file)
index 0000000..6c7d153
--- /dev/null
@@ -0,0 +1,43 @@
+<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.0
+          </val>
+          <txt>h
+          </txt>
+        </prop>
+        <prop>
+          <key>remediationFunction
+          </key>
+          <txt>linear
+          </txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_constant_issue.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_constant_issue.xml
new file mode 100644 (file)
index 0000000..86b1f55
--- /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>offset</key>
+          <val>3.0</val>
+          <txt>d</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>constant_issue</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_linear.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_linear.xml
new file mode 100644 (file)
index 0000000..f641a51
--- /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.0</val>
+          <txt>h</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>linear</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_rules.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/import_rules.xml
new file mode 100644 (file)
index 0000000..d035d7b
--- /dev/null
@@ -0,0 +1,58 @@
+<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.0</val>
+          <txt>h</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>linear</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+  <chc>
+    <key>PORTABILITY</key>
+    <name>Portability</name>
+    <chc>
+      <key>COMPILER_RELATED_PORTABILITY</key>
+      <name>Compiler related portability</name>
+    </chc>
+    <chc>
+      <key>HARDWARE_RELATED_PORTABILITY</key>
+      <name>Hardware related portability</name>
+      <chc>
+        <rule-repo>checkstyle</rule-repo>
+        <rule-key>Regexp2</rule-key>
+        <prop>
+          <key>remediationFactor</key>
+          <val>3.0</val>
+          <txt>h</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>linear</txt>
+        </prop>
+        <prop>
+          <key>offset</key>
+          <val>1.0</val>
+          <txt>h</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/replace_mn_by_min.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/replace_mn_by_min.xml
new file mode 100644 (file)
index 0000000..3254fef
--- /dev/null
@@ -0,0 +1,49 @@
+<!--
+  ~ SonarQube, open source software quality management tool.
+  ~ Copyright (C) 2008-2013 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.
+  -->
+
+<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.0</val>
+          <txt>mn</txt>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>linear</txt>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/use_default_unit_when_no_unit.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/RuleDebtXMLImporterTest/use_default_unit_when_no_unit.xml
new file mode 100644 (file)
index 0000000..f05e512
--- /dev/null
@@ -0,0 +1,32 @@
+<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.0</val>
+        </prop>
+        <prop>
+          <key>remediationFunction</key>
+          <txt>linear</txt>
+        </prop>
+        <prop>
+          <key>offset</key>
+          <val>1.0</val>
+        </prop>
+      </chc>
+    </chc>
+  </chc>
+
+</sqale>
index 4a4f73cb465147e1e109b82833d244a22a771d39..d08ff9f9a6655c1ba6d5387a52dd6533612ba223 100644 (file)
@@ -63,10 +63,7 @@ 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.DefaultTechnicalDebtManager;
-import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
-import org.sonar.core.technicaldebt.TechnicalDebtModelSynchronizer;
-import org.sonar.core.technicaldebt.TechnicalDebtXMLImporter;
+import org.sonar.core.technicaldebt.*;
 import org.sonar.core.test.TestPlanPerspectiveLoader;
 import org.sonar.core.test.TestablePerspectiveLoader;
 import org.sonar.core.timemachine.Periods;
@@ -387,8 +384,11 @@ public final class Platform {
     // technical debt
     servicesContainer.addSingleton(DebtService.class);
     servicesContainer.addSingleton(TechnicalDebtModelSynchronizer.class);
+    servicesContainer.addSingleton(DebtCharacteristicsSynchronizer.class);
     servicesContainer.addSingleton(TechnicalDebtModelRepository.class);
     servicesContainer.addSingleton(TechnicalDebtXMLImporter.class);
+    servicesContainer.addSingleton(RuleDebtXMLImporter.class);
+    servicesContainer.addSingleton(CharacteristicsXMLImporter.class);
     servicesContainer.addSingleton(DefaultTechnicalDebtManager.class);
 
     // source
@@ -437,6 +437,7 @@ public final class Platform {
     startupContainer.addSingleton(RuleRegistration.class);
     startupContainer.addSingleton(RegisterNewProfiles.class);
     startupContainer.addSingleton(JdbcDriverDeployer.class);
+    startupContainer.addSingleton(RegisterDebtCharacteristicModel.class);
     startupContainer.addSingleton(RegisterTechnicalDebtModel.class);
     startupContainer.addSingleton(DeleteDeprecatedMeasures.class);
     startupContainer.addSingleton(GeneratePluginIndex.class);
index 650aae01ff03d3a47dbb497d635787d444ab6acf..f96f117a0347d490fb71bd92ea64f712e76412ba 100644 (file)
  */
 package org.sonar.server.rule;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.server.rule.RuleDefinitions;
-import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.api.rules.RuleParam;
 import org.sonar.api.rules.RuleRepository;
+import org.sonar.api.server.rule.RuleDefinitions;
+import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.check.Cardinality;
 import org.sonar.core.i18n.RuleI18nManager;
+import org.sonar.core.technicaldebt.RuleDebtXMLImporter;
+import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
 
 import javax.annotation.CheckForNull;
 
+import java.io.Reader;
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
 /**
  * Inject deprecated RuleRepository into RuleDefinitions for backward-compatibility.
  *
  * @since 4.2
  */
 public class DeprecatedRuleDefinitions implements RuleDefinitions {
+
   private final RuleI18nManager i18n;
   private final RuleRepository[] repositories;
 
-  public DeprecatedRuleDefinitions(RuleI18nManager i18n, RuleRepository[] repositories) {
+  private final TechnicalDebtModelRepository languageModelFinder;
+  private final RuleDebtXMLImporter importer;
+
+  public DeprecatedRuleDefinitions(RuleI18nManager i18n, RuleRepository[] repositories, TechnicalDebtModelRepository languageModelFinder, RuleDebtXMLImporter importer) {
     this.i18n = i18n;
     this.repositories = repositories;
+    this.languageModelFinder = languageModelFinder;
+    this.importer = importer;
   }
 
-  public DeprecatedRuleDefinitions(RuleI18nManager i18n) {
-    this(i18n, new RuleRepository[0]);
+  public DeprecatedRuleDefinitions(RuleI18nManager i18n, TechnicalDebtModelRepository languageModelFinder, RuleDebtXMLImporter 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<RuleDebtXMLImporter.RuleDebt> ruleDebts = loadRuleDebtList();
+
     for (RuleRepository repository : repositories) {
       // RuleRepository API does not handle difference between new and extended repositories,
       NewRepository newRepository;
@@ -74,11 +95,22 @@ public class DeprecatedRuleDefinitions implements RuleDefinitions {
           newParam.setDescription(paramDescription(repository.getKey(), rule.getKey(), param));
           newParam.setType(RuleParamType.parse(param.getType()));
         }
+        updateRuleDebtDefinitions(newRule, repository.getKey(), rule.getKey(), ruleDebts);
       }
       newRepository.done();
     }
   }
 
+  private void updateRuleDebtDefinitions(NewRule newRule, String repoKey, String ruleKey, List<RuleDebtXMLImporter.RuleDebt> ruleDebts){
+    RuleDebtXMLImporter.RuleDebt ruleDebt = findRequirement(ruleDebts, repoKey, ruleKey);
+    if (ruleDebt != null) {
+      newRule.setCharacteristicKey(ruleDebt.characteristicKey());
+      newRule.setRemediationFunction(ruleDebt.function());
+      newRule.setRemediationFactor(ruleDebt.factor());
+      newRule.setRemediationOffset(ruleDebt.offset());
+    }
+  }
+
   @CheckForNull
   private String ruleName(String repositoryKey, org.sonar.api.rules.Rule rule) {
     String name = i18n.getName(repositoryKey, rule.getKey());
@@ -105,4 +137,39 @@ public class DeprecatedRuleDefinitions implements RuleDefinitions {
     );
     return StringUtils.defaultIfBlank(desc, null);
   }
+
+  public List<RuleDebtXMLImporter.RuleDebt> loadRuleDebtList() {
+    List<RuleDebtXMLImporter.RuleDebt> ruleDebtList = newArrayList();
+    for (String pluginKey : getContributingPluginListWithoutSqale()) {
+      ruleDebtList.addAll(loadRuleDebtsFromXml(pluginKey));
+    }
+    return ruleDebtList;
+  }
+
+  public List<RuleDebtXMLImporter.RuleDebt> loadRuleDebtsFromXml(String pluginKey) {
+    Reader xmlFileReader = null;
+    try {
+      xmlFileReader = languageModelFinder.createReaderForXMLFile(pluginKey);
+      return importer.importXML(xmlFileReader);
+    } finally {
+      IOUtils.closeQuietly(xmlFileReader);
+    }
+  }
+
+  private Collection<String> getContributingPluginListWithoutSqale() {
+    Collection<String> pluginList = newArrayList(languageModelFinder.getContributingPluginList());
+    pluginList.remove(TechnicalDebtModelRepository.DEFAULT_MODEL);
+    return pluginList;
+  }
+
+  @CheckForNull
+  private RuleDebtXMLImporter.RuleDebt findRequirement(List<RuleDebtXMLImporter.RuleDebt> requirements, final String repoKey, final String ruleKey) {
+    return Iterables.find(requirements, new Predicate<RuleDebtXMLImporter.RuleDebt>() {
+      @Override
+      public boolean apply(RuleDebtXMLImporter.RuleDebt input) {
+        return input.ruleKey().equals(RuleKey.of(repoKey, ruleKey));
+      }
+    }, null);
+  }
+
 }
index 2ab48037f8e99ffc1d665204ea4fd9bbf6bd8d8b..852273d40096634e7158a968bf232733414f029e 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.rule;
 
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.collect.*;
 import org.apache.commons.lang.ObjectUtils;
 import org.apache.commons.lang.StringUtils;
@@ -37,7 +38,10 @@ import org.sonar.check.Cardinality;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.qualityprofile.db.ActiveRuleDao;
 import org.sonar.core.rule.*;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
 import org.sonar.server.qualityprofile.ProfilesManager;
+import org.sonar.server.startup.RegisterDebtCharacteristicModel;
 
 import javax.annotation.CheckForNull;
 
@@ -51,6 +55,7 @@ import static com.google.common.collect.Lists.newArrayList;
  * @since 4.2
  */
 public class RuleRegistration implements Startable {
+
   private static final Logger LOG = LoggerFactory.getLogger(RuleRegistration.class);
 
   private final RuleDefinitionsLoader defLoader;
@@ -62,11 +67,16 @@ public class RuleRegistration implements Startable {
   private final RuleTagDao ruleTagDao;
   private final RuleTagOperations ruleTagOperations;
   private final ActiveRuleDao activeRuleDao;
+  private final CharacteristicDao characteristicDao;
   private final System2 system = System2.INSTANCE;
 
+  /**
+   * @param registerTechnicalDebtModel used only to be started after init of the technical debt model
+   */
   public RuleRegistration(RuleDefinitionsLoader defLoader, ProfilesManager profilesManager,
                           RuleRegistry ruleRegistry, ESRuleTags esRuleTags, RuleTagOperations ruleTagOperations,
-                          MyBatis myBatis, RuleDao ruleDao, RuleTagDao ruleTagDao, ActiveRuleDao activeRuleDao) {
+                          MyBatis myBatis, RuleDao ruleDao, RuleTagDao ruleTagDao, ActiveRuleDao activeRuleDao, CharacteristicDao characteristicDao,
+                          RegisterDebtCharacteristicModel registerTechnicalDebtModel) {
     this.defLoader = defLoader;
     this.profilesManager = profilesManager;
     this.ruleRegistry = ruleRegistry;
@@ -76,6 +86,7 @@ public class RuleRegistration implements Startable {
     this.ruleDao = ruleDao;
     this.ruleTagDao = ruleTagDao;
     this.activeRuleDao = activeRuleDao;
+    this.characteristicDao = characteristicDao;
   }
 
   @Override
@@ -85,8 +96,9 @@ public class RuleRegistration implements Startable {
     try {
       RuleDefinitions.Context context = defLoader.load();
       Buffer buffer = new Buffer(system.now());
+      List<CharacteristicDto> characteristicDtos = characteristicDao.selectEnabledCharacteristics();
       selectRulesFromDb(buffer, sqlSession);
-      enableRuleDefinitions(context, buffer, sqlSession);
+      enableRuleDefinitions(context, buffer, characteristicDtos, sqlSession);
       List<RuleDto> removedRules = processRemainingDbRules(buffer, sqlSession);
       removeActiveRulesOnStillExistingRepositories(removedRules, context);
       index(buffer);
@@ -120,27 +132,27 @@ public class RuleRegistration implements Startable {
     }
   }
 
-  private void enableRuleDefinitions(RuleDefinitions.Context context, Buffer buffer, SqlSession sqlSession) {
+  private void enableRuleDefinitions(RuleDefinitions.Context context, Buffer buffer, List<CharacteristicDto> characteristicDtos, SqlSession sqlSession) {
     for (RuleDefinitions.Repository repoDef : context.repositories()) {
-      enableRepository(buffer, sqlSession, repoDef);
+      enableRepository(buffer, sqlSession, repoDef, characteristicDtos);
     }
     for (RuleDefinitions.ExtendedRepository extendedRepoDef : context.extendedRepositories()) {
       if (context.repository(extendedRepoDef.key()) == null) {
         LOG.warn(String.format("Extension is ignored, repository %s does not exist", extendedRepoDef.key()));
       } else {
-        enableRepository(buffer, sqlSession, extendedRepoDef);
+        enableRepository(buffer, sqlSession, extendedRepoDef, characteristicDtos);
       }
     }
   }
 
-  private void enableRepository(Buffer buffer, SqlSession sqlSession, RuleDefinitions.ExtendedRepository repoDef) {
+  private void enableRepository(Buffer buffer, SqlSession sqlSession, RuleDefinitions.ExtendedRepository repoDef, List<CharacteristicDto> characteristicDtos) {
     int count = 0;
     for (RuleDefinitions.Rule ruleDef : repoDef.rules()) {
       RuleDto dto = buffer.rule(RuleKey.of(ruleDef.repository().key(), ruleDef.key()));
       if (dto == null) {
-        dto = enableAndInsert(buffer, sqlSession, ruleDef);
+        dto = enableAndInsert(buffer, sqlSession, ruleDef, characteristicDtos);
       } else {
-        enableAndUpdate(buffer, sqlSession, ruleDef, dto);
+        enableAndUpdate(buffer, sqlSession, ruleDef, dto, characteristicDtos);
       }
       buffer.markProcessed(dto);
       count++;
@@ -151,8 +163,9 @@ public class RuleRegistration implements Startable {
     sqlSession.commit();
   }
 
-  private RuleDto enableAndInsert(Buffer buffer, SqlSession sqlSession, RuleDefinitions.Rule ruleDef) {
+  private RuleDto enableAndInsert(Buffer buffer, SqlSession sqlSession, RuleDefinitions.Rule ruleDef, List<CharacteristicDto> characteristicDtos) {
     RemediationFunction remediationFunction = ruleDef.remediationFunction();
+
     RuleDto ruleDto = new RuleDto()
       .setCardinality(ruleDef.template() ? Cardinality.MULTIPLE : Cardinality.SINGLE)
       .setConfigKey(ruleDef.internalKey())
@@ -164,12 +177,17 @@ public class RuleRegistration implements Startable {
       .setSeverity(ruleDef.severity())
       .setCreatedAt(buffer.now())
       .setUpdatedAt(buffer.now())
-      .setStatus(ruleDef.status().name())
-      // TODO set default characteristic id
-      .setDefaultRemediationFunction(remediationFunction != null ? remediationFunction.name() : null)
-      .setDefaultRemediationFactor(ruleDef.remediationFactor())
-      .setDefaultRemediationOffset(ruleDef.remediationOffset())
-      .setEffortToFixL10nKey(ruleDef.effortToFixL10nKey());
+      .setStatus(ruleDef.status().name());
+
+    CharacteristicDto characteristic = findCharacteristic(characteristicDtos, ruleDef);
+    if (characteristic != null) {
+      ruleDto.setDefaultCharacteristicId(characteristic.getId())
+        .setDefaultRemediationFunction(remediationFunction != null ? remediationFunction.name() : null)
+        .setDefaultRemediationFactor(ruleDef.remediationFactor())
+        .setDefaultRemediationOffset(ruleDef.remediationOffset())
+        .setEffortToFixL10nKey(ruleDef.effortToFixL10nKey());
+    }
+
     ruleDao.insert(ruleDto, sqlSession);
     buffer.add(ruleDto);
 
@@ -187,8 +205,8 @@ public class RuleRegistration implements Startable {
     return ruleDto;
   }
 
-  private void enableAndUpdate(Buffer buffer, SqlSession sqlSession, RuleDefinitions.Rule ruleDef, RuleDto dto) {
-    if (mergeRule(buffer, ruleDef, dto)) {
+  private void enableAndUpdate(Buffer buffer, SqlSession sqlSession, RuleDefinitions.Rule ruleDef, RuleDto dto, List<CharacteristicDto> characteristicDtos) {
+    if (mergeRule(buffer, ruleDef, dto, characteristicDtos)) {
       ruleDao.update(dto);
     }
     mergeParams(buffer, sqlSession, ruleDef, dto);
@@ -196,7 +214,7 @@ public class RuleRegistration implements Startable {
     buffer.markProcessed(dto);
   }
 
-  private boolean mergeRule(Buffer buffer, RuleDefinitions.Rule def, RuleDto dto) {
+  private boolean mergeRule(Buffer buffer, RuleDefinitions.Rule def, RuleDto dto, List<CharacteristicDto> characteristicDtos) {
     boolean changed = false;
     if (!StringUtils.equals(dto.getName(), def.name())) {
       dto.setName(def.name());
@@ -229,34 +247,42 @@ public class RuleRegistration implements Startable {
       dto.setLanguage(def.repository().language());
       changed = true;
     }
-    changed = mergeDebtRule(def, dto) || changed;
+    changed = mergeDebtDefinitions(def, dto, characteristicDtos) || changed;
     if (changed) {
       dto.setUpdatedAt(buffer.now());
     }
     return changed;
   }
 
-  private boolean mergeDebtRule(RuleDefinitions.Rule def, RuleDto dto){
+  private boolean mergeDebtDefinitions(RuleDefinitions.Rule def, RuleDto dto, List<CharacteristicDto> characteristicDtos) {
     boolean changed = false;
 
-    // TODO add characteristic id change verification
+    CharacteristicDto characteristic = findCharacteristic(characteristicDtos, def);
+    Integer characteristicId = characteristic != null ? characteristic.getId() : null;
+    RemediationFunction remediationFunction = characteristic != null ? def.remediationFunction() : null;
+    String remediationFactor = characteristic != null ? def.remediationFactor() : null;
+    String remediationOffset = characteristic != null ? def.remediationOffset() : null;
+    String effortToFixL10nKey = characteristic != null ? def.effortToFixL10nKey() : null;
 
-    RemediationFunction remediationFunction = def.remediationFunction();
+    if (!ObjectUtils.equals(dto.getDefaultCharacteristicId(), characteristicId)) {
+      dto.setDefaultCharacteristicId(characteristicId);
+      changed = true;
+    }
     String remediationFunctionString = remediationFunction != null ? remediationFunction.name() : null;
     if (!StringUtils.equals(dto.getDefaultRemediationFunction(), remediationFunctionString)) {
       dto.setDefaultRemediationFunction(remediationFunctionString);
       changed = true;
     }
-    if (!StringUtils.equals(dto.getDefaultRemediationFactor(), def.remediationFactor())) {
-      dto.setDefaultRemediationFactor(def.remediationFactor());
+    if (!StringUtils.equals(dto.getDefaultRemediationFactor(), remediationFactor)) {
+      dto.setDefaultRemediationFactor(remediationFactor);
       changed = true;
     }
-    if (!StringUtils.equals(dto.getDefaultRemediationOffset(), def.remediationOffset())) {
-      dto.setDefaultRemediationOffset(def.remediationOffset());
+    if (!StringUtils.equals(dto.getDefaultRemediationOffset(), remediationOffset)) {
+      dto.setDefaultRemediationOffset(remediationOffset);
       changed = true;
     }
-    if (!StringUtils.equals(dto.getEffortToFixL10nKey(), def.effortToFixL10nKey())) {
-      dto.setEffortToFixL10nKey(def.effortToFixL10nKey());
+    if (!StringUtils.equals(dto.getEffortToFixL10nKey(), effortToFixL10nKey)) {
+      dto.setEffortToFixL10nKey(effortToFixL10nKey);
       changed = true;
     }
     return changed;
@@ -508,4 +534,21 @@ public class RuleRegistration implements Startable {
       unprocessedRuleIds.remove(ruleDto.getId());
     }
   }
+
+  @CheckForNull
+  private CharacteristicDto findCharacteristic(List<CharacteristicDto> characteristicDtos, RuleDefinitions.Rule ruleDef) {
+    final String key = ruleDef.characteristicKey();
+    CharacteristicDto characteristicDto = Iterables.find(characteristicDtos, new Predicate<CharacteristicDto>() {
+      @Override
+      public boolean apply(CharacteristicDto input) {
+        // TODO remove check on null rule id when only characteristics without requirements will be returned
+        return input.getRuleId() == null && input.getKey().equals(key);
+      }
+    }, null);
+    // TODO check not root characteristic
+    if (characteristicDto == null) {
+      LOG.warn(String.format("Characteristic : '%s' has not been found, Technical debt definitions on rule '%s:%s' will be ignored", key, ruleDef.repository(), ruleDef.key()));
+    }
+    return characteristicDto;
+  }
 }
diff --git a/sonar-server/src/main/java/org/sonar/server/startup/RegisterDebtCharacteristicModel.java b/sonar-server/src/main/java/org/sonar/server/startup/RegisterDebtCharacteristicModel.java
new file mode 100644 (file)
index 0000000..5395740
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.startup;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.api.utils.ValidationMessages;
+import org.sonar.core.technicaldebt.DebtCharacteristicsSynchronizer;
+
+public class RegisterDebtCharacteristicModel {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(RegisterDebtCharacteristicModel.class);
+
+  private final DebtCharacteristicsSynchronizer manager;
+
+  public RegisterDebtCharacteristicModel(DebtCharacteristicsSynchronizer manager) {
+    this.manager = manager;
+  }
+
+  public void start() {
+    TimeProfiler profiler = new TimeProfiler(LOGGER).start("Register Debt Characteristics Model");
+    manager.synchronize(ValidationMessages.create());
+    profiler.stop();
+  }
+
+}
index 5937637d48db49e7ed8ad801fcc0e4ff75d71c7f..00fd9e91ece41d07fb21373275bedd456daa1e42 100644 (file)
 package org.sonar.server.rule;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.rule.RemediationFunction;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.server.rule.RuleDefinitions;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RulePriority;
 import org.sonar.api.rules.RuleRepository;
+import org.sonar.api.server.rule.RuleDefinitions;
 import org.sonar.core.i18n.RuleI18nManager;
+import org.sonar.core.technicaldebt.RuleDebtXMLImporter;
+import org.sonar.core.technicaldebt.TechnicalDebtModelRepository;
 
+import java.io.Reader;
 import java.util.Arrays;
 import java.util.List;
 
+import static com.google.common.collect.Lists.newArrayList;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+@RunWith(MockitoJUnitRunner.class)
 public class DeprecatedRuleDefinitionsTest {
 
+  @Mock
+  RuleI18nManager i18n;
+
+  @Mock
+  TechnicalDebtModelRepository debtModelRepository;
+
+  @Mock
+  RuleDebtXMLImporter importer;
+
   static class CheckstyleRules extends RuleRepository {
     public CheckstyleRules() {
       super("checkstyle", "java");
@@ -56,12 +76,24 @@ public class DeprecatedRuleDefinitionsTest {
     }
   }
 
+  static class UseBundles extends RuleRepository {
+    public UseBundles() {
+      super("checkstyle", "java");
+      setName("Checkstyle");
+    }
+
+    @Override
+    public List<Rule> createRules() {
+      Rule rule = Rule.create("checkstyle", "ConstantName");
+      rule.createParameter("format");
+      return Arrays.asList(rule);
+    }
+  }
+
   @Test
   public void wrap_deprecated_rule_repositories() throws Exception {
     RuleDefinitions.Context context = new RuleDefinitions.Context();
-    RuleI18nManager i18n = mock(RuleI18nManager.class);
-
-    new DeprecatedRuleDefinitions(i18n, new RuleRepository[]{new CheckstyleRules()}).define(context);
+    new DeprecatedRuleDefinitions(i18n, new RuleRepository[]{new CheckstyleRules()}, debtModelRepository, importer).define(context);
 
     assertThat(context.repositories()).hasSize(1);
     RuleDefinitions.Repository checkstyle = context.repository("checkstyle");
@@ -91,38 +123,21 @@ public class DeprecatedRuleDefinitionsTest {
   @Test
   public void emulate_the_day_deprecated_api_can_be_dropped() throws Exception {
     RuleDefinitions.Context context = new RuleDefinitions.Context();
-    RuleI18nManager i18n = mock(RuleI18nManager.class);
 
     // no more RuleRepository !
-    new DeprecatedRuleDefinitions(i18n);
+    new DeprecatedRuleDefinitions(i18n, debtModelRepository, importer);
 
     assertThat(context.repositories()).isEmpty();
   }
 
-
-  static class UseBundles extends RuleRepository {
-    public UseBundles() {
-      super("checkstyle", "java");
-      setName("Checkstyle");
-    }
-
-    @Override
-    public List<Rule> createRules() {
-      Rule rule = Rule.create("checkstyle", "ConstantName");
-      rule.createParameter("format");
-      return Arrays.asList(rule);
-    }
-  }
-
   @Test
   public void use_l10n_bundles() throws Exception {
     RuleDefinitions.Context context = new RuleDefinitions.Context();
-    RuleI18nManager i18n = mock(RuleI18nManager.class);
     when(i18n.getName("checkstyle", "ConstantName")).thenReturn("Constant Name");
     when(i18n.getDescription("checkstyle", "ConstantName")).thenReturn("Checks that constant names conform to the specified format");
     when(i18n.getParamDescription("checkstyle", "ConstantName", "format")).thenReturn("Regular expression");
 
-    new DeprecatedRuleDefinitions(i18n, new RuleRepository[]{new UseBundles()}).define(context);
+    new DeprecatedRuleDefinitions(i18n, new RuleRepository[]{new UseBundles()}, debtModelRepository, importer).define(context);
 
     RuleDefinitions.Repository checkstyle = context.repository("checkstyle");
     RuleDefinitions.Rule rule = checkstyle.rule("ConstantName");
@@ -134,4 +149,38 @@ public class DeprecatedRuleDefinitionsTest {
     assertThat(param.name()).isEqualTo("format");
     assertThat(param.description()).isEqualTo("Regular expression");
   }
+
+  @Test
+  public void define_rule_debt() throws Exception {
+    RuleDefinitions.Context context = new RuleDefinitions.Context();
+
+    List<RuleDebtXMLImporter.RuleDebt> ruleDebts = newArrayList(
+      new RuleDebtXMLImporter.RuleDebt()
+        .setCharacteristicKey("MEMORY_EFFICIENCY")
+        .setRuleKey(RuleKey.of("checkstyle", "ConstantName"))
+        .setFunction(RemediationFunction.LINEAR_OFFSET)
+        .setFactor("1d")
+        .setOffset("10min")
+    );
+
+    Reader javaModelReader = mock(Reader.class);
+    when(debtModelRepository.createReaderForXMLFile("java")).thenReturn(javaModelReader);
+    when(debtModelRepository.getContributingPluginList()).thenReturn(newArrayList("java"));
+    when(importer.importXML(eq(javaModelReader))).thenReturn(ruleDebts);
+
+    new DeprecatedRuleDefinitions(i18n, new RuleRepository[]{new CheckstyleRules()}, debtModelRepository, importer).define(context);
+
+    assertThat(context.repositories()).hasSize(1);
+    RuleDefinitions.Repository checkstyle = context.repository("checkstyle");
+    assertThat(checkstyle.rules()).hasSize(1);
+
+    RuleDefinitions.Rule rule = checkstyle.rule("ConstantName");
+    assertThat(rule).isNotNull();
+    assertThat(rule.key()).isEqualTo("ConstantName");
+    assertThat(rule.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(rule.remediationFunction()).isEqualTo(RemediationFunction.LINEAR_OFFSET);
+    assertThat(rule.remediationFactor()).isEqualTo("1d");
+    assertThat(rule.remediationOffset()).isEqualTo("10min");
+  }
+
 }
index 3853bff4914285e6e96c8be9ed37a1281c5f7cf1..7b75b8b8401b5a783664b28c9261fab9d8223e93 100644 (file)
@@ -31,7 +31,9 @@ import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.qualityprofile.db.ActiveRuleDao;
 import org.sonar.core.rule.RuleDao;
 import org.sonar.core.rule.RuleTagDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
 import org.sonar.server.qualityprofile.ProfilesManager;
+import org.sonar.server.startup.RegisterDebtCharacteristicModel;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.*;
@@ -53,6 +55,7 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
   RuleDao ruleDao;
   RuleTagDao ruleTagDao;
   ActiveRuleDao activeRuleDao;
+  CharacteristicDao characteristicDao;
 
   @Before
   public void before() {
@@ -61,8 +64,9 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
     ruleTagDao = new RuleTagDao(myBatis);
     activeRuleDao = new ActiveRuleDao(myBatis);
     ruleTagOperations = new RuleTagOperations(ruleTagDao, esRuleTags);
+    characteristicDao = new CharacteristicDao(myBatis);
     task = new RuleRegistration(new RuleDefinitionsLoader(mock(RuleRepositories.class), new RuleDefinitions[]{new FakeRepository()}),
-      profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao);
+      profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao, characteristicDao, mock(RegisterDebtCharacteristicModel.class));
   }
 
   @Test
@@ -161,6 +165,14 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
     checkTables("update_rule_parameters", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules", "rules_parameters");
   }
 
+  @Test
+  public void remove_rule_debt_definitions_if_characteristic_not_found() {
+    setupData("remove_rule_debt_definitions_if_characteristic_not_found");
+    task.start();
+
+    checkTables("remove_rule_debt_definitions_if_characteristic_not_found", EXCLUDED_COLUMN_NAMES, "rules");
+  }
+
   @Test
   public void not_disable_template_rules_if_parent_is_enabled() {
     setupData("not_disable_template_rules_if_parent_is_enabled");
@@ -189,7 +201,7 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
   @Test
   public void test_high_number_of_rules() {
     task = new RuleRegistration(new RuleDefinitionsLoader(mock(RuleRepositories.class), new RuleDefinitions[]{new BigRepository()}),
-      profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao);
+      profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao, characteristicDao, mock(RegisterDebtCharacteristicModel.class));
 
     setupData("shared");
     task.start();
@@ -204,7 +216,7 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
   public void insert_extended_repositories() {
     task = new RuleRegistration(new RuleDefinitionsLoader(mock(RuleRepositories.class), new RuleDefinitions[]{
       new FindbugsRepository(), new FbContribRepository()}),
-      profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao);
+      profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao, characteristicDao, mock(RegisterDebtCharacteristicModel.class));
 
     setupData("empty");
     task.start();
@@ -221,7 +233,7 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
         .setName("One")
         .setHtmlDescription("Description of One")
         .setSeverity(Severity.BLOCKER)
-        .setCharacteristicKey("COMPILER")
+        .setCharacteristicKey("MEMORY_EFFICIENCY")
         .setRemediationFunction(RemediationFunction.LINEAR_OFFSET)
         .setRemediationFactor("5d")
         .setRemediationOffset("10h")
diff --git a/sonar-server/src/test/java/org/sonar/server/startup/RegisterDebtCharacteristicModelTest.java b/sonar-server/src/test/java/org/sonar/server/startup/RegisterDebtCharacteristicModelTest.java
new file mode 100644 (file)
index 0000000..0dcd74a
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.startup;
+
+import org.junit.Test;
+import org.sonar.api.utils.ValidationMessages;
+import org.sonar.core.technicaldebt.DebtCharacteristicsSynchronizer;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class RegisterDebtCharacteristicModelTest {
+
+  @Test
+  public void create_model() throws Exception {
+    DebtCharacteristicsSynchronizer synchronizer = mock(DebtCharacteristicsSynchronizer.class);
+    RegisterDebtCharacteristicModel sqaleDefinition = new RegisterDebtCharacteristicModel(synchronizer);
+
+    sqaleDefinition.start();
+
+    verify(synchronizer, times(1)).synchronize(any(ValidationMessages.class));
+  }
+}
index 050c8b4377cc61451afd301d59fa1718a5f9e293..489e2108e8520bffb774a4b7c52377ce0dcd851a 100644 (file)
@@ -1,23 +1,3 @@
-<!--
-  ~ SonarQube, open source software quality management tool.
-  ~ Copyright (C) 2008-2013 SonarSource
-  ~ mailto:contact AT sonarsource DOT com
-  ~
-  ~ SonarQube is free software; you can redistribute it and/or
-  ~ modify it under the terms of the GNU Lesser General Public
-  ~ License as published by the Free Software Foundation; either
-  ~ version 3 of the License, or (at your option) any later version.
-  ~
-  ~ SonarQube is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-  ~ Lesser General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU Lesser General Public License
-  ~ along with this program; if not, write to the Free Software Foundation,
-  ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-  -->
-
 <dataset>
 
   <rules id="1" plugin_rule_key="deprecated-key" plugin_name="deprecated-repo" plugin_config_key="[null]" name="Deprecated" description="[null]"
@@ -30,7 +10,7 @@
 
   <rules id="2" plugin_rule_key="rule1" plugin_name="fake" plugin_config_key="config1" name="One" description="Description of One"
          status="READY" priority="4" cardinality="SINGLE" parent_id="[null]" language="java"
-         characteristic_id="[null]" default_characteristic_id="[null]"
+         characteristic_id="[null]" default_characteristic_id="2"
          remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
          remediation_factor="[null]" default_remediation_factor="5d"
          remediation_offset="[null]" default_remediation_offset="10h"
diff --git a/sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/remove_rule_debt_definitions_if_characteristic_not_found-result.xml b/sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/remove_rule_debt_definitions_if_characteristic_not_found-result.xml
new file mode 100644 (file)
index 0000000..52e53e0
--- /dev/null
@@ -0,0 +1,20 @@
+<dataset>
+
+  <!-- All debt parameters are reset as the characteristic MEMORY_EFFICIENCY do not exists -->
+  <rules id="1" plugin_rule_key="rule1" plugin_name="fake" plugin_config_key="config1" name="One" description="Description of One"
+         status="READY" priority="4" cardinality="SINGLE" parent_id="[null]" language="java"
+         characteristic_id="[null]" default_characteristic_id="[null]"
+         remediation_function="[null]" default_remediation_function="[null]"
+         remediation_factor="[null]" default_remediation_factor="[null]"
+         remediation_offset="[null]" default_remediation_offset="[null]"
+         effort_to_fix_l10n_key="[null]"/>
+
+  <rules id="2" plugin_rule_key="rule2" plugin_name="fake" plugin_config_key="[null]" name="Two" description="Description of Two"
+         status="DEPRECATED" priority="0" cardinality="SINGLE" parent_id="[null]" language="java"
+         characteristic_id="[null]" default_characteristic_id="[null]"
+         remediation_function="[null]" default_remediation_function="[null]"
+         remediation_factor="[null]" default_remediation_factor="[null]"
+         remediation_offset="[null]" default_remediation_offset="[null]"
+         effort_to_fix_l10n_key="[null]"/>
+
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/remove_rule_debt_definitions_if_characteristic_not_found.xml b/sonar-server/src/test/resources/org/sonar/server/rule/RuleRegistrationTest/remove_rule_debt_definitions_if_characteristic_not_found.xml
new file mode 100644 (file)
index 0000000..f926443
--- /dev/null
@@ -0,0 +1,17 @@
+<dataset>
+
+  <characteristics id="999" kee="NEW" name="New" root_id="1" characteristic_order="1" enabled="[true]"/>
+
+  <rules id="1" plugin_rule_key="rule1" plugin_name="fake" plugin_config_key="old_config_key" name="old name" description="old description"
+         status="READY" priority="2" cardinality="SINGLE" parent_id="[null]"
+         characteristic_id="[null]" default_characteristic_id="200"
+         remediation_function="[null]" default_remediation_function="LINEAR"
+         remediation_factor="[null]" default_remediation_factor="14min"
+         remediation_offset="[null]" default_remediation_offset="[null]"
+         effort_to_fix_l10n_key="[null]"/>
+
+  <rules id="2" plugin_rule_key="rule2" plugin_name="fake" plugin_config_key="old_config_key2" name="old name2" description="old description2"
+         status="READY" priority="1" cardinality="SINGLE" parent_id="[null]"/>
+
+
+</dataset>
index 84192cc4810a77f8738fc06d91eaa3c18aaaf1ff..17f652e7255831acf3a4260e8a6998d57c31a0a0 100644 (file)
@@ -1,5 +1,7 @@
 <dataset>
 
+  <characteristics id="2" kee="MEMORY_EFFICIENCY" name="Memory Efficiency" root_id="1" characteristic_order="1" enabled="[true]"/>
+
   <rules id="1" plugin_rule_key="deprecated-key" plugin_name="deprecated-repo" plugin_config_key="[null]" name="Deprecated" description="[null]"
                    status="READY" priority="4" cardinality="SINGLE" parent_id="[null]"/>
 
index 416104e82873540cf5f70df653cfac62aa300a7f..cfa18f014e5c6f8be56fe8fbb92b579a803b3378 100644 (file)
@@ -1,5 +1,7 @@
 <dataset>
 
+  <characteristics id="200" kee="MEMORY_EFFICIENCY" name="Memory Efficiency" root_id="1" characteristic_order="1" enabled="[true]"/>
+
   <rules id="1" plugin_rule_key="rule1" plugin_name="fake" plugin_config_key="old_config_key" name="old name" description="old description"
          status="READY" priority="2" cardinality="SINGLE" parent_id="[null]"
          characteristic_id="[null]" default_characteristic_id="200"
index b5a6352ede71e2227a5fd8fc3e44654a3bbc5991..0ea5e06aa1735b3cc78023908290032dc80d63ee 100644 (file)
@@ -1,25 +1,7 @@
-<!--
-  ~ SonarQube, open source software quality management tool.
-  ~ Copyright (C) 2008-2013 SonarSource
-  ~ mailto:contact AT sonarsource DOT com
-  ~
-  ~ SonarQube is free software; you can redistribute it and/or
-  ~ modify it under the terms of the GNU Lesser General Public
-  ~ License as published by the Free Software Foundation; either
-  ~ version 3 of the License, or (at your option) any later version.
-  ~
-  ~ SonarQube is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-  ~ Lesser General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU Lesser General Public License
-  ~ along with this program; if not, write to the Free Software Foundation,
-  ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-  -->
-
 <dataset>
 
+  <characteristics id="100" kee="MEMORY_EFFICIENCY" name="Memory Efficiency" root_id="1" characteristic_order="1" enabled="[true]"/>
+
   <rules id="1" plugin_rule_key="rule1" plugin_name="fake" plugin_config_key="[null]" name="Rule one" description="[null]"
          status="READY" priority="4" cardinality="MULTIPLE" parent_id="[null]" language="[null]"
          characteristic_id="[null]" default_characteristic_id="100"