diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2021-05-27 15:10:28 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-05-28 20:21:38 +0000 |
commit | 95d416a5748d0dd9255d730b6ebf357c5edc628e (patch) | |
tree | 4d6a6d5771494d9d59f81e0a544ad1d4f4719bdf /sonar-plugin-api | |
parent | 2fb8b360e3eb2252ea9c2e3f984f1ddd0130fc90 (diff) | |
download | sonarqube-95d416a5748d0dd9255d730b6ebf357c5edc628e.tar.gz sonarqube-95d416a5748d0dd9255d730b6ebf357c5edc628e.zip |
SONAR-14882 Remove Staxmate and Woodstox dependencies from plugin API
Diffstat (limited to 'sonar-plugin-api')
6 files changed, 357 insertions, 257 deletions
diff --git a/sonar-plugin-api/build.gradle b/sonar-plugin-api/build.gradle index bff7eec5a3b..04612e58427 100644 --- a/sonar-plugin-api/build.gradle +++ b/sonar-plugin-api/build.gradle @@ -16,10 +16,6 @@ dependencies { // shaded, but not relocated compile project(':sonar-check-api') - shadow 'org.codehaus.staxmate:staxmate' - shadow 'org.codehaus.woodstox:stax2-api' - shadow 'org.codehaus.woodstox:woodstox-core-lgpl' - compileOnly 'ch.qos.logback:logback-classic' compileOnly 'com.google.code.findbugs:jsr305' compileOnly 'javax.servlet:javax.servlet-api' diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/XMLProfileParser.java b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/XMLProfileParser.java index d2459c75785..bf78c8dd0df 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/XMLProfileParser.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/XMLProfileParser.java @@ -25,12 +25,13 @@ import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; +import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; 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.ce.ComputeEngineSide; import org.sonar.api.rules.ActiveRule; import org.sonar.api.rules.Rule; @@ -49,6 +50,11 @@ import org.sonar.api.utils.ValidationMessages; @ComputeEngineSide public class XMLProfileParser { + private static final String ELEMENT_PROFILE = "profile"; + private static final String ELEMENT_RULES = "rules"; + private static final String ELEMENT_RULE = "rule"; + private static final String ELEMENT_PARAMETERS = "parameters"; + private static final String ELEMENT_PARAMETER = "parameter"; private final RuleFinder ruleFinder; /** @@ -70,24 +76,16 @@ public class XMLProfileParser { } } - public RulesProfile parse(Reader reader, ValidationMessages messages) { + public RulesProfile parse(Reader inputReader, ValidationMessages messages) { RulesProfile profile = RulesProfile.create(); - SMInputFactory inputFactory = initStax(); + XMLInputFactory inputFactory = initStax(); try { - SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader); - rootC.advance(); // <profile> - SMInputCursor cursor = rootC.childElementCursor(); - while (cursor.getNext() != null) { - String nodeName = cursor.getLocalName(); - if (StringUtils.equals("rules", nodeName)) { - SMInputCursor rulesCursor = cursor.childElementCursor("rule"); - processRules(rulesCursor, profile, messages); - - } else if (StringUtils.equals("name", nodeName)) { - profile.setName(StringUtils.trim(cursor.collectDescendantText(false))); - - } else if (StringUtils.equals("language", nodeName)) { - profile.setLanguage(StringUtils.trim(cursor.collectDescendantText(false))); + final XMLEventReader reader = inputFactory.createXMLEventReader(inputReader); + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isStartElement() && event.asStartElement().getName() + .getLocalPart().equals(ELEMENT_PROFILE)) { + parseProfile(profile, reader, messages); } } } catch (XMLStreamException e) { @@ -97,6 +95,42 @@ public class XMLProfileParser { return profile; } + private void parseProfile(RulesProfile profile, final XMLEventReader reader, ValidationMessages messages) throws XMLStreamException { + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_PROFILE)) { + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if (ELEMENT_RULES.equals(elementName)) { + parseRules(profile, reader, messages); + } else if ("name".equals(elementName)) { + profile.setName(StringUtils.trim(reader.getElementText())); + } else if ("language".equals(elementName)) { + profile.setLanguage(StringUtils.trim(reader.getElementText())); + } + } + } + } + + private void parseRules(RulesProfile profile, XMLEventReader reader, ValidationMessages messages) throws XMLStreamException { + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_RULES)) { + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if (ELEMENT_RULE.equals(elementName)) { + parseRule(profile, reader, messages); + } + } + } + } + private static void checkProfile(RulesProfile profile, ValidationMessages messages) { if (StringUtils.isBlank(profile.getName())) { messages.addErrorText("The mandatory node <name> is missing."); @@ -106,56 +140,55 @@ public class XMLProfileParser { } } - private static SMInputFactory initStax() { + private static XMLInputFactory initStax() { 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); - return new SMInputFactory(xmlFactory); + return xmlFactory; } - private void processRules(SMInputCursor rulesCursor, RulesProfile profile, ValidationMessages messages) throws XMLStreamException { + private void parseRule(RulesProfile profile, XMLEventReader reader, ValidationMessages messages) throws XMLStreamException { Map<String, String> parameters = new HashMap<>(); - while (rulesCursor.getNext() != null) { - SMInputCursor ruleCursor = rulesCursor.childElementCursor(); - - String repositoryKey = null; - String key = null; - RulePriority priority = null; - parameters.clear(); - - while (ruleCursor.getNext() != null) { - String nodeName = ruleCursor.getLocalName(); - - if (StringUtils.equals("repositoryKey", nodeName)) { - repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false)); - - } else if (StringUtils.equals("key", nodeName)) { - key = StringUtils.trim(ruleCursor.collectDescendantText(false)); - - } else if (StringUtils.equals("priority", nodeName)) { - priority = RulePriority.valueOf(StringUtils.trim(ruleCursor.collectDescendantText(false))); + String repositoryKey = null; + String key = null; + RulePriority priority = null; + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_RULE)) { + buildRule(profile, messages, parameters, repositoryKey, key, priority); + return; + } - } else if (StringUtils.equals("parameters", nodeName)) { - SMInputCursor propsCursor = ruleCursor.childElementCursor("parameter"); - processParameters(propsCursor, parameters); + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if ("repositoryKey".equals(elementName)) { + repositoryKey = StringUtils.trim(reader.getElementText()); + } else if ("key".equals(elementName)) { + key = StringUtils.trim(reader.getElementText()); + } else if ("priority".equals(elementName)) { + priority = RulePriority.valueOf(StringUtils.trim(reader.getElementText())); + } else if (ELEMENT_PARAMETERS.equals(elementName)) { + processParameters(parameters, reader); } } + } + } - Rule rule = ruleFinder.findByKey(repositoryKey, key); - if (rule == null) { - messages.addWarningText("Rule not found: " + ruleToString(repositoryKey, key)); - - } else { - ActiveRule activeRule = profile.activateRule(rule, priority); - for (Map.Entry<String, String> entry : parameters.entrySet()) { - if (rule.getParam(entry.getKey()) == null) { - messages.addWarningText("The parameter '" + entry.getKey() + "' does not exist in the rule: " + ruleToString(repositoryKey, key)); - } else { - activeRule.setParameter(entry.getKey(), entry.getValue()); - } + private void buildRule(RulesProfile profile, ValidationMessages messages, Map<String, String> parameters, String repositoryKey, String key, @Nullable RulePriority priority) { + Rule rule = ruleFinder.findByKey(repositoryKey, key); + if (rule == null) { + messages.addWarningText("Rule not found: " + ruleToString(repositoryKey, key)); + } else { + ActiveRule activeRule = profile.activateRule(rule, priority); + for (Map.Entry<String, String> entry : parameters.entrySet()) { + if (rule.getParam(entry.getKey()) == null) { + messages.addWarningText("The parameter '" + entry.getKey() + "' does not exist in the rule: " + ruleToString(repositoryKey, key)); + } else { + activeRule.setParameter(entry.getKey(), entry.getValue()); } } } @@ -165,22 +198,41 @@ public class XMLProfileParser { return "[repository=" + repositoryKey + ", key=" + key + "]"; } - private static void processParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException { - while (propsCursor.getNext() != null) { - SMInputCursor propCursor = propsCursor.childElementCursor(); - String key = null; - String value = null; - while (propCursor.getNext() != null) { - String nodeName = propCursor.getLocalName(); - if (StringUtils.equals("key", nodeName)) { - key = StringUtils.trim(propCursor.collectDescendantText(false)); - - } else if (StringUtils.equals("value", nodeName)) { - value = StringUtils.trim(propCursor.collectDescendantText(false)); + private static void processParameters(Map<String, String> parameters, XMLEventReader reader) throws XMLStreamException { + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_PARAMETERS)) { + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if (ELEMENT_PARAMETER.equals(elementName)) { + processParameter(parameters, reader); } } - if (key != null) { - parameters.put(key, value); + } + } + + private static void processParameter(Map<String, String> parameters, XMLEventReader reader) throws XMLStreamException { + String key = null; + String value = null; + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_PARAMETER)) { + if (key != null) { + parameters.put(key, value); + } + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if ("key".equals(elementName)) { + key = StringUtils.trim(reader.getElementText()); + } else if ("value".equals(elementName)) { + value = StringUtils.trim(reader.getElementText()); + } } } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/XMLRuleParser.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/XMLRuleParser.java index 374d9ba9aad..466b3e4fbec 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/rules/XMLRuleParser.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/rules/XMLRuleParser.java @@ -28,13 +28,16 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; 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.PropertyType; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.server.ServerSide; @@ -52,6 +55,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; @ComputeEngineSide public final class XMLRuleParser { private static final Map<String, String> TYPE_MAP = typeMapWithDeprecatedValues(); + private static final String ELEMENT_RULES = "rules"; + private static final String ELEMENT_RULE = "rule"; + private static final String ELEMENT_PARAM = "param"; public List<Rule> parse(File file) { try (Reader reader = new InputStreamReader(Files.newInputStream(file.toPath()), UTF_8)) { @@ -74,26 +80,22 @@ public final class XMLRuleParser { } } - public List<Rule> parse(Reader reader) { + public List<Rule> parse(Reader inputReader) { 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> + final XMLEventReader reader = xmlFactory.createXMLEventReader(inputReader); List<Rule> rules = new ArrayList<>(); - - SMInputCursor rulesC = rootC.childElementCursor("rule"); - while (rulesC.getNext() != null) { - // <rule> - Rule rule = Rule.create(); - rules.add(rule); - - processRule(rule, rulesC); + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isStartElement() && event.asStartElement().getName() + .getLocalPart().equals(ELEMENT_RULES)) { + parseRules(rules, reader); + } } return rules; @@ -102,51 +104,77 @@ public final class XMLRuleParser { } } - private static void processRule(Rule rule, SMInputCursor ruleC) throws XMLStreamException { + private void parseRules(List<Rule> rules, XMLEventReader reader) throws XMLStreamException { + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_RULES)) { + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + switch (elementName) { + case ELEMENT_RULE: + Rule rule = Rule.create(); + rules.add(rule); + parseRule(rule, element, reader); + break; + } + } + } + } + + private static void parseRule(Rule rule, StartElement ruleElement, XMLEventReader reader) throws XMLStreamException { /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ - String keyAttribute = ruleC.getAttrValue("key"); - if (StringUtils.isNotBlank(keyAttribute)) { - rule.setKey(StringUtils.trim(keyAttribute)); + Attribute keyAttribute = ruleElement.getAttributeByName(new QName("key")); + if (keyAttribute != null && StringUtils.isNotBlank(keyAttribute.getValue())) { + rule.setKey(StringUtils.trim(keyAttribute.getValue())); } /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ - String priorityAttribute = ruleC.getAttrValue("priority"); - if (StringUtils.isNotBlank(priorityAttribute)) { - rule.setSeverity(RulePriority.valueOf(StringUtils.trim(priorityAttribute))); + Attribute priorityAttribute = ruleElement.getAttributeByName(new QName("priority")); + if (priorityAttribute != null && StringUtils.isNotBlank(priorityAttribute.getValue())) { + rule.setSeverity(RulePriority.valueOf(StringUtils.trim(priorityAttribute.getValue()))); } List<String> tags = new ArrayList<>(); - SMInputCursor cursor = ruleC.childElementCursor(); - - while (cursor.getNext() != null) { - String nodeName = cursor.getLocalName(); - - if (StringUtils.equalsIgnoreCase("name", nodeName)) { - rule.setName(StringUtils.trim(cursor.collectDescendantText(false))); - - } else if (StringUtils.equalsIgnoreCase("description", nodeName)) { - rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false))); - - } else if (StringUtils.equalsIgnoreCase("key", nodeName)) { - rule.setKey(StringUtils.trim(cursor.collectDescendantText(false))); - - } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) { - rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false))); - - } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) { - rule.setSeverity(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false)))); - - } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) { - rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false)))); - - } else if (StringUtils.equalsIgnoreCase("status", nodeName)) { - rule.setStatus(StringUtils.trim(cursor.collectDescendantText(false))); - - } else if (StringUtils.equalsIgnoreCase("param", nodeName)) { - processParameter(rule, cursor); - - } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) { - tags.add(StringUtils.trim(cursor.collectDescendantText(false))); + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_RULE)) { + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart().toLowerCase(Locale.ENGLISH); + switch (elementName) { + case "name": + rule.setName(StringUtils.trim(reader.getElementText())); + break; + case "description": + rule.setDescription(StringUtils.trim(reader.getElementText())); + break; + case "key": + rule.setKey(StringUtils.trim(reader.getElementText())); + break; + case "configkey": + rule.setConfigKey(StringUtils.trim(reader.getElementText())); + break; + case "priority": + rule.setSeverity(RulePriority.valueOf(StringUtils.trim(reader.getElementText()))); + break; + case "cardinality": + rule.setCardinality(Cardinality.valueOf(StringUtils.trim(reader.getElementText()))); + break; + case "status": + rule.setStatus(StringUtils.trim(reader.getElementText())); + break; + case ELEMENT_PARAM: + processParameter(rule, element, reader); + break; + case "tag": + tags.add(StringUtils.trim(reader.getElementText())); + break; + } } } if (rule.getKey() == null || rule.getKey().isEmpty()) { @@ -155,36 +183,43 @@ public final class XMLRuleParser { rule.setTags(tags.toArray(new String[tags.size()])); } - private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException { + private static void processParameter(Rule rule, StartElement paramElement, XMLEventReader reader) throws XMLStreamException { RuleParam param = rule.createParameter(); - String keyAttribute = ruleC.getAttrValue("key"); - if (StringUtils.isNotBlank(keyAttribute)) { + Attribute keyAttribute = paramElement.getAttributeByName(new QName("key")); + if (keyAttribute != null && StringUtils.isNotBlank(keyAttribute.getValue())) { /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ - param.setKey(StringUtils.trim(keyAttribute)); + param.setKey(StringUtils.trim(keyAttribute.getValue())); } - String typeAttribute = ruleC.getAttrValue("type"); - if (StringUtils.isNotBlank(typeAttribute)) { + Attribute typeAttribute = paramElement.getAttributeByName(new QName("type")); + if (typeAttribute != null && StringUtils.isNotBlank(typeAttribute.getValue())) { /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ - param.setType(type(StringUtils.trim(typeAttribute))); + param.setType(type(StringUtils.trim(typeAttribute.getValue()))); } - 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.setKey(propText); - - } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) { - param.setDescription(propText); - - } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) { - param.setType(type(propText)); - - } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) { - param.setDefaultValue(propText); + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_PARAM)) { + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart().toLowerCase(Locale.ENGLISH); + switch (elementName) { + case "key": + param.setKey(StringUtils.trim(reader.getElementText())); + break; + case "description": + param.setDescription(StringUtils.trim(reader.getElementText())); + break; + case "type": + param.setType(type(StringUtils.trim(reader.getElementText()))); + break; + case "defaultvalue": + param.setDefaultValue(StringUtils.trim(reader.getElementText())); + break; + } } } if (StringUtils.isEmpty(param.getKey())) { 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 index 33c178bb5b0..fb5e0494766 100644 --- 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 @@ -27,11 +27,14 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; -import org.codehaus.staxmate.SMInputFactory; -import org.codehaus.staxmate.in.SMHierarchicCursor; -import org.codehaus.staxmate.in.SMInputCursor; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; +import org.apache.commons.lang.StringUtils; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; @@ -42,7 +45,6 @@ import org.sonar.check.Cardinality; import org.sonarsource.api.sonarlint.SonarLintSide; import static java.lang.String.format; -import static org.apache.commons.lang.StringUtils.equalsIgnoreCase; import static org.apache.commons.lang.StringUtils.isNotBlank; import static org.apache.commons.lang.StringUtils.trim; @@ -184,6 +186,10 @@ import static org.apache.commons.lang.StringUtils.trim; @SonarLintSide public class RulesDefinitionXmlLoader { + private static final String ELEMENT_RULES = "rules"; + private static final String ELEMENT_RULE = "rule"; + private static final String ELEMENT_PARAM = "param"; + private enum DescriptionFormat { HTML, MARKDOWN } @@ -191,6 +197,7 @@ public class RulesDefinitionXmlLoader { /** * Loads rules by reading the XML input stream. The input stream is not always closed by the method, so it * should be handled by the caller. + * * @since 4.3 */ public void load(RulesDefinition.NewRepository repo, InputStream input, String encoding) { @@ -211,32 +218,47 @@ public class RulesDefinitionXmlLoader { /** * Loads rules by reading the XML input stream. The reader is not closed by the method, so it * should be handled by the caller. + * * @since 4.3 */ - public void load(RulesDefinition.NewRepository repo, Reader reader) { + public void load(RulesDefinition.NewRepository repo, Reader inputReader) { 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); + final XMLEventReader reader = xmlFactory.createXMLEventReader(inputReader); + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isStartElement() && event.asStartElement().getName() + .getLocalPart().equals(ELEMENT_RULES)) { + parseRules(repo, reader); + } } - } catch (XMLStreamException e) { throw new IllegalStateException("XML is not valid", e); } } - private static void processRule(RulesDefinition.NewRepository repo, SMInputCursor ruleC) throws XMLStreamException { + private static void parseRules(RulesDefinition.NewRepository repo, XMLEventReader reader) throws XMLStreamException { + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_RULES)) { + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if (ELEMENT_RULE.equals(elementName)) { + processRule(repo, element, reader); + } + } + } + } + + private static void processRule(RulesDefinition.NewRepository repo, StartElement ruleElement, XMLEventReader reader) throws XMLStreamException { String key = null; String name = null; String description = null; @@ -255,74 +277,71 @@ public class RulesDefinitionXmlLoader { List<String> tags = new ArrayList<>(); /* BACKWARD COMPATIBILITY WITH VERY OLD FORMAT */ - String keyAttribute = ruleC.getAttrValue("key"); - if (isNotBlank(keyAttribute)) { - key = trim(keyAttribute); + Attribute keyAttribute = ruleElement.getAttributeByName(new QName("key")); + if (keyAttribute != null && StringUtils.isNotBlank(keyAttribute.getValue())) { + key = trim(keyAttribute.getValue()); } - String priorityAttribute = ruleC.getAttrValue("priority"); - if (isNotBlank(priorityAttribute)) { - severity = trim(priorityAttribute); + Attribute priorityAttribute = ruleElement.getAttributeByName(new QName("priority")); + if (priorityAttribute != null && StringUtils.isNotBlank(priorityAttribute.getValue())) { + severity = trim(priorityAttribute.getValue()); } - SMInputCursor cursor = ruleC.childElementCursor(); - while (cursor.getNext() != null) { - String nodeName = cursor.getLocalName(); - - if (equalsIgnoreCase("name", nodeName)) { - name = nodeValue(cursor); - - } else if (equalsIgnoreCase("type", nodeName)) { - type = nodeValue(cursor); - - } else if (equalsIgnoreCase("description", nodeName)) { - description = nodeValue(cursor); - - } else if (equalsIgnoreCase("descriptionFormat", nodeName)) { - descriptionFormat = nodeValue(cursor); - - } else if (equalsIgnoreCase("key", nodeName)) { - key = nodeValue(cursor); - - } else if (equalsIgnoreCase("configKey", nodeName)) { - // deprecated field, replaced by internalKey - internalKey = nodeValue(cursor); - - } else if (equalsIgnoreCase("internalKey", nodeName)) { - internalKey = nodeValue(cursor); - - } else if (equalsIgnoreCase("priority", nodeName) || equalsIgnoreCase("severity", nodeName)) { - // "priority" is deprecated field and has been replaced by "severity" - severity = nodeValue(cursor); - - } else if (equalsIgnoreCase("cardinality", nodeName)) { - template = Cardinality.MULTIPLE == Cardinality.valueOf(nodeValue(cursor)); - - } else if (equalsIgnoreCase("gapDescription", nodeName) || equalsIgnoreCase("effortToFixDescription", nodeName)) { - gapDescription = nodeValue(cursor); - - } else if (equalsIgnoreCase("remediationFunction", nodeName) || equalsIgnoreCase("debtRemediationFunction", nodeName)) { - debtRemediationFunction = nodeValue(cursor); - - } else if (equalsIgnoreCase("remediationFunctionBaseEffort", nodeName) || equalsIgnoreCase("debtRemediationFunctionOffset", nodeName)) { - debtRemediationFunctionGapMultiplier = nodeValue(cursor); - - } else if (equalsIgnoreCase("remediationFunctionGapMultiplier", nodeName) || equalsIgnoreCase("debtRemediationFunctionCoefficient", nodeName)) { - debtRemediationFunctionBaseEffort = nodeValue(cursor); - - } else if (equalsIgnoreCase("status", nodeName)) { - String s = nodeValue(cursor); - if (s != null) { - status = RuleStatus.valueOf(s); + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_RULE)) { + buildRule(repo, key, name, description, descriptionFormat, internalKey, severity, type, status, template, + gapDescription, debtRemediationFunction, debtRemediationFunctionGapMultiplier, debtRemediationFunctionBaseEffort, params, tags); + return; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if ("name".equalsIgnoreCase(elementName)) { + name = StringUtils.trim(reader.getElementText()); + } else if ("type".equalsIgnoreCase(elementName)) { + type = StringUtils.trim(reader.getElementText()); + } else if ("description".equalsIgnoreCase(elementName)) { + description = StringUtils.trim(reader.getElementText()); + } else if ("descriptionFormat".equalsIgnoreCase(elementName)) { + descriptionFormat = StringUtils.trim(reader.getElementText()); + } else if ("key".equalsIgnoreCase(elementName)) { + key = StringUtils.trim(reader.getElementText()); + } else if ("configKey".equalsIgnoreCase(elementName)) { + // deprecated field, replaced by internalKey + internalKey = StringUtils.trim(reader.getElementText()); + } else if ("internalKey".equalsIgnoreCase(elementName)) { + internalKey = StringUtils.trim(reader.getElementText()); + } else if ("priority".equalsIgnoreCase(elementName) || "severity".equalsIgnoreCase(elementName)) { + // "priority" is deprecated field and has been replaced by "severity" + severity = StringUtils.trim(reader.getElementText()); + } else if ("cardinality".equalsIgnoreCase(elementName)) { + template = Cardinality.MULTIPLE == Cardinality.valueOf(StringUtils.trim(reader.getElementText())); + } else if ("gapDescription".equalsIgnoreCase(elementName) || "effortToFixDescription".equalsIgnoreCase(elementName)) { + gapDescription = StringUtils.trim(reader.getElementText()); + } else if ("remediationFunction".equalsIgnoreCase(elementName) || "debtRemediationFunction".equalsIgnoreCase(elementName)) { + debtRemediationFunction = StringUtils.trim(reader.getElementText()); + } else if ("remediationFunctionBaseEffort".equalsIgnoreCase(elementName) || "debtRemediationFunctionOffset".equalsIgnoreCase(elementName)) { + debtRemediationFunctionGapMultiplier = StringUtils.trim(reader.getElementText()); + } else if ("remediationFunctionGapMultiplier".equalsIgnoreCase(elementName) || "debtRemediationFunctionCoefficient".equalsIgnoreCase(elementName)) { + debtRemediationFunctionBaseEffort = StringUtils.trim(reader.getElementText()); + } else if ("status".equalsIgnoreCase(elementName)) { + String s = StringUtils.trim(reader.getElementText()); + if (s != null) { + status = RuleStatus.valueOf(s); + } + } else if (ELEMENT_PARAM.equalsIgnoreCase(elementName)) { + params.add(processParameter(element, reader)); + } else if ("tag".equalsIgnoreCase(elementName)) { + tags.add(StringUtils.trim(reader.getElementText())); } - - } else if (equalsIgnoreCase("param", nodeName)) { - params.add(processParameter(cursor)); - - } else if (equalsIgnoreCase("tag", nodeName)) { - tags.add(nodeValue(cursor)); } } + } + private static void buildRule(RulesDefinition.NewRepository repo, String key, String name, @Nullable String description, + String descriptionFormat, @Nullable String internalKey, String severity, @Nullable String type, RuleStatus status, + boolean template, @Nullable String gapDescription, @Nullable String debtRemediationFunction, @Nullable String debtRemediationFunctionGapMultiplier, + @Nullable String debtRemediationFunctionBaseEffort, List<ParamStruct> params, List<String> tags) { try { RulesDefinition.NewRule rule = repo.createRule(key) .setSeverity(severity) @@ -375,10 +394,6 @@ public class RulesDefinitionXmlLoader { } } - private static String nodeValue(SMInputCursor cursor) throws XMLStreamException { - return trim(cursor.collectDescendantText(false)); - } - private static class ParamStruct { String key; @@ -387,36 +402,38 @@ public class RulesDefinitionXmlLoader { RuleParamType type = RuleParamType.STRING; } - private static ParamStruct processParameter(SMInputCursor ruleC) throws XMLStreamException { + private static ParamStruct processParameter(StartElement paramElement, XMLEventReader reader) throws XMLStreamException { ParamStruct param = new ParamStruct(); // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT - String keyAttribute = ruleC.getAttrValue("key"); - if (isNotBlank(keyAttribute)) { - param.key = trim(keyAttribute); + Attribute keyAttribute = paramElement.getAttributeByName(new QName("key")); + if (keyAttribute != null && StringUtils.isNotBlank(keyAttribute.getValue())) { + param.key = StringUtils.trim(keyAttribute.getValue()); } // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT - String typeAttribute = ruleC.getAttrValue("type"); - if (isNotBlank(typeAttribute)) { - param.type = RuleParamType.parse(typeAttribute); + Attribute typeAttribute = paramElement.getAttributeByName(new QName("type")); + if (typeAttribute != null && StringUtils.isNotBlank(typeAttribute.getValue())) { + param.type = RuleParamType.parse(StringUtils.trim(typeAttribute.getValue())); } - SMInputCursor paramC = ruleC.childElementCursor(); - while (paramC.getNext() != null) { - String propNodeName = paramC.getLocalName(); - String propText = nodeValue(paramC); - if (equalsIgnoreCase("key", propNodeName)) { - param.key = propText; - - } else if (equalsIgnoreCase("description", propNodeName)) { - param.description = propText; - - } else if (equalsIgnoreCase("type", propNodeName)) { - param.type = RuleParamType.parse(propText); - - } else if (equalsIgnoreCase("defaultValue", propNodeName)) { - param.defaultValue = propText; + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ELEMENT_PARAM)) { + return param; + } + if (event.isStartElement()) { + final StartElement element = event.asStartElement(); + final String elementName = element.getName().getLocalPart(); + if ("key".equalsIgnoreCase(elementName)) { + param.key = StringUtils.trim(reader.getElementText()); + } else if ("description".equalsIgnoreCase(elementName)) { + param.description = StringUtils.trim(reader.getElementText()); + } else if ("type".equalsIgnoreCase(elementName)) { + param.type = RuleParamType.parse(StringUtils.trim(reader.getElementText())); + } else if ("defaultValue".equalsIgnoreCase(elementName)) { + param.defaultValue = StringUtils.trim(reader.getElementText()); + } } } return param; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/text/XmlWriter.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/text/XmlWriter.java index ad30878cb6b..4c4489dd32a 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/text/XmlWriter.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/text/XmlWriter.java @@ -47,7 +47,7 @@ public class XmlWriter { public XmlWriter declaration() { try { - stream.writeStartDocument(); + stream.writeStartDocument("UTF-8", "1.0"); return this; } catch (XMLStreamException e) { throw rethrow(e); diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/text/XmlWriterTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/text/XmlWriterTest.java index 2be3eebfc9f..ba0a3d4127c 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/utils/text/XmlWriterTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/text/XmlWriterTest.java @@ -41,25 +41,25 @@ public class XmlWriterTest { @Test public void declaration() { writer.declaration().begin("foo").end().close(); - expect("<?xml version='1.0' encoding='UTF-8'?><foo/>"); + expect("<?xml version=\"1.0\" encoding=\"UTF-8\"?><foo></foo>"); } @Test public void end_with_unused_parameter() { writer.begin("foo").end("foo").close(); - expect("<foo/>"); + expect("<foo></foo>"); } @Test public void only_root() { writer.begin("foo").end().close(); - expect("<foo/>"); + expect("<foo></foo>"); } @Test public void escape_value() { writer.prop("foo", "1<2 & 2>=2").close(); - expect("<foo>1<2 & 2>=2</foo>"); + expect("<foo>1<2 & 2>=2</foo>"); } @Test @@ -74,7 +74,7 @@ public class XmlWriterTest { .prop("nullNumber", (Number) null) .prop("nullString", (String) null) .end().close(); - expect("<root/>"); + expect("<root></root>"); } @Test |