You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

QProfileParser.java 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.qualityprofile;
  21. import com.google.common.collect.Lists;
  22. import java.io.Reader;
  23. import java.io.Writer;
  24. import java.util.ArrayList;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.Iterator;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Objects;
  31. import java.util.Set;
  32. import java.util.stream.Collectors;
  33. import javax.xml.stream.XMLInputFactory;
  34. import javax.xml.stream.XMLStreamException;
  35. import org.apache.commons.lang.StringUtils;
  36. import org.codehaus.staxmate.SMInputFactory;
  37. import org.codehaus.staxmate.in.SMHierarchicCursor;
  38. import org.codehaus.staxmate.in.SMInputCursor;
  39. import org.sonar.api.rule.RuleKey;
  40. import org.sonar.api.server.ServerSide;
  41. import org.sonar.api.utils.text.XmlWriter;
  42. import org.sonar.db.qualityprofile.ExportRuleDto;
  43. import org.sonar.db.qualityprofile.ExportRuleParamDto;
  44. import org.sonar.db.qualityprofile.QProfileDto;
  45. @ServerSide
  46. public class QProfileParser {
  47. private static final String ATTRIBUTE_PROFILE = "profile";
  48. private static final String ATTRIBUTE_NAME = "name";
  49. private static final String ATTRIBUTE_LANGUAGE = "language";
  50. private static final String ATTRIBUTE_RULES = "rules";
  51. private static final String ATTRIBUTE_RULE = "rule";
  52. private static final String ATTRIBUTE_REPOSITORY_KEY = "repositoryKey";
  53. private static final String ATTRIBUTE_KEY = "key";
  54. private static final String ATTRIBUTE_PRIORITY = "priority";
  55. private static final String ATTRIBUTE_TEMPLATE_KEY = "templateKey";
  56. private static final String ATTRIBUTE_TYPE = "type";
  57. private static final String ATTRIBUTE_DESCRIPTION = "description";
  58. private static final String ATTRIBUTE_PARAMETERS = "parameters";
  59. private static final String ATTRIBUTE_PARAMETER = "parameter";
  60. private static final String ATTRIBUTE_PARAMETER_KEY = "key";
  61. private static final String ATTRIBUTE_PARAMETER_VALUE = "value";
  62. public void writeXml(Writer writer, QProfileDto profile, Iterator<ExportRuleDto> rulesToExport) {
  63. XmlWriter xml = XmlWriter.of(writer).declaration();
  64. xml.begin(ATTRIBUTE_PROFILE);
  65. xml.prop(ATTRIBUTE_NAME, profile.getName());
  66. xml.prop(ATTRIBUTE_LANGUAGE, profile.getLanguage());
  67. xml.begin(ATTRIBUTE_RULES);
  68. while (rulesToExport.hasNext()) {
  69. ExportRuleDto ruleToExport = rulesToExport.next();
  70. xml.begin(ATTRIBUTE_RULE);
  71. xml.prop(ATTRIBUTE_REPOSITORY_KEY, ruleToExport.getRuleKey().repository());
  72. xml.prop(ATTRIBUTE_KEY, ruleToExport.getRuleKey().rule());
  73. xml.prop(ATTRIBUTE_TYPE, ruleToExport.getRuleType().name());
  74. xml.prop(ATTRIBUTE_PRIORITY, ruleToExport.getSeverityString());
  75. if (ruleToExport.isCustomRule()) {
  76. xml.prop(ATTRIBUTE_NAME, ruleToExport.getName());
  77. xml.prop(ATTRIBUTE_TEMPLATE_KEY, ruleToExport.getTemplateRuleKey().rule());
  78. xml.prop(ATTRIBUTE_DESCRIPTION, ruleToExport.getDescription());
  79. }
  80. xml.begin(ATTRIBUTE_PARAMETERS);
  81. for (ExportRuleParamDto param : ruleToExport.getParams()) {
  82. xml
  83. .begin(ATTRIBUTE_PARAMETER)
  84. .prop(ATTRIBUTE_PARAMETER_KEY, param.getKey())
  85. .prop(ATTRIBUTE_PARAMETER_VALUE, param.getValue())
  86. .end();
  87. }
  88. xml.end(ATTRIBUTE_PARAMETERS);
  89. xml.end(ATTRIBUTE_RULE);
  90. }
  91. xml.end(ATTRIBUTE_RULES).end(ATTRIBUTE_PROFILE).close();
  92. }
  93. public ImportedQProfile readXml(Reader reader) {
  94. List<ImportedRule> rules = Lists.newArrayList();
  95. String profileName = null;
  96. String profileLang = null;
  97. try {
  98. SMInputFactory inputFactory = initStax();
  99. SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
  100. rootC.advance(); // <profile>
  101. if (!ATTRIBUTE_PROFILE.equals(rootC.getLocalName())) {
  102. throw new IllegalArgumentException("Backup XML is not valid. Root element must be <profile>.");
  103. }
  104. SMInputCursor cursor = rootC.childElementCursor();
  105. while (cursor.getNext() != null) {
  106. String nodeName = cursor.getLocalName();
  107. if (StringUtils.equals(ATTRIBUTE_NAME, nodeName)) {
  108. profileName = StringUtils.trim(cursor.collectDescendantText(false));
  109. } else if (StringUtils.equals(ATTRIBUTE_LANGUAGE, nodeName)) {
  110. profileLang = StringUtils.trim(cursor.collectDescendantText(false));
  111. } else if (StringUtils.equals(ATTRIBUTE_RULES, nodeName)) {
  112. SMInputCursor rulesCursor = cursor.childElementCursor("rule");
  113. rules = parseRuleActivations(rulesCursor);
  114. }
  115. }
  116. } catch (XMLStreamException e) {
  117. throw new IllegalArgumentException("Fail to restore Quality profile backup, XML document is not well formed", e);
  118. }
  119. return new ImportedQProfile(profileName, profileLang, rules);
  120. }
  121. private static SMInputFactory initStax() {
  122. XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
  123. xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
  124. xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
  125. // just so it won't try to load DTD in if there's DOCTYPE
  126. xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
  127. xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
  128. return new SMInputFactory(xmlFactory);
  129. }
  130. private static List<ImportedRule> parseRuleActivations(SMInputCursor rulesCursor) throws XMLStreamException {
  131. List<ImportedRule> activations = new ArrayList<>();
  132. Set<RuleKey> activatedKeys = new HashSet<>();
  133. List<RuleKey> duplicatedKeys = new ArrayList<>();
  134. while (rulesCursor.getNext() != null) {
  135. SMInputCursor ruleCursor = rulesCursor.childElementCursor();
  136. Map<String, String> parameters = new HashMap<>();
  137. String repositoryKey = null;
  138. String key = null;
  139. String templateKey = null;
  140. ImportedRule rule = new ImportedRule();
  141. while (ruleCursor.getNext() != null) {
  142. String nodeName = ruleCursor.getLocalName();
  143. if (StringUtils.equals(ATTRIBUTE_REPOSITORY_KEY, nodeName)) {
  144. repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
  145. } else if (StringUtils.equals(ATTRIBUTE_KEY, nodeName)) {
  146. key = StringUtils.trim(ruleCursor.collectDescendantText(false));
  147. } else if (StringUtils.equals(ATTRIBUTE_TEMPLATE_KEY, nodeName)) {
  148. templateKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
  149. } else if (StringUtils.equals(ATTRIBUTE_NAME, nodeName)) {
  150. rule.setName(StringUtils.trim(ruleCursor.collectDescendantText(false)));
  151. } else if (StringUtils.equals(ATTRIBUTE_TYPE, nodeName)) {
  152. rule.setType(StringUtils.trim(ruleCursor.collectDescendantText(false)));
  153. } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION, nodeName)) {
  154. rule.setDescription(StringUtils.trim(ruleCursor.collectDescendantText(false)));
  155. } else if (StringUtils.equals(ATTRIBUTE_PRIORITY, nodeName)) {
  156. rule.setSeverity(StringUtils.trim(ruleCursor.collectDescendantText(false)));
  157. } else if (StringUtils.equals(ATTRIBUTE_PARAMETERS, nodeName)) {
  158. SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_PARAMETER);
  159. readParameters(propsCursor, parameters);
  160. rule.setParameters(parameters);
  161. }
  162. }
  163. RuleKey ruleKey = RuleKey.of(repositoryKey, key);
  164. rule.setRuleKey(ruleKey);
  165. if (templateKey != null) {
  166. rule.setTemplateKey(RuleKey.of(repositoryKey, templateKey));
  167. }
  168. if (activatedKeys.contains(ruleKey)) {
  169. duplicatedKeys.add(ruleKey);
  170. }
  171. activatedKeys.add(ruleKey);
  172. activations.add(rule);
  173. }
  174. if (!duplicatedKeys.isEmpty()) {
  175. throw new IllegalArgumentException("The quality profile cannot be restored as it contains duplicates for the following rules: " +
  176. duplicatedKeys.stream().map(RuleKey::toString).filter(Objects::nonNull).collect(Collectors.joining(", ")));
  177. }
  178. return activations;
  179. }
  180. private static void readParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException {
  181. while (propsCursor.getNext() != null) {
  182. SMInputCursor propCursor = propsCursor.childElementCursor();
  183. String key = null;
  184. String value = null;
  185. while (propCursor.getNext() != null) {
  186. String nodeName = propCursor.getLocalName();
  187. if (StringUtils.equals(ATTRIBUTE_PARAMETER_KEY, nodeName)) {
  188. key = StringUtils.trim(propCursor.collectDescendantText(false));
  189. } else if (StringUtils.equals(ATTRIBUTE_PARAMETER_VALUE, nodeName)) {
  190. value = StringUtils.trim(propCursor.collectDescendantText(false));
  191. }
  192. }
  193. if (key != null) {
  194. parameters.put(key, value);
  195. }
  196. }
  197. }
  198. }