]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4908 extract RulesDefinitionXmlLoader
authorSimon Brandhof <simon.brandhof@gmail.com>
Mon, 24 Mar 2014 17:01:30 +0000 (18:01 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Mon, 24 Mar 2014 17:48:48 +0000 (18:48 +0100)
13 files changed:
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleDefinitionsFromXml.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionXmlLoader.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionFromXmlTest.java [deleted file]
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/deprecated.xml [deleted file]
sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/rules.xml [deleted file]
sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/utf8.xml [deleted file]
sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/deprecated.xml [new file with mode: 0644]
sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/invalid.xml [new file with mode: 0644]
sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/rules.xml [new file with mode: 0644]
sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/utf8.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java

diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleDefinitionsFromXml.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleDefinitionsFromXml.java
deleted file mode 100644 (file)
index 80b9bec..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.api.server.rule;
-
-import com.google.common.io.Closeables;
-import org.apache.commons.lang.StringUtils;
-import org.codehaus.staxmate.SMInputFactory;
-import org.codehaus.staxmate.in.SMHierarchicCursor;
-import org.codehaus.staxmate.in.SMInputCursor;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rule.Severity;
-import org.sonar.check.Cardinality;
-
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @since 4.2
- */
-class RuleDefinitionsFromXml {
-
-  void loadRules(RulesDefinition.NewRepository repo, InputStream input, String encoding) {
-    Reader reader = null;
-    try {
-      reader = new InputStreamReader(input, encoding);
-      loadRules(repo, reader);
-
-    } catch (IOException e) {
-      throw new IllegalStateException("Fail to load XML file", e);
-
-    } finally {
-      Closeables.closeQuietly(reader);
-    }
-  }
-
-  void loadRules(RulesDefinition.NewRepository repo, Reader reader) {
-    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
-    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
-    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
-    // just so it won't try to load DTD in if there's DOCTYPE
-    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
-    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
-    SMInputFactory inputFactory = new SMInputFactory(xmlFactory);
-    try {
-      SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
-      rootC.advance(); // <rules>
-
-      SMInputCursor rulesC = rootC.childElementCursor("rule");
-      while (rulesC.getNext() != null) {
-        // <rule>
-        processRule(repo, rulesC);
-      }
-
-    } catch (XMLStreamException e) {
-      throw new IllegalStateException("XML is not valid", e);
-    }
-  }
-
-  private void processRule(RulesDefinition.NewRepository repo, SMInputCursor ruleC) throws XMLStreamException {
-    String key = null, name = null, description = null, internalKey = null, severity = Severity.defaultSeverity(), status = null;
-    Cardinality cardinality = Cardinality.SINGLE;
-    List<ParamStruct> params = new ArrayList<ParamStruct>();
-    List<String> tags = new ArrayList<String>();
-
-    /* BACKWARD COMPATIBILITY WITH VERY OLD FORMAT */
-    String keyAttribute = ruleC.getAttrValue("key");
-    if (StringUtils.isNotBlank(keyAttribute)) {
-      key = StringUtils.trim(keyAttribute);
-    }
-    String priorityAttribute = ruleC.getAttrValue("priority");
-    if (StringUtils.isNotBlank(priorityAttribute)) {
-      severity = StringUtils.trim(priorityAttribute);
-    }
-
-    SMInputCursor cursor = ruleC.childElementCursor();
-    while (cursor.getNext() != null) {
-      String nodeName = cursor.getLocalName();
-
-      if (StringUtils.equalsIgnoreCase("name", nodeName)) {
-        name = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
-        description = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
-        key = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
-        // deprecated field, replaced by internalKey
-        internalKey = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("internalKey", nodeName)) {
-        internalKey = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
-        // deprecated field, replaced by severity
-        severity = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("severity", nodeName)) {
-        severity = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
-        cardinality = Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false)));
-
-      } else if (StringUtils.equalsIgnoreCase("status", nodeName)) {
-        status = StringUtils.trim(cursor.collectDescendantText(false));
-
-      } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
-        params.add(processParameter(cursor));
-
-      } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) {
-        tags.add(StringUtils.trim(cursor.collectDescendantText(false)));
-      }
-    }
-    RulesDefinition.NewRule rule = repo.createRule(key)
-      .setHtmlDescription(description)
-      .setSeverity(severity)
-      .setName(name)
-      .setInternalKey(internalKey)
-      .setTags(tags.toArray(new String[tags.size()]))
-      .setTemplate(cardinality == Cardinality.MULTIPLE);
-    if (status != null) {
-      rule.setStatus(RuleStatus.valueOf(status));
-    }
-    for (ParamStruct param : params) {
-      rule.createParam(param.key)
-        .setDefaultValue(param.defaultValue)
-        .setType(param.type)
-        .setDescription(param.description);
-    }
-  }
-
-  private static class ParamStruct {
-    String key, description, defaultValue;
-    RuleParamType type = RuleParamType.STRING;
-  }
-
-  private ParamStruct processParameter(SMInputCursor ruleC) throws XMLStreamException {
-    ParamStruct param = new ParamStruct();
-
-    // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
-    String keyAttribute = ruleC.getAttrValue("key");
-    if (StringUtils.isNotBlank(keyAttribute)) {
-      param.key = StringUtils.trim(keyAttribute);
-    }
-
-    // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
-    String typeAttribute = ruleC.getAttrValue("type");
-    if (StringUtils.isNotBlank(typeAttribute)) {
-      param.type = RuleParamType.parse(typeAttribute);
-    }
-
-    SMInputCursor paramC = ruleC.childElementCursor();
-    while (paramC.getNext() != null) {
-      String propNodeName = paramC.getLocalName();
-      String propText = StringUtils.trim(paramC.collectDescendantText(false));
-      if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
-        param.key = propText;
-
-      } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
-        param.description = propText;
-
-      } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
-        param.type = RuleParamType.parse(propText);
-
-      } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
-        param.defaultValue = propText;
-      }
-    }
-    return param;
-  }
-}
index 45650ecf6c04e07f7b4919f3d005496181f4e712..2bab880020aec650d45b5b7f0152ee88db70bff1 100644 (file)
 package org.sonar.api.server.rule;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.*;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.LoggerFactory;
@@ -31,10 +37,9 @@ import org.sonar.api.rule.Severity;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
-
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.URL;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -107,48 +112,6 @@ public interface RulesDefinition extends ServerExtension {
      */
     NewExtendedRepository loadAnnotatedClasses(Class... classes);
 
-    /**
-     * Reads definitions of rules from a XML file. Format is :
-     * <pre>
-     * &lt;rules&gt;
-     * &lt;rule&gt;
-     * &lt;!-- required fields --&gt;
-     * &lt;key&gt;the-rule-key&lt;/key&gt;
-     * &lt;name&gt;The purpose of the rule&lt;/name&gt;
-     * &lt;description&gt;
-     * &lt;![CDATA[The description]]&gt;
-     * &lt;/description&gt;
-     *
-     * &lt;!-- optional fields --&gt;
-     * &lt;internalKey&gt;Checker/TreeWalker/LocalVariableName&lt;/internalKey&gt;
-     * &lt;severity&gt;BLOCKER&lt;/severity&gt;
-     * &lt;cardinality&gt;MULTIPLE&lt;/cardinality&gt;
-     * &lt;status&gt;BETA&lt;/status&gt;
-     * &lt;param&gt;
-     * &lt;key&gt;the-param-key&lt;/key&gt;
-     * &lt;tag&gt;style&lt;/tag&gt;
-     * &lt;tag&gt;security&lt;/tag&gt;
-     * &lt;description&gt;
-     * &lt;![CDATA[
-     * the param-description
-     * ]]&gt;
-     * &lt;/description&gt;
-     * &lt;defaultValue&gt;42&lt;/defaultValue&gt;
-     * &lt;/param&gt;
-     * &lt;param&gt;
-     * &lt;key&gt;another-param&lt;/key&gt;
-     * &lt;/param&gt;
-     *
-     * &lt;!-- deprecated fields --&gt;
-     * &lt;configKey&gt;Checker/TreeWalker/LocalVariableName&lt;/configKey&gt;
-     * &lt;priority&gt;BLOCKER&lt;/priority&gt;
-     * &lt;/rule&gt;
-     * &lt;/rules&gt;
-     *
-     * </pre>
-     */
-    NewExtendedRepository loadXml(InputStream xmlInput, String encoding);
-
     void done();
   }
 
@@ -157,6 +120,10 @@ public interface RulesDefinition extends ServerExtension {
 
     @CheckForNull
     NewRule rule(String ruleKey);
+
+    Collection<NewRule> rules();
+
+    String key();
   }
 
   class NewRepositoryImpl implements NewRepository {
@@ -174,6 +141,11 @@ public interface RulesDefinition extends ServerExtension {
       this.language = language;
     }
 
+    @Override
+    public String key() {
+      return key;
+    }
+
     @Override
     public NewRepositoryImpl setName(@Nullable String s) {
       if (StringUtils.isNotEmpty(s)) {
@@ -201,6 +173,11 @@ public interface RulesDefinition extends ServerExtension {
       return newRules.get(ruleKey);
     }
 
+    @Override
+    public Collection<NewRule> rules() {
+      return newRules.values();
+    }
+
     @Override
     public NewRepositoryImpl loadAnnotatedClasses(Class... classes) {
       new RuleDefinitionsFromAnnotations().loadRules(this, classes);
@@ -212,12 +189,6 @@ public interface RulesDefinition extends ServerExtension {
       return new RuleDefinitionsFromAnnotations().loadRule(this, clazz);
     }
 
-    @Override
-    public NewRepositoryImpl loadXml(InputStream xmlInput, String encoding) {
-      new RuleDefinitionsFromXml().loadRules(this, xmlInput, encoding);
-      return this;
-    }
-
     @Override
     public void done() {
       // note that some validations can be done here, for example for
@@ -409,6 +380,10 @@ public interface RulesDefinition extends ServerExtension {
       return paramsByKey.get(paramKey);
     }
 
+    public Collection<NewParam> params() {
+      return paramsByKey.values();
+    }
+
     /**
      * @see RuleTagFormat
      */
@@ -589,6 +564,10 @@ public interface RulesDefinition extends ServerExtension {
       this.key = this.name = key;
     }
 
+    public String key() {
+      return key;
+    }
+
     public NewParam setName(@Nullable String s) {
       // name must never be null.
       this.name = StringUtils.defaultIfBlank(s, key);
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionXmlLoader.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionXmlLoader.java
new file mode 100644 (file)
index 0000000..240420d
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.server.rule;
+
+import com.google.common.io.Closeables;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.staxmate.SMInputFactory;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.check.Cardinality;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loads definitions of rules from a XML file.
+ *
+ * <h3>XML Format</h3>
+ * <pre>
+ * &lt;rules&gt;
+ *   &lt;rule&gt;
+ *     &lt;!-- required fields --&gt;
+ *     &lt;key&gt;the-rule-key&lt;/key&gt;
+ *     &lt;name&gt;The purpose of the rule&lt;/name&gt;
+ *
+ *     &lt;!-- optional fields --&gt;
+ *     &lt;description&gt;
+ *       &lt;![CDATA[The description]]&gt;
+ *     &lt;/description&gt;
+ *     &lt;internalKey&gt;Checker/TreeWalker/LocalVariableName&lt;/internalKey&gt;
+ *     &lt;severity&gt;BLOCKER&lt;/severity&gt;
+ *     &lt;cardinality&gt;MULTIPLE&lt;/cardinality&gt;
+ *     &lt;status&gt;BETA&lt;/status&gt;
+ *     &lt;param&gt;
+ *       &lt;key&gt;the-param-key&lt;/key&gt;
+ *       &lt;tag&gt;style&lt;/tag&gt;
+ *       &lt;tag&gt;security&lt;/tag&gt;
+ *       &lt;description&gt;
+ *         &lt;![CDATA[the param-description]]&gt;
+ *       &lt;/description&gt;
+ *       &lt;defaultValue&gt;42&lt;/defaultValue&gt;
+ *     &lt;/param&gt;
+ *     &lt;param&gt;
+ *       &lt;key&gt;another-param&lt;/key&gt;
+ *     &lt;/param&gt;
+ *
+ *     &lt;!-- deprecated fields --&gt;
+ *     &lt;configKey&gt;Checker/TreeWalker/LocalVariableName&lt;/configKey&gt;
+ *     &lt;priority&gt;BLOCKER&lt;/priority&gt;
+ *   &lt;/rule&gt;
+ * &lt;/rules&gt;
+ * </pre>
+ *
+ * @since 4.3
+ */
+public class RulesDefinitionXmlLoader implements ServerComponent {
+
+  public void load(RulesDefinition.NewRepository repo, InputStream input, String encoding) {
+    Reader reader = null;
+    try {
+      reader = new InputStreamReader(input, encoding);
+      load(repo, reader);
+
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to load XML file", e);
+
+    } finally {
+      Closeables.closeQuietly(reader);
+    }
+  }
+
+  public void load(RulesDefinition.NewRepository repo, Reader reader) {
+    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
+    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
+    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
+    // just so it won't try to load DTD in if there's DOCTYPE
+    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
+    SMInputFactory inputFactory = new SMInputFactory(xmlFactory);
+    try {
+      SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
+      rootC.advance(); // <rules>
+
+      SMInputCursor rulesC = rootC.childElementCursor("rule");
+      while (rulesC.getNext() != null) {
+        // <rule>
+        processRule(repo, rulesC);
+      }
+
+    } catch (XMLStreamException e) {
+      throw new IllegalStateException("XML is not valid", e);
+    }
+  }
+
+  private void processRule(RulesDefinition.NewRepository repo, SMInputCursor ruleC) throws XMLStreamException {
+    String key = null, name = null, description = null, internalKey = null, severity = Severity.defaultSeverity(), status = null;
+    Cardinality cardinality = Cardinality.SINGLE;
+    List<ParamStruct> params = new ArrayList<ParamStruct>();
+    List<String> tags = new ArrayList<String>();
+
+    /* BACKWARD COMPATIBILITY WITH VERY OLD FORMAT */
+    String keyAttribute = ruleC.getAttrValue("key");
+    if (StringUtils.isNotBlank(keyAttribute)) {
+      key = StringUtils.trim(keyAttribute);
+    }
+    String priorityAttribute = ruleC.getAttrValue("priority");
+    if (StringUtils.isNotBlank(priorityAttribute)) {
+      severity = StringUtils.trim(priorityAttribute);
+    }
+
+    SMInputCursor cursor = ruleC.childElementCursor();
+    while (cursor.getNext() != null) {
+      String nodeName = cursor.getLocalName();
+
+      if (StringUtils.equalsIgnoreCase("name", nodeName)) {
+        name = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
+        description = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
+        key = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
+        // deprecated field, replaced by internalKey
+        internalKey = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("internalKey", nodeName)) {
+        internalKey = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
+        // deprecated field, replaced by severity
+        severity = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("severity", nodeName)) {
+        severity = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
+        cardinality = Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false)));
+
+      } else if (StringUtils.equalsIgnoreCase("status", nodeName)) {
+        status = StringUtils.trim(cursor.collectDescendantText(false));
+
+      } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
+        params.add(processParameter(cursor));
+
+      } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) {
+        tags.add(StringUtils.trim(cursor.collectDescendantText(false)));
+      }
+    }
+    RulesDefinition.NewRule rule = repo.createRule(key)
+      .setHtmlDescription(description)
+      .setSeverity(severity)
+      .setName(name)
+      .setInternalKey(internalKey)
+      .setTags(tags.toArray(new String[tags.size()]))
+      .setTemplate(cardinality == Cardinality.MULTIPLE);
+    if (status != null) {
+      rule.setStatus(RuleStatus.valueOf(status));
+    }
+    for (ParamStruct param : params) {
+      rule.createParam(param.key)
+        .setDefaultValue(param.defaultValue)
+        .setType(param.type)
+        .setDescription(param.description);
+    }
+  }
+
+  private static class ParamStruct {
+    String key, description, defaultValue;
+    RuleParamType type = RuleParamType.STRING;
+  }
+
+  private ParamStruct processParameter(SMInputCursor ruleC) throws XMLStreamException {
+    ParamStruct param = new ParamStruct();
+
+    // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
+    String keyAttribute = ruleC.getAttrValue("key");
+    if (StringUtils.isNotBlank(keyAttribute)) {
+      param.key = StringUtils.trim(keyAttribute);
+    }
+
+    // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
+    String typeAttribute = ruleC.getAttrValue("type");
+    if (StringUtils.isNotBlank(typeAttribute)) {
+      param.type = RuleParamType.parse(typeAttribute);
+    }
+
+    SMInputCursor paramC = ruleC.childElementCursor();
+    while (paramC.getNext() != null) {
+      String propNodeName = paramC.getLocalName();
+      String propText = StringUtils.trim(paramC.collectDescendantText(false));
+      if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
+        param.key = propText;
+
+      } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
+        param.description = propText;
+
+      } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
+        param.type = RuleParamType.parse(propText);
+
+      } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
+        param.defaultValue = propText;
+      }
+    }
+    return param;
+  }
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionFromXmlTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionFromXmlTest.java
deleted file mode 100644 (file)
index 8be3591..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.api.server.rule;
-
-import com.google.common.base.Charsets;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rule.Severity;
-
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class RulesDefinitionFromXmlTest {
-
-  @org.junit.Rule
-  public final ExpectedException thrown = ExpectedException.none();
-
-  private RulesDefinition.Repository load(Reader reader) {
-    RulesDefinition.Context context = new RulesDefinition.Context();
-    RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
-    new RuleDefinitionsFromXml().loadRules(newRepository, reader);
-    newRepository.done();
-    return context.repository("squid");
-  }
-
-  @Test
-  public void should_parse_xml() throws Exception {
-    InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/rules.xml"), Charsets.UTF_8.name());
-    RulesDefinition.Repository repository = load(reader);
-    assertThat(repository.rules()).hasSize(2);
-
-    RulesDefinition.Rule rule = repository.rule("complete");
-    assertThat(rule.key()).isEqualTo("complete");
-    assertThat(rule.name()).isEqualTo("Complete");
-    assertThat(rule.htmlDescription()).isEqualTo("Description of Complete");
-    assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
-    assertThat(rule.template()).isTrue();
-    assertThat(rule.status()).isEqualTo(RuleStatus.BETA);
-    assertThat(rule.internalKey()).isEqualTo("Checker/TreeWalker/LocalVariableName");
-    assertThat(rule.tags()).containsOnly("style", "security");
-
-    assertThat(rule.params()).hasSize(2);
-    RulesDefinition.Param ignore = rule.param("ignore");
-    assertThat(ignore.key()).isEqualTo("ignore");
-    assertThat(ignore.description()).isEqualTo("Ignore ?");
-    assertThat(ignore.defaultValue()).isEqualTo("false");
-
-    rule = repository.rule("minimal");
-    assertThat(rule.key()).isEqualTo("minimal");
-    assertThat(rule.name()).isEqualTo("Minimal");
-    assertThat(rule.htmlDescription()).isEqualTo("Description of Minimal");
-    assertThat(rule.params()).isEmpty();
-    assertThat(rule.status()).isEqualTo(RuleStatus.READY);
-    assertThat(rule.severity()).isEqualTo(Severity.MAJOR);
-  }
-
-  @Test
-  public void should_fail_if_missing_rule_key() {
-    thrown.expect(IllegalStateException.class);
-    load(new StringReader("<rules><rule><name>Foo</name></rule></rules>"));
-  }
-
-  @Test
-  public void should_fail_if_missing_property_key() {
-    thrown.expect(IllegalStateException.class);
-    load(new StringReader("<rules><rule><key>foo</key><name>Foo</name><param></param></rule></rules>"));
-  }
-
-  @Test
-  public void should_fail_on_invalid_rule_parameter_type() {
-    thrown.expect(IllegalStateException.class);
-    load(new StringReader("<rules><rule><key>foo</key><name>Foo</name><param><key>key</key><type>INVALID</type></param></rule></rules>"));
-  }
-
-  @Test
-  public void test_utf8_encoding() throws UnsupportedEncodingException {
-    InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/utf8.xml"), Charsets.UTF_8.name());
-    RulesDefinition.Repository repository = load(reader);
-
-    assertThat(repository.rules()).hasSize(1);
-    RulesDefinition.Rule rule = repository.rules().get(0);
-    assertThat(rule.key()).isEqualTo("com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck");
-    assertThat(rule.name()).isEqualTo("M & M");
-    assertThat(rule.htmlDescription().charAt(0)).isEqualTo('\u00E9');
-    assertThat(rule.htmlDescription().charAt(1)).isEqualTo('\u00E0');
-    assertThat(rule.htmlDescription().charAt(2)).isEqualTo('\u0026');
-  }
-
-  @Test
-  public void should_support_deprecated_format() throws UnsupportedEncodingException {
-    // the deprecated format uses some attributes instead of nodes
-    InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/deprecated.xml"), Charsets.UTF_8.name());
-    RulesDefinition.Repository repository = load(reader);
-
-    assertThat(repository.rules()).hasSize(1);
-    RulesDefinition.Rule rule = repository.rules().get(0);
-    assertThat(rule.key()).isEqualTo("org.sonar.it.checkstyle.MethodsCountCheck");
-    assertThat(rule.internalKey()).isEqualTo("Checker/TreeWalker/org.sonar.it.checkstyle.MethodsCountCheck");
-    assertThat(rule.severity()).isEqualTo(Severity.CRITICAL);
-    assertThat(rule.htmlDescription()).isEqualTo("Count methods");
-    assertThat(rule.param("minMethodsCount")).isNotNull();
-  }
-}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest.java
new file mode 100644 (file)
index 0000000..df6ff3b
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.server.rule;
+
+import com.google.common.base.Charsets;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RulesDefinitionXmlLoaderTest {
+
+  @org.junit.Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  private RulesDefinition.Repository load(InputStream input, String encoding) {
+    RulesDefinition.Context context = new RulesDefinition.Context();
+    RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
+    new RulesDefinitionXmlLoader().load(newRepository, input, encoding);
+    newRepository.done();
+    return context.repository("squid");
+  }
+
+  @Test
+  public void parse_xml() throws Exception {
+    InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/rules.xml");
+    RulesDefinition.Repository repository = load(input, Charsets.UTF_8.name());
+    assertThat(repository.rules()).hasSize(2);
+
+    RulesDefinition.Rule rule = repository.rule("complete");
+    assertThat(rule.key()).isEqualTo("complete");
+    assertThat(rule.name()).isEqualTo("Complete");
+    assertThat(rule.htmlDescription()).isEqualTo("Description of Complete");
+    assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
+    assertThat(rule.template()).isTrue();
+    assertThat(rule.status()).isEqualTo(RuleStatus.BETA);
+    assertThat(rule.internalKey()).isEqualTo("Checker/TreeWalker/LocalVariableName");
+    assertThat(rule.tags()).containsOnly("style", "security");
+
+    assertThat(rule.params()).hasSize(2);
+    RulesDefinition.Param ignore = rule.param("ignore");
+    assertThat(ignore.key()).isEqualTo("ignore");
+    assertThat(ignore.description()).isEqualTo("Ignore ?");
+    assertThat(ignore.defaultValue()).isEqualTo("false");
+
+    rule = repository.rule("minimal");
+    assertThat(rule.key()).isEqualTo("minimal");
+    assertThat(rule.name()).isEqualTo("Minimal");
+    assertThat(rule.htmlDescription()).isEqualTo("Description of Minimal");
+    assertThat(rule.params()).isEmpty();
+    assertThat(rule.status()).isEqualTo(RuleStatus.READY);
+    assertThat(rule.severity()).isEqualTo(Severity.MAJOR);
+  }
+
+  @Test
+  public void fail_if_missing_rule_key() {
+    thrown.expect(IllegalStateException.class);
+    load(IOUtils.toInputStream("<rules><rule><name>Foo</name></rule></rules>"), Charsets.UTF_8.name());
+  }
+
+  @Test
+  public void fail_if_missing_property_key() {
+    thrown.expect(IllegalStateException.class);
+    load(IOUtils.toInputStream("<rules><rule><key>foo</key><name>Foo</name><param></param></rule></rules>"), Charsets.UTF_8.name());
+  }
+
+  @Test
+  public void fail_on_invalid_rule_parameter_type() {
+    thrown.expect(IllegalStateException.class);
+    load(IOUtils.toInputStream("<rules><rule><key>foo</key><name>Foo</name><param><key>key</key><type>INVALID</type></param></rule></rules>"), Charsets.UTF_8.name());
+  }
+
+  @Test
+  public void fail_if_invalid_xml() throws UnsupportedEncodingException {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("XML is not valid");
+
+    InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/invalid.xml");
+    load(input, Charsets.UTF_8.name());
+  }
+
+  @Test
+  public void test_utf8_encoding() throws UnsupportedEncodingException {
+    InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/utf8.xml");
+    RulesDefinition.Repository repository = load(input, Charsets.UTF_8.name());
+
+    assertThat(repository.rules()).hasSize(1);
+    RulesDefinition.Rule rule = repository.rules().get(0);
+    assertThat(rule.key()).isEqualTo("com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck");
+    assertThat(rule.name()).isEqualTo("M & M");
+    assertThat(rule.htmlDescription().charAt(0)).isEqualTo('\u00E9');
+    assertThat(rule.htmlDescription().charAt(1)).isEqualTo('\u00E0');
+    assertThat(rule.htmlDescription().charAt(2)).isEqualTo('\u0026');
+  }
+
+  @Test
+  public void support_deprecated_format() throws UnsupportedEncodingException {
+    // the deprecated format uses some attributes instead of nodes
+    InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/deprecated.xml");
+    RulesDefinition.Repository repository = load(input, Charsets.UTF_8.name());
+
+    assertThat(repository.rules()).hasSize(1);
+    RulesDefinition.Rule rule = repository.rules().get(0);
+    assertThat(rule.key()).isEqualTo("org.sonar.it.checkstyle.MethodsCountCheck");
+    assertThat(rule.internalKey()).isEqualTo("Checker/TreeWalker/org.sonar.it.checkstyle.MethodsCountCheck");
+    assertThat(rule.severity()).isEqualTo(Severity.CRITICAL);
+    assertThat(rule.htmlDescription()).isEqualTo("Count methods");
+    assertThat(rule.param("minMethodsCount")).isNotNull();
+  }
+}
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/deprecated.xml b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/deprecated.xml
deleted file mode 100644 (file)
index 2938ea7..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<rules>
-  <rule key="org.sonar.it.checkstyle.MethodsCountCheck" priority="CRITICAL">
-    <name>Methods Count Check</name>
-    <configKey>Checker/TreeWalker/org.sonar.it.checkstyle.MethodsCountCheck</configKey>
-    <description>Count methods</description>
-    <param key="minMethodsCount" type="i">
-      <description>description of param</description>
-    </param>
-  </rule>
-</rules>
\ No newline at end of file
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/rules.xml b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/rules.xml
deleted file mode 100644 (file)
index 28ea701..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<rules>
-  <rule>
-    <!-- with exhaustive fields -->
-    <key>complete</key>
-    <name>Complete</name>
-    <description>
-      <![CDATA[Description of Complete]]>
-    </description>
-    <internalKey>Checker/TreeWalker/LocalVariableName</internalKey>
-    <severity>BLOCKER</severity>
-    <cardinality>MULTIPLE</cardinality>
-    <status>BETA</status>
-    <tag>style</tag>
-    <tag>security</tag>
-    <param>
-      <key>tokens</key>
-      <description>
-        <![CDATA[
-          Controls whether the check applies to variable declarations or catch clause parameters
-          ]]>
-      </description>
-    </param>
-    <param>
-      <key>ignore</key>
-      <description>
-        Ignore ?
-      </description>
-      <defaultValue>false</defaultValue>
-    </param>
-  </rule>
-
-
-  <rule>
-    <!-- with only required fields -->
-    <key>minimal</key>
-    <name>Minimal</name>
-    <description>
-      <![CDATA[Description of Minimal]]>
-    </description>
-  </rule>
-</rules>
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/utf8.xml b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsFromXmlTest/utf8.xml
deleted file mode 100644 (file)
index 6197e03..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<rules>
-  <rule>
-    <key>com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck</key>
-    <priority>BLOCKER</priority>
-    <configKey>Checker/TreeWalker/LocalVariableName</configKey>
-    <name>M &amp; M</name>
-    <description>
-      <![CDATA[éà&]]>
-    </description>
-  </rule>
-</rules>
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/deprecated.xml b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/deprecated.xml
new file mode 100644 (file)
index 0000000..2938ea7
--- /dev/null
@@ -0,0 +1,10 @@
+<rules>
+  <rule key="org.sonar.it.checkstyle.MethodsCountCheck" priority="CRITICAL">
+    <name>Methods Count Check</name>
+    <configKey>Checker/TreeWalker/org.sonar.it.checkstyle.MethodsCountCheck</configKey>
+    <description>Count methods</description>
+    <param key="minMethodsCount" type="i">
+      <description>description of param</description>
+    </param>
+  </rule>
+</rules>
\ No newline at end of file
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/invalid.xml b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/invalid.xml
new file mode 100644 (file)
index 0000000..de3d00c
--- /dev/null
@@ -0,0 +1,2 @@
+<rules>
+  <rule>
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/rules.xml b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/rules.xml
new file mode 100644 (file)
index 0000000..28ea701
--- /dev/null
@@ -0,0 +1,41 @@
+<rules>
+  <rule>
+    <!-- with exhaustive fields -->
+    <key>complete</key>
+    <name>Complete</name>
+    <description>
+      <![CDATA[Description of Complete]]>
+    </description>
+    <internalKey>Checker/TreeWalker/LocalVariableName</internalKey>
+    <severity>BLOCKER</severity>
+    <cardinality>MULTIPLE</cardinality>
+    <status>BETA</status>
+    <tag>style</tag>
+    <tag>security</tag>
+    <param>
+      <key>tokens</key>
+      <description>
+        <![CDATA[
+          Controls whether the check applies to variable declarations or catch clause parameters
+          ]]>
+      </description>
+    </param>
+    <param>
+      <key>ignore</key>
+      <description>
+        Ignore ?
+      </description>
+      <defaultValue>false</defaultValue>
+    </param>
+  </rule>
+
+
+  <rule>
+    <!-- with only required fields -->
+    <key>minimal</key>
+    <name>Minimal</name>
+    <description>
+      <![CDATA[Description of Minimal]]>
+    </description>
+  </rule>
+</rules>
diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/utf8.xml b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/utf8.xml
new file mode 100644 (file)
index 0000000..6197e03
--- /dev/null
@@ -0,0 +1,11 @@
+<rules>
+  <rule>
+    <key>com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck</key>
+    <priority>BLOCKER</priority>
+    <configKey>Checker/TreeWalker/LocalVariableName</configKey>
+    <name>M &amp; M</name>
+    <description>
+      <![CDATA[éà&]]>
+    </description>
+  </rule>
+</rules>
index d1125b423cd2e4f481e30a00dd9d329c401dc298..e550c817e90b75a71dc2b31f02dd470a26553e08 100644 (file)
@@ -31,6 +31,8 @@ import org.sonar.api.resources.Languages;
 import org.sonar.api.resources.ResourceTypes;
 import org.sonar.api.rules.AnnotationRuleParser;
 import org.sonar.api.rules.XMLRuleParser;
+import org.sonar.api.server.rule.RulesDefinitionI18nLoader;
+import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
 import org.sonar.api.utils.Durations;
 import org.sonar.api.utils.HttpDownloader;
 import org.sonar.api.utils.UriReader;
@@ -262,6 +264,8 @@ class ServerComponents {
     pico.addSingleton(RuleSearchWsHandler.class);
     pico.addSingleton(AddTagsWsHandler.class);
     pico.addSingleton(RemoveTagsWsHandler.class);
+    pico.addSingleton(RulesDefinitionXmlLoader.class);
+    pico.addSingleton(RulesDefinitionI18nLoader.class);
 
     // rule tags
     pico.addSingleton(ESRuleTags.class);