001/*
002 * SonarQube
003 * Copyright (C) 2009-2017 SonarSource SA
004 * mailto:info AT sonarsource DOT com
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.rules;
021
022import com.google.common.base.Strings;
023import java.io.File;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.InputStreamReader;
027import java.io.Reader;
028import java.util.ArrayList;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import javax.xml.stream.XMLInputFactory;
033import javax.xml.stream.XMLStreamException;
034import org.apache.commons.io.FileUtils;
035import org.apache.commons.lang.StringUtils;
036import org.codehaus.staxmate.SMInputFactory;
037import org.codehaus.staxmate.in.SMHierarchicCursor;
038import org.codehaus.staxmate.in.SMInputCursor;
039import org.sonar.api.PropertyType;
040import org.sonar.api.ce.ComputeEngineSide;
041import org.sonar.api.server.ServerSide;
042import org.sonar.api.utils.SonarException;
043import org.sonar.check.Cardinality;
044
045import static java.nio.charset.StandardCharsets.UTF_8;
046
047/**
048 * @since 2.3
049 * @deprecated in 4.2. Replaced by org.sonar.api.server.rule.RulesDefinition and org.sonar.api.server.rule.RulesDefinitionXmlLoader
050 */
051@Deprecated
052@ServerSide
053@ComputeEngineSide
054public final class XMLRuleParser {
055  private static final Map<String, String> TYPE_MAP = typeMapWithDeprecatedValues();
056
057  public List<Rule> parse(File file) {
058    try (Reader reader = new InputStreamReader(FileUtils.openInputStream(file), UTF_8)) {
059      return parse(reader);
060
061    } catch (IOException e) {
062      throw new SonarException("Fail to load the file: " + file, e);
063    }
064  }
065
066  /**
067   * Warning : the input stream is closed in this method
068   */
069  public List<Rule> parse(InputStream input) {
070    try (Reader reader = new InputStreamReader(input, UTF_8)) {
071      return parse(reader);
072
073    } catch (IOException e) {
074      throw new SonarException("Fail to load the xml stream", e);
075    }
076  }
077
078  public List<Rule> parse(Reader reader) {
079    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
080    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
081    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
082    // just so it won't try to load DTD in if there's DOCTYPE
083    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
084    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
085    SMInputFactory inputFactory = new SMInputFactory(xmlFactory);
086    try {
087      SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
088      rootC.advance(); // <rules>
089      List<Rule> rules = new ArrayList<>();
090
091      SMInputCursor rulesC = rootC.childElementCursor("rule");
092      while (rulesC.getNext() != null) {
093        // <rule>
094        Rule rule = Rule.create();
095        rules.add(rule);
096
097        processRule(rule, rulesC);
098      }
099      return rules;
100
101    } catch (XMLStreamException e) {
102      throw new SonarException("XML is not valid", e);
103    }
104  }
105
106  private static void processRule(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
107    /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
108    String keyAttribute = ruleC.getAttrValue("key");
109    if (StringUtils.isNotBlank(keyAttribute)) {
110      rule.setKey(StringUtils.trim(keyAttribute));
111    }
112
113    /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
114    String priorityAttribute = ruleC.getAttrValue("priority");
115    if (StringUtils.isNotBlank(priorityAttribute)) {
116      rule.setSeverity(RulePriority.valueOf(StringUtils.trim(priorityAttribute)));
117    }
118
119    List<String> tags = new ArrayList<>();
120    SMInputCursor cursor = ruleC.childElementCursor();
121
122    while (cursor.getNext() != null) {
123      String nodeName = cursor.getLocalName();
124
125      if (StringUtils.equalsIgnoreCase("name", nodeName)) {
126        rule.setName(StringUtils.trim(cursor.collectDescendantText(false)));
127
128      } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
129        rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false)));
130
131      } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
132        rule.setKey(StringUtils.trim(cursor.collectDescendantText(false)));
133
134      } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
135        rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false)));
136
137      } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
138        rule.setSeverity(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
139
140      } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
141        rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
142
143      } else if (StringUtils.equalsIgnoreCase("status", nodeName)) {
144        rule.setStatus(StringUtils.trim(cursor.collectDescendantText(false)));
145
146      } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
147        processParameter(rule, cursor);
148
149      } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) {
150        tags.add(StringUtils.trim(cursor.collectDescendantText(false)));
151      }
152    }
153    if (Strings.isNullOrEmpty(rule.getKey())) {
154      throw new SonarException("Node <key> is missing in <rule>");
155    }
156    rule.setTags(tags.toArray(new String[tags.size()]));
157  }
158
159  private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
160    RuleParam param = rule.createParameter();
161
162    String keyAttribute = ruleC.getAttrValue("key");
163    if (StringUtils.isNotBlank(keyAttribute)) {
164      /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
165      param.setKey(StringUtils.trim(keyAttribute));
166    }
167
168    String typeAttribute = ruleC.getAttrValue("type");
169    if (StringUtils.isNotBlank(typeAttribute)) {
170      /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
171      param.setType(type(StringUtils.trim(typeAttribute)));
172    }
173
174    SMInputCursor paramC = ruleC.childElementCursor();
175    while (paramC.getNext() != null) {
176      String propNodeName = paramC.getLocalName();
177      String propText = StringUtils.trim(paramC.collectDescendantText(false));
178      if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
179        param.setKey(propText);
180
181      } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
182        param.setDescription(propText);
183
184      } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
185        param.setType(type(propText));
186
187      } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
188        param.setDefaultValue(propText);
189      }
190    }
191    if (StringUtils.isEmpty(param.getKey())) {
192      throw new SonarException("Node <key> is missing in <param>");
193    }
194  }
195
196  private static Map<String, String> typeMapWithDeprecatedValues() {
197    Map<String, String> map = new HashMap<>();
198    map.put("i", PropertyType.INTEGER.name());
199    map.put("s", PropertyType.STRING.name());
200    map.put("b", PropertyType.BOOLEAN.name());
201    map.put("r", PropertyType.REGULAR_EXPRESSION.name());
202    map.put("s{}", "s{}");
203    map.put("i{}", "i{}");
204    for (PropertyType propertyType : PropertyType.values()) {
205      map.put(propertyType.name(), propertyType.name());
206    }
207    return map;
208  }
209
210  static String type(String type) {
211    String validType = TYPE_MAP.get(type);
212    if (null != validType) {
213      return validType;
214    }
215
216    if (type.matches(".\\[.+\\]")) {
217      return type;
218    }
219    throw new SonarException("Invalid property type [" + type + "]");
220  }
221
222}