]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4357 Move some classes to Sonar core
authorJulien Lancelot <julien.lancelot@gmail.com>
Tue, 8 Oct 2013 10:48:59 +0000 (12:48 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Tue, 8 Oct 2013 10:48:59 +0000 (12:48 +0200)
34 files changed:
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtMergeModel.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelFinder.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCache.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtMergeModelTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCacheTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/csharp-model.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/java-model.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_badly-formatted.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldLogWarningIfRuleNotFound.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldRejectXML_with_invalid_value.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/startup/RegisterTechnicalDebtModel.java
sonar-server/src/main/java/org/sonar/server/technicaldebt/RuleCache.java [deleted file]
sonar-server/src/main/java/org/sonar/server/technicaldebt/TechnicalDebtManager.java
sonar-server/src/main/java/org/sonar/server/technicaldebt/TechnicalDebtModel.java [deleted file]
sonar-server/src/main/java/org/sonar/server/technicaldebt/TechnicalDebtModelFinder.java [deleted file]
sonar-server/src/main/java/org/sonar/server/technicaldebt/XMLImporter.java [deleted file]
sonar-server/src/test/java/org/sonar/server/startup/RegisterTechnicalDebtModelTest.java
sonar-server/src/test/java/org/sonar/server/technicaldebt/RuleCacheTest.java [deleted file]
sonar-server/src/test/java/org/sonar/server/technicaldebt/TechnicalDebtManagerTest.java
sonar-server/src/test/java/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest.java [deleted file]
sonar-server/src/test/java/org/sonar/server/technicaldebt/TechnicalDebtModelTest.java [deleted file]
sonar-server/src/test/java/org/sonar/server/technicaldebt/XMLImporterTest.java [deleted file]
sonar-server/src/test/resources/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/csharp-model.xml [deleted file]
sonar-server/src/test/resources/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/java-model.xml [deleted file]
sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldImportXML.xml [deleted file]
sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldImportXML_badly-formatted.xml [deleted file]
sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldLogWarningIfRuleNotFound.xml [deleted file]
sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldRejectXML_with_invalid_value.xml [deleted file]

diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtMergeModel.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtMergeModel.java
new file mode 100644 (file)
index 0000000..b64f00e
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.CharacteristicProperty;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.utils.ValidationMessages;
+
+import java.util.List;
+
+public class TechnicalDebtMergeModel {
+
+  private Model model;
+
+  private List<Characteristic> defaultCharacteristics;
+
+  public TechnicalDebtMergeModel(Model model, List<Characteristic> defaultCharacteristics) {
+    this.model = model;
+    this.defaultCharacteristics = defaultCharacteristics;
+  }
+
+  public void mergeWith(Model with, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) {
+    for (Characteristic characteristic : with.getCharacteristics()) {
+      if (isRequirement(characteristic)) {
+        mergeRequirement(characteristic, messages, technicalDebtRuleCache);
+      } else {
+        mergeCharacteristic(characteristic, messages);
+      }
+    }
+  }
+
+  private Characteristic mergeCharacteristic(Characteristic characteristic, ValidationMessages messages) {
+    Characteristic existingCharacteristic = model.getCharacteristicByKey(characteristic.getKey());
+    if (existingCharacteristic == null) {
+      if (defaultCharacteristics.contains(characteristic)) {
+        existingCharacteristic = model.addCharacteristic(clone(characteristic));
+        if (!characteristic.getParents().isEmpty()) {
+          Characteristic parentTargetCharacteristic = mergeCharacteristic(characteristic.getParents().get(0), messages);
+          parentTargetCharacteristic.addChild(existingCharacteristic);
+        }
+      } else {
+        throw new IllegalArgumentException("The characteristic : " + characteristic.getKey() + " cannot be used as it's not available in default ones.");
+      }
+    }
+    return existingCharacteristic;
+  }
+
+  private void mergeRequirement(Characteristic requirement, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) {
+    Characteristic targetRequirement = model.getCharacteristicByRule(requirement.getRule());
+    if (targetRequirement == null && !requirement.getParents().isEmpty()) {
+      Rule rule = technicalDebtRuleCache.getRule(requirement.getRule().getRepositoryKey(), requirement.getRule().getKey());
+      if (rule == null) {
+        messages.addWarningText("The rule " + requirement.getRule() + " does not exist.");
+
+      } else {
+        Characteristic parent = mergeCharacteristic(requirement.getParents().get(0), messages);
+        requirement = model.addCharacteristic(clone(requirement));
+        requirement.setRule(rule);
+        parent.addChild(requirement);
+      }
+    }
+  }
+
+  private boolean isRequirement(Characteristic characteristic) {
+    return characteristic.hasRule();
+  }
+
+  private Characteristic clone(Characteristic c) {
+    Characteristic clone = Characteristic.create();
+    clone.setRule(c.getRule());
+    clone.setDescription(c.getDescription());
+    clone.setKey(c.getKey());
+    clone.setName(c.getName(), false);
+    for (CharacteristicProperty property : c.getProperties()) {
+      clone.setProperty(property.getKey(), property.getTextValue()).setValue(property.getValue());
+    }
+    return clone;
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelFinder.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModelFinder.java
new file mode 100644 (file)
index 0000000..c9234b8
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
+import org.picocontainer.Startable;
+import org.sonar.api.ServerExtension;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * <p>This class is used to find which technical debt model XML files exist in the Sonar instance.</p>
+ * <p>
+ * Those XML files are provided by language plugins that embed their own contribution to the definition of the Technical debt model.
+ * They must be located in the classpath of those language plugins, more specifically in the "com.sonar.sqale" package, and
+ * they must be named "<pluginKey>-model.xml".
+ * </p>
+ */
+public class TechnicalDebtModelFinder implements ServerExtension, Startable {
+
+  public static final String DEFAULT_MODEL = "technical-debt";
+
+  private static final String XML_FILE_SUFFIX = "-model.xml";
+  private static final String XML_FILE_PREFIX = "com/sonar/sqale/";
+
+  private String xmlFilePrefix;
+
+  private PluginRepository pluginRepository;
+  private Map<String, ClassLoader> contributingPluginKeyToClassLoader;
+
+  public TechnicalDebtModelFinder(PluginRepository pluginRepository) {
+    this.pluginRepository = pluginRepository;
+    this.xmlFilePrefix = XML_FILE_PREFIX;
+  }
+
+  @VisibleForTesting
+  TechnicalDebtModelFinder(PluginRepository pluginRepository, String xmlFilePrefix) {
+    this.pluginRepository = pluginRepository;
+    this.xmlFilePrefix = xmlFilePrefix;
+  }
+
+  @VisibleForTesting
+  TechnicalDebtModelFinder(Map<String, ClassLoader> contributingPluginKeyToClassLoader, String xmlFilePrefix) {
+    this.contributingPluginKeyToClassLoader = contributingPluginKeyToClassLoader;
+    this.xmlFilePrefix = xmlFilePrefix;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void start() {
+    findAvailableXMLFiles();
+  }
+
+  protected void findAvailableXMLFiles() {
+    if (contributingPluginKeyToClassLoader == null) {
+      contributingPluginKeyToClassLoader = Maps.newTreeMap();
+      for (PluginMetadata metadata : pluginRepository.getMetadata()) {
+        String pluginKey = metadata.getKey();
+        ClassLoader classLoader = pluginRepository.getPlugin(pluginKey).getClass().getClassLoader();
+        if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
+          contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
+        }
+      }
+      // Add default model
+      contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
+    }
+    contributingPluginKeyToClassLoader = Collections.unmodifiableMap(contributingPluginKeyToClassLoader);
+  }
+
+  @VisibleForTesting
+  String getXMLFilePath(String model) {
+    return xmlFilePrefix + model + XML_FILE_SUFFIX;
+  }
+
+  /**
+   * Returns the list of plugins that can contribute to the technical debt model (without the default model).
+   *
+   * @return the list of plugin keys
+   */
+  public Collection<String> getContributingPluginList() {
+    Collection<String> contributingPlugins = newArrayList(contributingPluginKeyToClassLoader.keySet());
+    contributingPlugins.remove(DEFAULT_MODEL);
+    return contributingPlugins;
+  }
+
+  /**
+   * Creates a new {@link java.io.Reader} for the XML file that contains the model contributed by the given plugin.
+   *
+   * @param pluginKey the key of the plugin that contributes the XML file
+   * @return the reader, that must be closed once its use is finished.
+   */
+  public Reader createReaderForXMLFile(String pluginKey) {
+    ClassLoader classLoader = contributingPluginKeyToClassLoader.get(pluginKey);
+    String xmlFilePath = getXMLFilePath(pluginKey);
+    return new InputStreamReader(classLoader.getResourceAsStream(xmlFilePath));
+  }
+
+  @VisibleForTesting
+  Map<String, ClassLoader> getContributingPluginKeyToClassLoader(){
+    return contributingPluginKeyToClassLoader;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+
+}
index 674da01ba0419f534b2393d462595260caa550f2..f597f4bace3a34dedf45bf954c32706e102e3dac 100644 (file)
@@ -84,7 +84,7 @@ public class TechnicalDebtRequirement {
     return offset;
   }
 
-  public org.sonar.api.qualitymodel.Characteristic toCharacteristic() {
+  public Characteristic toCharacteristic() {
     return characteristic;
   }
 }
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCache.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCache.java
new file mode 100644 (file)
index 0000000..86abd9c
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.Maps;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TechnicalDebtRuleCache {
+
+  private final RuleFinder ruleFinder;
+
+  private Map<String, Map<String, Rule>> cachedRules;
+
+  public TechnicalDebtRuleCache(RuleFinder ruleFinder) {
+    this.ruleFinder = ruleFinder;
+  }
+
+  @CheckForNull
+  public Rule getRule(String repository, String ruleKey) {
+    initRules();
+    return lookUpRuleInCache(repository, ruleKey);
+  }
+
+  public boolean exists(Rule rule) {
+    return getRule(rule.getRepositoryKey(), rule.getKey()) != null;
+  }
+
+  private void initRules(){
+    if(cachedRules == null) {
+      loadRules();
+    }
+  }
+
+  private void loadRules() {
+    cachedRules = Maps.newHashMap();
+    Collection<Rule> rules = ruleFinder.findAll(RuleQuery.create());
+    for (Rule rule : rules) {
+      if(!cachedRules.containsKey(rule.getRepositoryKey())) {
+        cachedRules.put(rule.getRepositoryKey(), new HashMap<String, Rule>());
+      }
+      Map<String, Rule> cachedRepository = cachedRules.get(rule.getRepositoryKey());
+      if(!cachedRepository.containsKey(rule.getKey())) {
+        cachedRepository.put(rule.getKey(), rule);
+      }
+    }
+  }
+
+  @CheckForNull
+  private Rule lookUpRuleInCache(String repository, String ruleKey) {
+    Map<String, Rule> cachedRepository = cachedRules.get(repository);
+    if(cachedRepository != null) {
+      return cachedRepository.get(ruleKey);
+    }
+    return null;
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporter.java
new file mode 100644 (file)
index 0000000..df81a5d
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * 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.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.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.utils.ValidationMessages;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.List;
+
+public class TechnicalDebtXMLImporter implements ServerExtension {
+
+  private static final Logger LOG = LoggerFactory.getLogger(TechnicalDebtXMLImporter.class);
+
+  private static final String CHARACTERISTIC = "chc";
+  private static final String CHARACTERISTIC_KEY = "key";
+  private static final String CHARACTERISTIC_NAME = "name";
+  private static final String CHARACTERISTIC_DESCRIPTION = "desc";
+  private static final String PROPERTY = "prop";
+  private static final String PROPERTY_KEY = "key";
+  private static final String PROPERTY_VALUE = "val";
+  private static final String PROPERTY_TEXT_VALUE = "txt";
+
+  public Model importXML(String xml, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) {
+    return importXML(new StringReader(xml), messages, technicalDebtRuleCache);
+  }
+
+  public Model importXML(Reader xml, ValidationMessages messages, TechnicalDebtRuleCache repositoryCache) {
+    Model model = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+    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, chcCursor, messages, repositoryCache);
+      }
+
+      cursor.getStreamReader().closeCompletely();
+
+    } catch (XMLStreamException e) {
+      LOG.error("XML is not valid", e);
+      messages.addErrorText("XML is not valid: " + e.getMessage());
+    }
+    return model;
+  }
+
+  private SMInputFactory initStax() {
+    XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
+    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
+    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
+    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
+    return new SMInputFactory(xmlFactory);
+  }
+
+  private Characteristic processCharacteristic(Model model, SMInputCursor chcCursor, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) throws XMLStreamException {
+    Characteristic characteristic = Characteristic.create();
+    SMInputCursor cursor = chcCursor.childElementCursor();
+
+    String ruleRepositoryKey = null, ruleKey = null;
+    List<Characteristic> children = Lists.newArrayList();
+    while (cursor.getNext() != null) {
+      String node = cursor.getLocalName();
+      if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
+        characteristic.setKey(cursor.collectDescendantText().trim());
+
+      } else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) {
+        characteristic.setName(cursor.collectDescendantText().trim(), false);
+
+      } else if (StringUtils.equals(node, CHARACTERISTIC_DESCRIPTION)) {
+        characteristic.setDescription(cursor.collectDescendantText().trim());
+
+      } else if (StringUtils.equals(node, PROPERTY)) {
+        processProperty(characteristic, cursor, messages);
+
+      } else if (StringUtils.equals(node, CHARACTERISTIC)) {
+        children.add(processCharacteristic(model, cursor, messages, technicalDebtRuleCache));
+
+      } else if (StringUtils.equals(node, "rule-repo")) {
+        ruleRepositoryKey = cursor.collectDescendantText().trim();
+
+      } else if (StringUtils.equals(node, "rule-key")) {
+        ruleKey = cursor.collectDescendantText().trim();
+      }
+    }
+    fillRule(characteristic, ruleRepositoryKey, ruleKey, messages, technicalDebtRuleCache);
+
+    if (StringUtils.isNotBlank(characteristic.getKey()) || characteristic.getRule() != null) {
+      addCharacteristicToModel(model, characteristic, children);
+      return characteristic;
+    }
+    return null;
+  }
+
+  private void fillRule(Characteristic characteristic, String ruleRepositoryKey, String ruleKey, ValidationMessages messages,
+                        TechnicalDebtRuleCache technicalDebtRuleCache) {
+    if (StringUtils.isNotBlank(ruleRepositoryKey) && StringUtils.isNotBlank(ruleKey)) {
+      Rule rule = technicalDebtRuleCache.getRule(ruleRepositoryKey, ruleKey);
+      if (rule != null) {
+        characteristic.setRule(rule);
+      } else {
+        messages.addWarningText("Rule not found: [repository=" + ruleRepositoryKey + ", key=" + ruleKey + "]");
+      }
+    }
+  }
+
+  private void addCharacteristicToModel(Model model, Characteristic characteristic, List<Characteristic> children) {
+    model.addCharacteristic(characteristic);
+    for (Characteristic child : children) {
+      if (child != null) {
+        model.addCharacteristic(child);
+        characteristic.addChild(child);
+      }
+    }
+  }
+
+  private void processProperty(Characteristic characteristic, SMInputCursor cursor, ValidationMessages messages) throws XMLStreamException {
+    SMInputCursor c = cursor.childElementCursor();
+    String key = null;
+    Double value = null;
+    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 {
+          value = NumberUtils.createDouble(s);
+        } catch (NumberFormatException ex) {
+          String message = String.format("Cannot import value '%s' for field %s - Expected a numeric value instead", s, key);
+          LOG.error(message, ex);
+          messages.addErrorText(message);
+        }
+      } else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) {
+        textValue = c.collectDescendantText().trim();
+      }
+    }
+    if (StringUtils.isNotBlank(key)) {
+      characteristic.setProperty(key, textValue).setValue(value);
+    }
+  }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtMergeModelTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtMergeModelTest.java
new file mode 100644 (file)
index 0000000..13f566e
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.junit.Before;
+import org.junit.Test;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.ValidationMessages;
+
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TechnicalDebtMergeModelTest {
+
+  private Model model;
+  private TechnicalDebtMergeModel technicalDebtMergeModel;
+  private List<Characteristic> defaultCharacteristics;
+
+  @Before
+  public void setUpModel() {
+    model = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+    defaultCharacteristics = newArrayList();
+    technicalDebtMergeModel = new TechnicalDebtMergeModel(model, defaultCharacteristics);
+  }
+
+  @Test
+  public void merge_with_empty_model() {
+    Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+    Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
+    Characteristic ramEfficiency = with.createCharacteristicByKey("ram-efficiency", "RAM Efficiency");
+    efficiency.addChild(ramEfficiency);
+    Characteristic usability = with.createCharacteristicByKey("usability", "Usability");
+
+    ValidationMessages messages = ValidationMessages.create();
+
+    defaultCharacteristics.addAll(newArrayList(efficiency, ramEfficiency, usability));
+    technicalDebtMergeModel.mergeWith(with, messages, mockRuleCache());
+
+    assertThat(model.getCharacteristics()).hasSize(3);
+    assertThat(model.getRootCharacteristics()).hasSize(2);
+    assertThat(model.getCharacteristicByKey("ram-efficiency").getDepth()).isEqualTo(Characteristic.ROOT_DEPTH + 1);
+    assertThat(messages.getErrors()).isEmpty();
+  }
+
+  @Test
+  public void not_update_existing_characteristics() {
+    model.createCharacteristicByKey("efficiency", "Efficiency");
+
+    Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+    with.createCharacteristicByKey("efficiency", "New efficiency");
+
+    technicalDebtMergeModel.mergeWith(with, ValidationMessages.create(), mockRuleCache());
+
+    assertThat(model.getCharacteristics()).hasSize(1);
+    assertThat(model.getRootCharacteristics()).hasSize(1);
+    assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
+  }
+
+  @Test
+  public void warn_on_missing_rule() {
+    Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+    Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
+    Rule fooRule = Rule.create("foo", "bar", "Bar");
+    Characteristic requirement = with.createCharacteristicByRule(fooRule);
+    efficiency.addChild(requirement);
+
+    ValidationMessages messages = ValidationMessages.create();
+
+    defaultCharacteristics.add(efficiency);
+    technicalDebtMergeModel.mergeWith(with, messages, mockRuleCache());
+
+    assertThat(model.getCharacteristics()).hasSize(1);
+    assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
+    assertThat(model.getCharacteristicByRule(fooRule)).isNull();
+    assertThat(messages.getWarnings()).hasSize(1);
+    assertThat(messages.getWarnings().get(0)).contains("foo"); // warning: the rule foo does not exist
+  }
+
+  @Test
+  public void fail_when_adding_characteristic_not_existing_in_default_characteristics() {
+    Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+    Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
+    // usability is not available in default characteristics
+    with.createCharacteristicByKey("usability", "Usability");
+
+    ValidationMessages messages = ValidationMessages.create();
+
+    defaultCharacteristics.add(efficiency);
+    try {
+      technicalDebtMergeModel.mergeWith(with, messages, mockRuleCache());
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class);
+    }
+    assertThat(model.getCharacteristics()).hasSize(1);
+  }
+
+  private TechnicalDebtRuleCache mockRuleCache() {
+    RuleFinder ruleFinder = mock(RuleFinder.class);
+    when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(newRegexpRule()));
+    return new TechnicalDebtRuleCache(ruleFinder);
+  }
+
+  private Rule newRegexpRule() {
+    return Rule.create("checkstyle", "regexp", "Regular expression");
+  }
+}
+
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest.java
new file mode 100644 (file)
index 0000000..16804ca
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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 com.google.common.collect.Maps;
+import com.google.common.io.Resources;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.SonarPlugin;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TechnicalDebtModelFinderTest {
+
+  private static final String TEST_XML_PREFIX_PATH = "org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/";
+
+  private TechnicalDebtModelFinder modelFinder;
+
+  @Test
+  public void test_component_initialization() throws Exception {
+    // we do have the "csharp-model.xml" file in src/test/resources
+    PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class);
+    when(csharpPluginMetadata.getKey()).thenReturn("csharp");
+
+    // but we don' have the "php-model.xml" one
+    PluginMetadata phpPluginMetadata = mock(PluginMetadata.class);
+    when(phpPluginMetadata.getKey()).thenReturn("php");
+
+    PluginRepository repository = mock(PluginRepository.class);
+    when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
+    FakePlugin fakePlugin = new FakePlugin();
+    when(repository.getPlugin(anyString())).thenReturn(fakePlugin);
+    modelFinder = new TechnicalDebtModelFinder(repository, TEST_XML_PREFIX_PATH);
+
+    // when
+    modelFinder.start();
+
+    // assert
+    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+    assertThat(contributingPluginList.size()).isEqualTo(1);
+    assertThat(contributingPluginList).containsOnly("csharp");
+  }
+
+  @Test
+  public void contributing_plugin_list() throws Exception {
+    initModel();
+    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+    assertThat(contributingPluginList.size()).isEqualTo(2);
+    assertThat(contributingPluginList).contains("csharp", "java");
+  }
+
+  @Test
+  public void get_content_for_xml_file() throws Exception {
+    initModel();
+    Reader xmlFileReader = null;
+    try {
+      xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
+      assertNotNull(xmlFileReader);
+      List<String> lines = IOUtils.readLines(xmlFileReader);
+      assertThat(lines.size()).isEqualTo(25);
+      assertThat(lines.get(0)).isEqualTo("<sqale>");
+    } catch (Exception e) {
+      fail("Should be able to read the XML file.");
+    } finally {
+      IOUtils.closeQuietly(xmlFileReader);
+    }
+  }
+
+  @Test
+  public void return_xml_file_path_for_plugin() throws Exception {
+    initModel();
+    assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
+  }
+
+  @Test
+  public void contain_default_model() throws Exception {
+    modelFinder = new TechnicalDebtModelFinder(mock(PluginRepository.class));
+    modelFinder.start();
+    assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
+  }
+
+  private void initModel() throws MalformedURLException {
+    Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
+    contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
+    contributingPluginKeyToClassLoader.put("java", newClassLoader());
+    modelFinder = new TechnicalDebtModelFinder(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
+  }
+
+  private ClassLoader newClassLoader() throws MalformedURLException {
+    ClassLoader loader = mock(ClassLoader.class);
+    when(loader.getResourceAsStream(anyString())).thenAnswer(new Answer<InputStream>() {
+      public InputStream answer(InvocationOnMock invocation) throws Throwable {
+        return new FileInputStream(Resources.getResource((String) invocation.getArguments()[0]).getPath());
+      }
+    });
+    return loader;
+  }
+
+  class FakePlugin extends SonarPlugin {
+    public List getExtensions() {
+      return null;
+    }
+  }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCacheTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtRuleCacheTest.java
new file mode 100644 (file)
index 0000000..bd13002
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.fest.assertions.Assertions;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+
+import java.util.Collections;
+
+public class TechnicalDebtRuleCacheTest {
+
+  @Test
+  public void lazy_load_rules_on_first_call() throws Exception {
+
+    RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
+    Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Collections.EMPTY_LIST);
+
+    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+    technicalDebtRuleCache.getRule("", "");
+    technicalDebtRuleCache.getRule("", "");
+
+    Mockito.verify(ruleFinder, Mockito.times(1)).findAll(Matchers.any(RuleQuery.class));
+  }
+
+  @Test
+  public void return_matching_rule() throws Exception {
+
+    Rule rule1 = Rule.create("repo1", "rule1");
+    Rule rule2 = Rule.create("repo2", "rule2");
+
+    RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
+    Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1, rule2));
+
+    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+    Rule actualRule1 = technicalDebtRuleCache.getRule("repo1", "rule1");
+    Rule actualRule2 = technicalDebtRuleCache.getRule("repo2", "rule2");
+
+    Assertions.assertThat(actualRule1).isEqualTo(rule1);
+    Assertions.assertThat(actualRule2).isEqualTo(rule2);
+  }
+
+  @Test
+  public void return_if_rule_exists() throws Exception {
+
+    Rule rule1 = Rule.create("repo1", "rule1");
+    Rule rule2 = Rule.create("repo2", "rule2");
+
+    RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
+    Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1));
+
+    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+
+    Assertions.assertThat(technicalDebtRuleCache.exists(rule1)).isTrue();
+    Assertions.assertThat(technicalDebtRuleCache.exists(rule2)).isFalse();
+  }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest.java
new file mode 100644 (file)
index 0000000..cac08a4
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.collect.Lists;
+import com.google.common.io.Resources;
+import org.fest.assertions.Assertions;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.ValidationMessages;
+
+import java.io.IOException;
+
+public class TechnicalDebtXMLImporterTest {
+
+  @Test
+  public void shouldImportXML() {
+    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+
+    String xml = getFileContent("shouldImportXML.xml");
+
+    ValidationMessages messages = ValidationMessages.create();
+    Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+    checkXmlCorrectlyImported(sqale, messages);
+  }
+
+  @Test
+  public void shouldBadlyFormattedImportXML() {
+    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+    String xml = getFileContent("shouldImportXML_badly-formatted.xml");
+
+    ValidationMessages messages = ValidationMessages.create();
+    Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+    checkXmlCorrectlyImported(sqale, messages);
+  }
+
+  @Test
+  public void shouldLogWarningIfRuleNotFound() {
+    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+    String xml = getFileContent("shouldLogWarningIfRuleNotFound.xml");
+    ValidationMessages messages = ValidationMessages.create();
+
+    Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+    Assertions.assertThat(messages.getWarnings()).hasSize(1);
+
+    // characteristics
+    Assertions.assertThat(sqale.getRootCharacteristics()).hasSize(1);
+    Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
+    Assertions.assertThat(efficiency.getChildren()).isEmpty();
+    Assertions.assertThat(messages.getWarnings().get(0)).contains("findbugs");
+  }
+
+  @Test
+  public void shouldNotifyOnUnexpectedValueTypeInXml() throws Exception {
+
+    TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+
+    String xml = getFileContent("shouldRejectXML_with_invalid_value.xml");
+    ValidationMessages messages = ValidationMessages.create();
+
+    new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+    Assertions.assertThat(messages.getErrors()).hasSize(1);
+    Assertions.assertThat(messages.getErrors().get(0)).isEqualTo("Cannot import value 'abc' for field factor - Expected a numeric value instead");
+  }
+
+  private TechnicalDebtRuleCache mockRuleCache() {
+    RuleFinder finder = Mockito.mock(RuleFinder.class);
+    Mockito.when(finder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(Rule.create("checkstyle", "Regexp", "Regular expression")));
+    return new TechnicalDebtRuleCache(finder);
+  }
+
+  private void checkXmlCorrectlyImported(Model sqale, ValidationMessages messages) {
+
+    Assertions.assertThat(messages.getErrors()).isEmpty();
+    Assertions.assertThat(sqale.getName()).isEqualTo(TechnicalDebtModel.MODEL_NAME);
+
+    // characteristics
+    Assertions.assertThat(sqale.getRootCharacteristics()).hasSize(2);
+    Assertions.assertThat(sqale.getCharacteristicByKey("USABILITY").getDescription()).isEqualTo("Estimate usability");
+    Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
+    Assertions.assertThat(efficiency.getName()).isEqualTo("Efficiency");
+
+    // sub-characteristics
+    Assertions.assertThat(efficiency.getChildren()).hasSize(1);
+    Characteristic requirement = efficiency.getChildren().get(0);
+    Assertions.assertThat(requirement.getRule().getRepositoryKey()).isEqualTo("checkstyle");
+    Assertions.assertThat(requirement.getRule().getKey()).isEqualTo("Regexp");
+    Assertions.assertThat(requirement.getPropertyTextValue("function", null)).isEqualTo("linear");
+    Assertions.assertThat(requirement.getPropertyValue("factor", null)).isEqualTo(3.2);
+  }
+
+  private String getFileContent(String file) {
+    try {
+      return Resources.toString(Resources.getResource(TechnicalDebtXMLImporterTest.class, "TechnicalDebtXMLImporterTest/" + file), Charsets.UTF_8);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/csharp-model.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/csharp-model.xml
new file mode 100644 (file)
index 0000000..e4569a2
--- /dev/null
@@ -0,0 +1,25 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+    <desc>Estimate usability</desc>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <rule-repo>gendarme</rule-repo>
+      <rule-key>EnsureLocalDisposalRule</rule-key>
+      <prop>
+        <key>remediationFactor</key>
+        <val>0.125</val>
+        <txt>d</txt>
+      </prop>
+      <prop>
+        <key>remediationFunction</key>
+        <txt>linear</txt>
+      </prop>
+    </chc>
+  </chc>
+
+</sqale>
\ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/java-model.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/java-model.xml
new file mode 100644 (file)
index 0000000..0b37f56
--- /dev/null
@@ -0,0 +1,25 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+    <desc>Estimate usability</desc>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+    <chc>
+      <rule-repo>squid-cobol</rule-repo>
+      <rule-key>CheckLoop</rule-key>
+      <prop>
+        <key>remediationFactor</key>
+        <val>0.125</val>
+        <txt>d</txt>
+      </prop>
+      <prop>
+        <key>remediationFunction</key>
+        <txt>linear</txt>
+      </prop>
+    </chc>
+  </chc>
+
+</sqale>
\ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML.xml
new file mode 100644 (file)
index 0000000..e405189
--- /dev/null
@@ -0,0 +1,25 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+    <desc>Estimate usability</desc>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+
+    <chc>
+      <rule-repo>checkstyle</rule-repo>
+      <rule-key>Regexp</rule-key>
+      <prop>
+        <key>factor</key>
+        <val>3.2</val>
+      </prop>
+      <prop>
+        <key>function</key>
+        <txt>linear</txt>
+      </prop>
+    </chc>
+  </chc>
+
+</sqale>
\ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_badly-formatted.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldImportXML_badly-formatted.xml
new file mode 100644 (file)
index 0000000..373959f
--- /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>
+      <rule-repo>checkstyle
+      </rule-repo>
+      <rule-key>Regexp
+      </rule-key>
+      <prop>
+        <key>factor
+        </key>
+        <val>3.2
+        </val>
+      </prop>
+      <prop>
+        <key>function
+        </key>
+        <txt>linear
+        </txt>
+      </prop>
+    </chc>
+  </chc>
+
+</sqale>
\ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldLogWarningIfRuleNotFound.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldLogWarningIfRuleNotFound.xml
new file mode 100644 (file)
index 0000000..bd459f6
--- /dev/null
@@ -0,0 +1,12 @@
+<sqale>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+
+    <chc>
+      <rule-repo>findbugs</rule-repo>
+      <rule-key>Foo</rule-key>
+    </chc>
+  </chc>
+
+</sqale>
\ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldRejectXML_with_invalid_value.xml b/sonar-core/src/test/resources/org/sonar/core/technicaldebt/TechnicalDebtXMLImporterTest/shouldRejectXML_with_invalid_value.xml
new file mode 100644 (file)
index 0000000..638f00e
--- /dev/null
@@ -0,0 +1,25 @@
+<sqale>
+  <chc>
+    <key>USABILITY</key>
+    <name>Usability</name>
+    <desc>Estimate usability</desc>
+  </chc>
+  <chc>
+    <key>EFFICIENCY</key>
+    <name>Efficiency</name>
+
+    <chc>
+      <rule-repo>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>
+
+</sqale>
index ee5e32199266d6c09c343467482b0fc989c1e758..f5e0e944a4449d50a6aae3028634f45250564f40 100644 (file)
@@ -59,6 +59,8 @@ import org.sonar.core.resource.DefaultResourcePermissions;
 import org.sonar.core.rule.DefaultRuleFinder;
 import org.sonar.core.source.HtmlSourceDecorator;
 import org.sonar.core.technicaldebt.TechnicalDebtConverter;
+import org.sonar.core.technicaldebt.TechnicalDebtModelFinder;
+import org.sonar.core.technicaldebt.TechnicalDebtXMLImporter;
 import org.sonar.core.test.TestPlanPerspectiveLoader;
 import org.sonar.core.test.TestablePerspectiveLoader;
 import org.sonar.core.timemachine.Periods;
@@ -93,8 +95,6 @@ import org.sonar.server.rules.RulesConsole;
 import org.sonar.server.startup.*;
 import org.sonar.server.technicaldebt.RubyTechnicalDebtService;
 import org.sonar.server.technicaldebt.TechnicalDebtManager;
-import org.sonar.server.technicaldebt.TechnicalDebtModelFinder;
-import org.sonar.server.technicaldebt.XMLImporter;
 import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.text.RubyTextService;
 import org.sonar.server.ui.*;
@@ -302,7 +302,7 @@ public final class Platform {
     // technical debt
     servicesContainer.addSingleton(TechnicalDebtManager.class);
     servicesContainer.addSingleton(TechnicalDebtModelFinder.class);
-    servicesContainer.addSingleton(XMLImporter.class);
+    servicesContainer.addSingleton(TechnicalDebtXMLImporter.class);
     servicesContainer.addSingleton(TechnicalDebtConverter.class);
     servicesContainer.addSingleton(RubyTechnicalDebtService.class);
 
index bf9dfb9c27deb11fed9696dd5e57d67e5984907d..06e8636ddd9ec2f7821b38c9144f9f5a6efac9b4 100644 (file)
@@ -24,15 +24,13 @@ import org.slf4j.LoggerFactory;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.utils.TimeProfiler;
 import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.technicaldebt.RuleCache;
+import org.sonar.core.technicaldebt.TechnicalDebtRuleCache;
 import org.sonar.server.technicaldebt.TechnicalDebtManager;
 
 public final class RegisterTechnicalDebtModel {
 
   private static final Logger LOGGER = LoggerFactory.getLogger(RegisterTechnicalDebtModel.class);
 
-  public static final String TECHNICAL_DEBT_MODEL = "TECHNICAL_DEBT";
-
   private final TechnicalDebtManager technicalDebtManager;
   private final RuleFinder ruleFinder;
 
@@ -46,8 +44,8 @@ public final class RegisterTechnicalDebtModel {
 
   public void start() {
     TimeProfiler profiler = new TimeProfiler(LOGGER).start("Register Technical Debt Model");
-    RuleCache ruleCache = new RuleCache(ruleFinder);
-    technicalDebtManager.init(ValidationMessages.create(), ruleCache);
+    TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+    technicalDebtManager.init(ValidationMessages.create(), technicalDebtRuleCache);
     profiler.stop();
   }
 
diff --git a/sonar-server/src/main/java/org/sonar/server/technicaldebt/RuleCache.java b/sonar-server/src/main/java/org/sonar/server/technicaldebt/RuleCache.java
deleted file mode 100644 (file)
index 4858fea..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import com.google.common.collect.Maps;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-
-import javax.annotation.CheckForNull;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-public class RuleCache {
-
-  private final RuleFinder ruleFinder;
-
-  private Map<String, Map<String, Rule>> cachedRules;
-
-  public RuleCache(RuleFinder ruleFinder) {
-    this.ruleFinder = ruleFinder;
-  }
-
-  @CheckForNull
-  public Rule getRule(String repository, String ruleKey) {
-    initRules();
-    return lookUpRuleInCache(repository, ruleKey);
-  }
-
-  public boolean exists(Rule rule) {
-    return getRule(rule.getRepositoryKey(), rule.getKey()) != null;
-  }
-
-  private void initRules(){
-    if(cachedRules == null) {
-      loadRules();
-    }
-  }
-
-  private void loadRules() {
-    cachedRules = Maps.newHashMap();
-    Collection<Rule> rules = ruleFinder.findAll(RuleQuery.create());
-    for (Rule rule : rules) {
-      if(!cachedRules.containsKey(rule.getRepositoryKey())) {
-        cachedRules.put(rule.getRepositoryKey(), new HashMap<String, Rule>());
-      }
-      Map<String, Rule> cachedRepository = cachedRules.get(rule.getRepositoryKey());
-      if(!cachedRepository.containsKey(rule.getKey())) {
-        cachedRepository.put(rule.getKey(), rule);
-      }
-    }
-  }
-
-  @CheckForNull
-  private Rule lookUpRuleInCache(String repository, String ruleKey) {
-    Map<String, Rule> cachedRepository = cachedRules.get(repository);
-    if(cachedRepository != null) {
-      return cachedRepository.get(ruleKey);
-    }
-    return null;
-  }
-}
index 3d16526965b90628d5e23e1233b4e9a09bc0a3ec..1d9962c635eec97d41e1c77ad88496ef09d6a3ff 100644 (file)
@@ -28,8 +28,8 @@ import org.sonar.api.qualitymodel.Characteristic;
 import org.sonar.api.qualitymodel.Model;
 import org.sonar.api.qualitymodel.ModelFinder;
 import org.sonar.api.utils.ValidationMessages;
+import org.sonar.core.technicaldebt.*;
 import org.sonar.jpa.session.DatabaseSessionFactory;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
 
 import java.io.Reader;
 import java.util.Collection;
@@ -47,17 +47,17 @@ public class TechnicalDebtManager implements ServerExtension {
   private DatabaseSessionFactory sessionFactory;
   private ModelFinder modelFinder;
   private TechnicalDebtModelFinder languageModelFinder;
-  private XMLImporter importer;
+  private TechnicalDebtXMLImporter importer;
 
   public TechnicalDebtManager(DatabaseSessionFactory sessionFactory, ModelFinder modelFinder,
-                              TechnicalDebtModelFinder languageModelFinder, XMLImporter importer) {
+                              TechnicalDebtModelFinder languageModelFinder, TechnicalDebtXMLImporter importer) {
     this.sessionFactory = sessionFactory;
     this.modelFinder = modelFinder;
     this.languageModelFinder = languageModelFinder;
     this.importer = importer;
   }
 
-  public Model init(ValidationMessages messages, RuleCache rulesCache) {
+  public Model init(ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
     DatabaseSession session = sessionFactory.getSession();
 
     disableRequirementsOnRemovedRules(rulesCache);
@@ -71,31 +71,31 @@ public class TechnicalDebtManager implements ServerExtension {
     return model;
   }
 
-  private Model loadOrCreateModelFromDb(Model defaultModel, ValidationMessages messages, RuleCache rulesCache) {
-    Model model = modelFinder.findByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
+  private Model loadOrCreateModelFromDb(Model defaultModel, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
+    Model model = modelFinder.findByName(TechnicalDebtModel.MODEL_NAME);
     if (model == null) {
-      model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
+      model = Model.createByName(TechnicalDebtModel.MODEL_NAME);
       merge(defaultModel, model, defaultModel, messages, rulesCache);
     }
     return model;
   }
 
-  private void merge(Model pluginModel, Model existingModel, Model defaultModel, ValidationMessages messages, RuleCache rulesCache) {
+  private void merge(Model pluginModel, Model existingModel, Model defaultModel, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
     messages.log(LOG);
     if (!messages.hasErrors()) {
-      new TechnicalDebtModel(existingModel, defaultModel.getCharacteristics()).mergeWith(pluginModel, messages, rulesCache);
+      new TechnicalDebtMergeModel(existingModel, defaultModel.getCharacteristics()).mergeWith(pluginModel, messages, rulesCache);
       messages.log(LOG);
     }
   }
 
-  private void loadRequirementsFromPlugins(Model existingModel, Model defaultModel, ValidationMessages messages, RuleCache rulesCache) {
+  private void loadRequirementsFromPlugins(Model existingModel, Model defaultModel, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
     for (String pluginKey : getContributingPluginListWithoutSqale()) {
       Model pluginModel = loadModelFromXml(pluginKey, messages, rulesCache);
       merge(pluginModel, existingModel, defaultModel, messages, rulesCache);
     }
   }
 
-  private Model loadModelFromXml(String pluginKey, ValidationMessages messages, RuleCache rulesCache) {
+  private Model loadModelFromXml(String pluginKey, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
     Reader xmlFileReader = null;
     try {
       xmlFileReader = languageModelFinder.createReaderForXMLFile(pluginKey);
@@ -108,8 +108,8 @@ public class TechnicalDebtManager implements ServerExtension {
   /**
    * Disable requirements linked on removed rules
    */
-  private void disableRequirementsOnRemovedRules(RuleCache rulesCache) {
-    Model existingModel = modelFinder.findByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
+  private void disableRequirementsOnRemovedRules(TechnicalDebtRuleCache rulesCache) {
+    Model existingModel = modelFinder.findByName(TechnicalDebtModel.MODEL_NAME);
     if (existingModel != null) {
       for (Characteristic requirement : existingModel.getCharacteristicsByDepth(REQUIREMENT_LEVEL)) {
         if (!rulesCache.exists(requirement.getRule())) {
diff --git a/sonar-server/src/main/java/org/sonar/server/technicaldebt/TechnicalDebtModel.java b/sonar-server/src/main/java/org/sonar/server/technicaldebt/TechnicalDebtModel.java
deleted file mode 100644 (file)
index d7342d6..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import org.sonar.api.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.CharacteristicProperty;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.utils.ValidationMessages;
-
-import java.util.List;
-
-public class TechnicalDebtModel {
-
-  private Model model;
-
-  private List<Characteristic> defaultCharacteristics;
-
-  public TechnicalDebtModel(Model model, List<Characteristic> defaultCharacteristics) {
-    this.model = model;
-    this.defaultCharacteristics = defaultCharacteristics;
-  }
-
-  public void mergeWith(Model with, ValidationMessages messages, RuleCache ruleCache) {
-    for (Characteristic characteristic : with.getCharacteristics()) {
-      if (isRequirement(characteristic)) {
-        mergeRequirement(characteristic, messages, ruleCache);
-      } else {
-        mergeCharacteristic(characteristic, messages);
-      }
-    }
-  }
-
-  private Characteristic mergeCharacteristic(Characteristic characteristic, ValidationMessages messages) {
-    Characteristic existingCharacteristic = model.getCharacteristicByKey(characteristic.getKey());
-    if (existingCharacteristic == null) {
-      if (defaultCharacteristics.contains(characteristic)) {
-        existingCharacteristic = model.addCharacteristic(clone(characteristic));
-        if (!characteristic.getParents().isEmpty()) {
-          Characteristic parentTargetCharacteristic = mergeCharacteristic(characteristic.getParents().get(0), messages);
-          parentTargetCharacteristic.addChild(existingCharacteristic);
-        }
-      } else {
-        throw new IllegalArgumentException("The characteristic : " + characteristic.getKey() + " cannot be used as it's not available in default ones.");
-      }
-    }
-    return existingCharacteristic;
-  }
-
-  private void mergeRequirement(Characteristic requirement, ValidationMessages messages, RuleCache ruleCache) {
-    Characteristic targetRequirement = model.getCharacteristicByRule(requirement.getRule());
-    if (targetRequirement == null && !requirement.getParents().isEmpty()) {
-      Rule rule = ruleCache.getRule(requirement.getRule().getRepositoryKey(), requirement.getRule().getKey());
-      if (rule == null) {
-        messages.addWarningText("The rule " + requirement.getRule() + " does not exist.");
-
-      } else {
-        Characteristic parent = mergeCharacteristic(requirement.getParents().get(0), messages);
-        requirement = model.addCharacteristic(clone(requirement));
-        requirement.setRule(rule);
-        parent.addChild(requirement);
-      }
-    }
-  }
-
-  private boolean isRequirement(Characteristic characteristic) {
-    return characteristic.hasRule();
-  }
-
-  private Characteristic clone(Characteristic c) {
-    Characteristic clone = Characteristic.create();
-    clone.setRule(c.getRule());
-    clone.setDescription(c.getDescription());
-    clone.setKey(c.getKey());
-    clone.setName(c.getName(), false);
-    for (CharacteristicProperty property : c.getProperties()) {
-      clone.setProperty(property.getKey(), property.getTextValue()).setValue(property.getValue());
-    }
-    return clone;
-  }
-}
diff --git a/sonar-server/src/main/java/org/sonar/server/technicaldebt/TechnicalDebtModelFinder.java b/sonar-server/src/main/java/org/sonar/server/technicaldebt/TechnicalDebtModelFinder.java
deleted file mode 100644 (file)
index a90a38a..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Maps;
-import org.picocontainer.Startable;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-/**
- * <p>This class is used to find which technical debt model XML files exist in the Sonar instance.</p>
- * <p>
- * Those XML files are provided by language plugins that embed their own contribution to the definition of the Technical debt model.
- * They must be located in the classpath of those language plugins, more specifically in the "com.sonar.sqale" package, and
- * they must be named "<pluginKey>-model.xml".
- * </p>
- */
-public class TechnicalDebtModelFinder implements ServerExtension, Startable {
-
-  public static final String DEFAULT_MODEL = "technical-debt";
-
-  private static final String XML_FILE_SUFFIX = "-model.xml";
-  private static final String XML_FILE_PREFIX = "com/sonar/sqale/";
-
-  private String xmlFilePrefix;
-
-  private PluginRepository pluginRepository;
-  private Map<String, ClassLoader> contributingPluginKeyToClassLoader;
-
-  public TechnicalDebtModelFinder(PluginRepository pluginRepository) {
-    this.pluginRepository = pluginRepository;
-    this.xmlFilePrefix = XML_FILE_PREFIX;
-  }
-
-  @VisibleForTesting
-  TechnicalDebtModelFinder(PluginRepository pluginRepository, String xmlFilePrefix) {
-    this.pluginRepository = pluginRepository;
-    this.xmlFilePrefix = xmlFilePrefix;
-  }
-
-  @VisibleForTesting
-  TechnicalDebtModelFinder(Map<String, ClassLoader> contributingPluginKeyToClassLoader, String xmlFilePrefix) {
-    this.contributingPluginKeyToClassLoader = contributingPluginKeyToClassLoader;
-    this.xmlFilePrefix = xmlFilePrefix;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void start() {
-    findAvailableXMLFiles();
-  }
-
-  protected void findAvailableXMLFiles() {
-    if (contributingPluginKeyToClassLoader == null) {
-      contributingPluginKeyToClassLoader = Maps.newTreeMap();
-      for (PluginMetadata metadata : pluginRepository.getMetadata()) {
-        String pluginKey = metadata.getKey();
-        ClassLoader classLoader = pluginRepository.getPlugin(pluginKey).getClass().getClassLoader();
-        if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
-          contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
-        }
-      }
-      // Add default model
-      contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
-    }
-    contributingPluginKeyToClassLoader = Collections.unmodifiableMap(contributingPluginKeyToClassLoader);
-  }
-
-  @VisibleForTesting
-  String getXMLFilePath(String model) {
-    return xmlFilePrefix + model + XML_FILE_SUFFIX;
-  }
-
-  /**
-   * Returns the list of plugins that can contribute to the technical debt model (without the default model).
-   *
-   * @return the list of plugin keys
-   */
-  public Collection<String> getContributingPluginList() {
-    Collection<String> contributingPlugins = newArrayList(contributingPluginKeyToClassLoader.keySet());
-    contributingPlugins.remove(DEFAULT_MODEL);
-    return contributingPlugins;
-  }
-
-  /**
-   * Creates a new {@link java.io.Reader} for the XML file that contains the model contributed by the given plugin.
-   *
-   * @param pluginKey the key of the plugin that contributes the XML file
-   * @return the reader, that must be closed once its use is finished.
-   */
-  public Reader createReaderForXMLFile(String pluginKey) {
-    ClassLoader classLoader = contributingPluginKeyToClassLoader.get(pluginKey);
-    String xmlFilePath = getXMLFilePath(pluginKey);
-    return new InputStreamReader(classLoader.getResourceAsStream(xmlFilePath));
-  }
-
-  @VisibleForTesting
-  Map<String, ClassLoader> getContributingPluginKeyToClassLoader(){
-    return contributingPluginKeyToClassLoader;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void stop() {
-    // Nothing to do
-  }
-
-}
diff --git a/sonar-server/src/main/java/org/sonar/server/technicaldebt/XMLImporter.java b/sonar-server/src/main/java/org/sonar/server/technicaldebt/XMLImporter.java
deleted file mode 100644 (file)
index 4cf63de..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import com.google.common.collect.Lists;
-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.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
-
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.List;
-
-public class XMLImporter implements ServerExtension {
-
-  private static final Logger LOG = LoggerFactory.getLogger(XMLImporter.class);
-
-  private static final String CHARACTERISTIC = "chc";
-  private static final String CHARACTERISTIC_KEY = "key";
-  private static final String CHARACTERISTIC_NAME = "name";
-  private static final String CHARACTERISTIC_DESCRIPTION = "desc";
-  private static final String PROPERTY = "prop";
-  private static final String PROPERTY_KEY = "key";
-  private static final String PROPERTY_VALUE = "val";
-  private static final String PROPERTY_TEXT_VALUE = "txt";
-
-  public Model importXML(String xml, ValidationMessages messages, RuleCache ruleCache) {
-    return importXML(new StringReader(xml), messages, ruleCache);
-  }
-
-  public Model importXML(Reader xml, ValidationMessages messages, RuleCache repositoryCache) {
-    Model model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-    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, chcCursor, messages, repositoryCache);
-      }
-
-      cursor.getStreamReader().closeCompletely();
-
-    } catch (XMLStreamException e) {
-      LOG.error("XML is not valid", e);
-      messages.addErrorText("XML is not valid: " + e.getMessage());
-    }
-    return model;
-  }
-
-  private SMInputFactory initStax() {
-    XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
-    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
-    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
-    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
-    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
-    return new SMInputFactory(xmlFactory);
-  }
-
-  private Characteristic processCharacteristic(Model model, SMInputCursor chcCursor, ValidationMessages messages, RuleCache ruleCache) throws XMLStreamException {
-    Characteristic characteristic = Characteristic.create();
-    SMInputCursor cursor = chcCursor.childElementCursor();
-
-    String ruleRepositoryKey = null, ruleKey = null;
-    List<Characteristic> children = Lists.newArrayList();
-    while (cursor.getNext() != null) {
-      String node = cursor.getLocalName();
-      if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
-        characteristic.setKey(cursor.collectDescendantText().trim());
-
-      } else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) {
-        characteristic.setName(cursor.collectDescendantText().trim(), false);
-
-      } else if (StringUtils.equals(node, CHARACTERISTIC_DESCRIPTION)) {
-        characteristic.setDescription(cursor.collectDescendantText().trim());
-
-      } else if (StringUtils.equals(node, PROPERTY)) {
-        processProperty(characteristic, cursor, messages);
-
-      } else if (StringUtils.equals(node, CHARACTERISTIC)) {
-        children.add(processCharacteristic(model, cursor, messages, ruleCache));
-
-      } else if (StringUtils.equals(node, "rule-repo")) {
-        ruleRepositoryKey = cursor.collectDescendantText().trim();
-
-      } else if (StringUtils.equals(node, "rule-key")) {
-        ruleKey = cursor.collectDescendantText().trim();
-      }
-    }
-    fillRule(characteristic, ruleRepositoryKey, ruleKey, messages, ruleCache);
-
-    if (StringUtils.isNotBlank(characteristic.getKey()) || characteristic.getRule() != null) {
-      addCharacteristicToModel(model, characteristic, children);
-      return characteristic;
-    }
-    return null;
-  }
-
-  private void fillRule(Characteristic characteristic, String ruleRepositoryKey, String ruleKey, ValidationMessages messages,
-                        RuleCache ruleCache) {
-    if (StringUtils.isNotBlank(ruleRepositoryKey) && StringUtils.isNotBlank(ruleKey)) {
-      Rule rule = ruleCache.getRule(ruleRepositoryKey, ruleKey);
-      if (rule != null) {
-        characteristic.setRule(rule);
-      } else {
-        messages.addWarningText("Rule not found: [repository=" + ruleRepositoryKey + ", key=" + ruleKey + "]");
-      }
-    }
-  }
-
-  private void addCharacteristicToModel(Model model, Characteristic characteristic, List<Characteristic> children) {
-    model.addCharacteristic(characteristic);
-    for (Characteristic child : children) {
-      if (child != null) {
-        model.addCharacteristic(child);
-        characteristic.addChild(child);
-      }
-    }
-  }
-
-  private void processProperty(Characteristic characteristic, SMInputCursor cursor, ValidationMessages messages) throws XMLStreamException {
-    SMInputCursor c = cursor.childElementCursor();
-    String key = null;
-    Double value = null;
-    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 {
-          value = NumberUtils.createDouble(s);
-        } catch (NumberFormatException ex) {
-          String message = String.format("Cannot import value '%s' for field %s - Expected a numeric value instead", s, key);
-          LOG.error(message, ex);
-          messages.addErrorText(message);
-        }
-      } else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) {
-        textValue = c.collectDescendantText().trim();
-      }
-    }
-    if (StringUtils.isNotBlank(key)) {
-      characteristic.setProperty(key, textValue).setValue(value);
-    }
-  }
-}
index f65e5d175147f51d4486a61b45326bc4fc55fafc..022c3103ab2ac3b8793af85e0465a37f6b60c334 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.server.startup;
 import org.junit.Test;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.technicaldebt.RuleCache;
+import org.sonar.core.technicaldebt.TechnicalDebtRuleCache;
 import org.sonar.server.technicaldebt.TechnicalDebtManager;
 
 import static org.mockito.Matchers.any;
@@ -38,6 +38,6 @@ public class RegisterTechnicalDebtModelTest {
 
     sqaleDefinition.start();
 
-    verify(technicalDebtManager, times(1)).init(any(ValidationMessages.class), any(RuleCache.class));
+    verify(technicalDebtManager, times(1)).init(any(ValidationMessages.class), any(TechnicalDebtRuleCache.class));
   }
 }
diff --git a/sonar-server/src/test/java/org/sonar/server/technicaldebt/RuleCacheTest.java b/sonar-server/src/test/java/org/sonar/server/technicaldebt/RuleCacheTest.java
deleted file mode 100644 (file)
index 52e439b..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import com.google.common.collect.Lists;
-import org.junit.Test;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-
-import java.util.Collections;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
-public class RuleCacheTest {
-
-  @Test
-  public void lazy_load_rules_on_first_call() throws Exception {
-
-    RuleFinder ruleFinder = mock(RuleFinder.class);
-    when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(Collections.EMPTY_LIST);
-
-    RuleCache ruleCache = new RuleCache(ruleFinder);
-    ruleCache.getRule("", "");
-    ruleCache.getRule("", "");
-
-    verify(ruleFinder, times(1)).findAll(any(RuleQuery.class));
-  }
-
-  @Test
-  public void return_matching_rule() throws Exception {
-
-    Rule rule1 = Rule.create("repo1", "rule1");
-    Rule rule2 = Rule.create("repo2", "rule2");
-
-    RuleFinder ruleFinder = mock(RuleFinder.class);
-    when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1, rule2));
-
-    RuleCache ruleCache = new RuleCache(ruleFinder);
-    Rule actualRule1 = ruleCache.getRule("repo1", "rule1");
-    Rule actualRule2 = ruleCache.getRule("repo2", "rule2");
-
-    assertThat(actualRule1).isEqualTo(rule1);
-    assertThat(actualRule2).isEqualTo(rule2);
-  }
-
-  @Test
-  public void return_if_rule_exists() throws Exception {
-
-    Rule rule1 = Rule.create("repo1", "rule1");
-    Rule rule2 = Rule.create("repo2", "rule2");
-
-    RuleFinder ruleFinder = mock(RuleFinder.class);
-    when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1));
-
-    RuleCache ruleCache = new RuleCache(ruleFinder);
-
-    assertThat(ruleCache.exists(rule1)).isTrue();
-    assertThat(ruleCache.exists(rule2)).isFalse();
-  }
-}
index 2036f2aa19a2e9da47b9354c6c0fc9448d2074ee..b948e25c0b6c30d1a72e8b7b21a9a807d7563366 100644 (file)
@@ -28,6 +28,9 @@ import org.sonar.api.rules.Rule;
 import org.sonar.api.utils.ValidationMessages;
 import org.sonar.core.qualitymodel.DefaultModelFinder;
 import org.sonar.core.rule.DefaultRuleFinder;
+import org.sonar.core.technicaldebt.TechnicalDebtModelFinder;
+import org.sonar.core.technicaldebt.TechnicalDebtRuleCache;
+import org.sonar.core.technicaldebt.TechnicalDebtXMLImporter;
 import org.sonar.jpa.test.AbstractDbUnitTestCase;
 
 import java.io.FileNotFoundException;
@@ -49,7 +52,7 @@ public class TechnicalDebtManagerTest extends AbstractDbUnitTestCase {
     when(technicalDebtModelFinder.createReaderForXMLFile("technical-debt")).thenReturn(
       new FileReader(Resources.getResource(TechnicalDebtManagerTest.class, "TechnicalDebtManagerTest/fake-default-model.xml").getPath()));
 
-    manager = new TechnicalDebtManager(getSessionFactory(), new DefaultModelFinder(getSessionFactory()), technicalDebtModelFinder, new XMLImporter());
+    manager = new TechnicalDebtManager(getSessionFactory(), new DefaultModelFinder(getSessionFactory()), technicalDebtModelFinder, new TechnicalDebtXMLImporter());
   }
 
   @Test
@@ -67,15 +70,15 @@ public class TechnicalDebtManagerTest extends AbstractDbUnitTestCase {
 
     addPluginModel("java", "fake-java-model.xml");
 
-    RuleCache ruleCache = mock(RuleCache.class);
+    TechnicalDebtRuleCache technicalDebtRuleCache = mock(TechnicalDebtRuleCache.class);
     Rule rule1 = Rule.create("checkstyle", "import", "Regular expression");
     rule1.setId(1);
-    when(ruleCache.getRule("checkstyle", "import")).thenReturn(rule1);
+    when(technicalDebtRuleCache.getRule("checkstyle", "import")).thenReturn(rule1);
     Rule rule2 = Rule.create("checkstyle", "export", "Regular expression");
     rule2.setId(2);
-    when(ruleCache.getRule("checkstyle", "export")).thenReturn(rule2);
+    when(technicalDebtRuleCache.getRule("checkstyle", "export")).thenReturn(rule2);
 
-    manager.init(ValidationMessages.create(), ruleCache);
+    manager.init(ValidationMessages.create(), technicalDebtRuleCache);
 
     checkTables("create_model_with_requirements_from_plugin_on_first_execution", "quality_models", "characteristics", "characteristic_edges", "characteristic_properties");
   }
@@ -147,23 +150,23 @@ public class TechnicalDebtManagerTest extends AbstractDbUnitTestCase {
 
     addPluginModel("java", "fake-java-model.xml");
 
-    RuleCache ruleCache = mock(RuleCache.class);
+    TechnicalDebtRuleCache technicalDebtRuleCache = mock(TechnicalDebtRuleCache.class);
     Rule rule1 = Rule.create("checkstyle", "import", "Regular expression");
     rule1.setId(1);
-    when(ruleCache.getRule("checkstyle", "import")).thenReturn(rule1);
+    when(technicalDebtRuleCache.getRule("checkstyle", "import")).thenReturn(rule1);
     Rule rule2 = Rule.create("checkstyle", "export", "Regular expression");
     rule2.setId(2);
-    when(ruleCache.getRule("checkstyle", "export")).thenReturn(rule2);
+    when(technicalDebtRuleCache.getRule("checkstyle", "export")).thenReturn(rule2);
 
     ValidationMessages messages = ValidationMessages.create();
-    manager.init(messages, ruleCache);
+    manager.init(messages, technicalDebtRuleCache);
 
     assertThat(messages.getWarnings()).hasSize(1);
     assertThat(messages.getWarnings().get(0)).isEqualTo("Rule not found: [repository=checkstyle, key=ConstantNameCheck]");
   }
 
-  private RuleCache defaultRuleCache() {
-    return new RuleCache(new DefaultRuleFinder(getSessionFactory()));
+  private TechnicalDebtRuleCache defaultRuleCache() {
+    return new TechnicalDebtRuleCache(new DefaultRuleFinder(getSessionFactory()));
   }
 
   private void addPluginModel(String pluginKey, String xmlFile) throws FileNotFoundException {
diff --git a/sonar-server/src/test/java/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest.java b/sonar-server/src/test/java/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest.java
deleted file mode 100644 (file)
index b63ac66..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.Resources;
-import org.apache.commons.io.IOUtils;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.SonarPlugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.Reader;
-import java.net.MalformedURLException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TechnicalDebtModelFinderTest {
-
-  private static final String TEST_XML_PREFIX_PATH = "org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/";
-
-  private TechnicalDebtModelFinder modelFinder;
-
-  @Test
-  public void test_component_initialization() throws Exception {
-    // we do have the "csharp-model.xml" file in src/test/resources
-    PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class);
-    when(csharpPluginMetadata.getKey()).thenReturn("csharp");
-
-    // but we don' have the "php-model.xml" one
-    PluginMetadata phpPluginMetadata = mock(PluginMetadata.class);
-    when(phpPluginMetadata.getKey()).thenReturn("php");
-
-    PluginRepository repository = mock(PluginRepository.class);
-    when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
-    FakePlugin fakePlugin = new FakePlugin();
-    when(repository.getPlugin(anyString())).thenReturn(fakePlugin);
-    modelFinder = new TechnicalDebtModelFinder(repository, TEST_XML_PREFIX_PATH);
-
-    // when
-    modelFinder.start();
-
-    // assert
-    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
-    assertThat(contributingPluginList.size()).isEqualTo(1);
-    assertThat(contributingPluginList).containsOnly("csharp");
-  }
-
-  @Test
-  public void contributing_plugin_list() throws Exception {
-    initModel();
-    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
-    assertThat(contributingPluginList.size()).isEqualTo(2);
-    assertThat(contributingPluginList).contains("csharp", "java");
-  }
-
-  @Test
-  public void get_content_for_xml_file() throws Exception {
-    initModel();
-    Reader xmlFileReader = null;
-    try {
-      xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
-      assertNotNull(xmlFileReader);
-      List<String> lines = IOUtils.readLines(xmlFileReader);
-      assertThat(lines.size()).isEqualTo(25);
-      assertThat(lines.get(0)).isEqualTo("<sqale>");
-    } catch (Exception e) {
-      fail("Should be able to read the XML file.");
-    } finally {
-      IOUtils.closeQuietly(xmlFileReader);
-    }
-  }
-
-  @Test
-  public void return_xml_file_path_for_plugin() throws Exception {
-    initModel();
-    assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
-  }
-
-  @Test
-  public void contain_default_model() throws Exception {
-    modelFinder = new TechnicalDebtModelFinder(mock(PluginRepository.class));
-    modelFinder.start();
-    assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
-  }
-
-  private void initModel() throws MalformedURLException {
-    Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
-    contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
-    contributingPluginKeyToClassLoader.put("java", newClassLoader());
-    modelFinder = new TechnicalDebtModelFinder(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
-  }
-
-  private ClassLoader newClassLoader() throws MalformedURLException {
-    ClassLoader loader = mock(ClassLoader.class);
-    when(loader.getResourceAsStream(anyString())).thenAnswer(new Answer<InputStream>() {
-      public InputStream answer(InvocationOnMock invocation) throws Throwable {
-        return new FileInputStream(Resources.getResource((String) invocation.getArguments()[0]).getPath());
-      }
-    });
-    return loader;
-  }
-
-  class FakePlugin extends SonarPlugin {
-    public List getExtensions() {
-      return null;
-    }
-  }
-
-}
diff --git a/sonar-server/src/test/java/org/sonar/server/technicaldebt/TechnicalDebtModelTest.java b/sonar-server/src/test/java/org/sonar/server/technicaldebt/TechnicalDebtModelTest.java
deleted file mode 100644 (file)
index 1ade076..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
-
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TechnicalDebtModelTest {
-
-  private Model model;
-  private TechnicalDebtModel technicalDebtModel;
-  private List<Characteristic> defaultCharacteristics;
-
-  @Before
-  public void setUpModel() {
-    model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-    defaultCharacteristics = newArrayList();
-    technicalDebtModel = new TechnicalDebtModel(model, defaultCharacteristics);
-  }
-
-  @Test
-  public void merge_with_empty_model() {
-    Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-    Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
-    Characteristic ramEfficiency = with.createCharacteristicByKey("ram-efficiency", "RAM Efficiency");
-    efficiency.addChild(ramEfficiency);
-    Characteristic usability = with.createCharacteristicByKey("usability", "Usability");
-
-    ValidationMessages messages = ValidationMessages.create();
-
-    defaultCharacteristics.addAll(newArrayList(efficiency, ramEfficiency, usability));
-    technicalDebtModel.mergeWith(with, messages, mockRuleCache());
-
-    assertThat(model.getCharacteristics()).hasSize(3);
-    assertThat(model.getRootCharacteristics()).hasSize(2);
-    assertThat(model.getCharacteristicByKey("ram-efficiency").getDepth()).isEqualTo(Characteristic.ROOT_DEPTH + 1);
-    assertThat(messages.getErrors()).isEmpty();
-  }
-
-  @Test
-  public void not_update_existing_characteristics() {
-    model.createCharacteristicByKey("efficiency", "Efficiency");
-
-    Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-    with.createCharacteristicByKey("efficiency", "New efficiency");
-
-    technicalDebtModel.mergeWith(with, ValidationMessages.create(), mockRuleCache());
-
-    assertThat(model.getCharacteristics()).hasSize(1);
-    assertThat(model.getRootCharacteristics()).hasSize(1);
-    assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
-  }
-
-  @Test
-  public void warn_on_missing_rule() {
-    Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-    Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
-    Rule fooRule = Rule.create("foo", "bar", "Bar");
-    Characteristic requirement = with.createCharacteristicByRule(fooRule);
-    efficiency.addChild(requirement);
-
-    ValidationMessages messages = ValidationMessages.create();
-
-    defaultCharacteristics.add(efficiency);
-    technicalDebtModel.mergeWith(with, messages, mockRuleCache());
-
-    assertThat(model.getCharacteristics()).hasSize(1);
-    assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
-    assertThat(model.getCharacteristicByRule(fooRule)).isNull();
-    assertThat(messages.getWarnings()).hasSize(1);
-    assertThat(messages.getWarnings().get(0)).contains("foo"); // warning: the rule foo does not exist
-  }
-
-  @Test
-  public void fail_when_adding_characteristic_not_existing_in_default_characteristics() {
-    Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-    Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
-    // usability is not available in default characteristics
-    with.createCharacteristicByKey("usability", "Usability");
-
-    ValidationMessages messages = ValidationMessages.create();
-
-    defaultCharacteristics.add(efficiency);
-    try {
-      technicalDebtModel.mergeWith(with, messages, mockRuleCache());
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class);
-    }
-    assertThat(model.getCharacteristics()).hasSize(1);
-  }
-
-  private RuleCache mockRuleCache() {
-    RuleFinder ruleFinder = mock(RuleFinder.class);
-    when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(newRegexpRule()));
-    return new RuleCache(ruleFinder);
-  }
-
-  private Rule newRegexpRule() {
-    return Rule.create("checkstyle", "regexp", "Regular expression");
-  }
-}
-
diff --git a/sonar-server/src/test/java/org/sonar/server/technicaldebt/XMLImporterTest.java b/sonar-server/src/test/java/org/sonar/server/technicaldebt/XMLImporterTest.java
deleted file mode 100644 (file)
index 3cec0b3..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.technicaldebt;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.io.Resources;
-import org.junit.Test;
-import org.sonar.api.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
-
-import java.io.IOException;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class XMLImporterTest {
-
-  @Test
-  public void shouldImportXML() {
-    RuleCache ruleCache = mockRuleCache();
-
-    String xml = getFileContent("shouldImportXML.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    Model sqale = new XMLImporter().importXML(xml, messages, ruleCache);
-
-    checkXmlCorrectlyImported(sqale, messages);
-  }
-
-  @Test
-  public void shouldBadlyFormattedImportXML() {
-    RuleCache ruleCache = mockRuleCache();
-    String xml = getFileContent("shouldImportXML_badly-formatted.xml");
-
-    ValidationMessages messages = ValidationMessages.create();
-    Model sqale = new XMLImporter().importXML(xml, messages, ruleCache);
-
-    checkXmlCorrectlyImported(sqale, messages);
-  }
-
-  @Test
-  public void shouldLogWarningIfRuleNotFound() {
-    RuleCache ruleCache = mockRuleCache();
-    String xml = getFileContent("shouldLogWarningIfRuleNotFound.xml");
-    ValidationMessages messages = ValidationMessages.create();
-
-    Model sqale = new XMLImporter().importXML(xml, messages, ruleCache);
-
-    assertThat(messages.getWarnings()).hasSize(1);
-
-    // characteristics
-    assertThat(sqale.getRootCharacteristics()).hasSize(1);
-    Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
-    assertThat(efficiency.getChildren()).isEmpty();
-    assertThat(messages.getWarnings().get(0)).contains("findbugs");
-  }
-
-  @Test
-  public void shouldNotifyOnUnexpectedValueTypeInXml() throws Exception {
-
-    RuleCache ruleCache = mockRuleCache();
-
-    String xml = getFileContent("shouldRejectXML_with_invalid_value.xml");
-    ValidationMessages messages = ValidationMessages.create();
-
-    new XMLImporter().importXML(xml, messages, ruleCache);
-
-    assertThat(messages.getErrors()).hasSize(1);
-    assertThat(messages.getErrors().get(0)).isEqualTo("Cannot import value 'abc' for field factor - Expected a numeric value instead");
-  }
-
-  private RuleCache mockRuleCache() {
-    RuleFinder finder = mock(RuleFinder.class);
-    when(finder.findAll(any(RuleQuery.class))).thenReturn(Lists.newArrayList(Rule.create("checkstyle", "Regexp", "Regular expression")));
-    return new RuleCache(finder);
-  }
-
-  private void checkXmlCorrectlyImported(Model sqale, ValidationMessages messages) {
-
-    assertThat(messages.getErrors()).isEmpty();
-    assertThat(sqale.getName()).isEqualTo(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-
-    // characteristics
-    assertThat(sqale.getRootCharacteristics()).hasSize(2);
-    assertThat(sqale.getCharacteristicByKey("USABILITY").getDescription()).isEqualTo("Estimate usability");
-    Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
-    assertThat(efficiency.getName()).isEqualTo("Efficiency");
-
-    // sub-characteristics
-    assertThat(efficiency.getChildren()).hasSize(1);
-    Characteristic requirement = efficiency.getChildren().get(0);
-    assertThat(requirement.getRule().getRepositoryKey()).isEqualTo("checkstyle");
-    assertThat(requirement.getRule().getKey()).isEqualTo("Regexp");
-    assertThat(requirement.getPropertyTextValue("function", null)).isEqualTo("linear");
-    assertThat(requirement.getPropertyValue("factor", null)).isEqualTo(3.2);
-  }
-
-  private String getFileContent(String file) {
-    try {
-      return Resources.toString(Resources.getResource(XMLImporterTest.class, "XMLImporterTest/" + file), Charsets.UTF_8);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-}
diff --git a/sonar-server/src/test/resources/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/csharp-model.xml b/sonar-server/src/test/resources/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/csharp-model.xml
deleted file mode 100644 (file)
index e4569a2..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<sqale>
-  <chc>
-    <key>USABILITY</key>
-    <name>Usability</name>
-    <desc>Estimate usability</desc>
-  </chc>
-  <chc>
-    <key>EFFICIENCY</key>
-    <name>Efficiency</name>
-    <chc>
-      <rule-repo>gendarme</rule-repo>
-      <rule-key>EnsureLocalDisposalRule</rule-key>
-      <prop>
-        <key>remediationFactor</key>
-        <val>0.125</val>
-        <txt>d</txt>
-      </prop>
-      <prop>
-        <key>remediationFunction</key>
-        <txt>linear</txt>
-      </prop>
-    </chc>
-  </chc>
-
-</sqale>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/java-model.xml b/sonar-server/src/test/resources/org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/java-model.xml
deleted file mode 100644 (file)
index 0b37f56..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<sqale>
-  <chc>
-    <key>USABILITY</key>
-    <name>Usability</name>
-    <desc>Estimate usability</desc>
-  </chc>
-  <chc>
-    <key>EFFICIENCY</key>
-    <name>Efficiency</name>
-    <chc>
-      <rule-repo>squid-cobol</rule-repo>
-      <rule-key>CheckLoop</rule-key>
-      <prop>
-        <key>remediationFactor</key>
-        <val>0.125</val>
-        <txt>d</txt>
-      </prop>
-      <prop>
-        <key>remediationFunction</key>
-        <txt>linear</txt>
-      </prop>
-    </chc>
-  </chc>
-
-</sqale>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldImportXML.xml b/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldImportXML.xml
deleted file mode 100644 (file)
index e405189..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<sqale>
-  <chc>
-    <key>USABILITY</key>
-    <name>Usability</name>
-    <desc>Estimate usability</desc>
-  </chc>
-  <chc>
-    <key>EFFICIENCY</key>
-    <name>Efficiency</name>
-
-    <chc>
-      <rule-repo>checkstyle</rule-repo>
-      <rule-key>Regexp</rule-key>
-      <prop>
-        <key>factor</key>
-        <val>3.2</val>
-      </prop>
-      <prop>
-        <key>function</key>
-        <txt>linear</txt>
-      </prop>
-    </chc>
-  </chc>
-
-</sqale>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldImportXML_badly-formatted.xml b/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldImportXML_badly-formatted.xml
deleted file mode 100644 (file)
index 373959f..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<sqale>
-  <chc>
-    <key>USABILITY
-    </key>
-    <name>Usability
-    </name>
-    <desc>Estimate usability
-    </desc>
-  </chc>
-  <chc>
-    <key>EFFICIENCY
-    </key>
-    <name>Efficiency
-    </name>
-
-    <chc>
-      <rule-repo>checkstyle
-      </rule-repo>
-      <rule-key>Regexp
-      </rule-key>
-      <prop>
-        <key>factor
-        </key>
-        <val>3.2
-        </val>
-      </prop>
-      <prop>
-        <key>function
-        </key>
-        <txt>linear
-        </txt>
-      </prop>
-    </chc>
-  </chc>
-
-</sqale>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldLogWarningIfRuleNotFound.xml b/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldLogWarningIfRuleNotFound.xml
deleted file mode 100644 (file)
index bd459f6..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<sqale>
-  <chc>
-    <key>EFFICIENCY</key>
-    <name>Efficiency</name>
-
-    <chc>
-      <rule-repo>findbugs</rule-repo>
-      <rule-key>Foo</rule-key>
-    </chc>
-  </chc>
-
-</sqale>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldRejectXML_with_invalid_value.xml b/sonar-server/src/test/resources/org/sonar/server/technicaldebt/XMLImporterTest/shouldRejectXML_with_invalid_value.xml
deleted file mode 100644 (file)
index 638f00e..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<sqale>
-  <chc>
-    <key>USABILITY</key>
-    <name>Usability</name>
-    <desc>Estimate usability</desc>
-  </chc>
-  <chc>
-    <key>EFFICIENCY</key>
-    <name>Efficiency</name>
-
-    <chc>
-      <rule-repo>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>
-
-</sqale>