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.annotations.VisibleForTesting;
023import com.google.common.base.Strings;
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.io.Reader;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033import javax.xml.stream.XMLInputFactory;
034import javax.xml.stream.XMLStreamException;
035import org.apache.commons.io.FileUtils;
036import org.apache.commons.lang.StringUtils;
037import org.codehaus.staxmate.SMInputFactory;
038import org.codehaus.staxmate.in.SMHierarchicCursor;
039import org.codehaus.staxmate.in.SMInputCursor;
040import org.sonar.api.PropertyType;
041import org.sonar.api.ce.ComputeEngineSide;
042import org.sonar.api.server.ServerSide;
043import org.sonar.api.utils.SonarException;
044import org.sonar.check.Cardinality;
045
046import static java.nio.charset.StandardCharsets.UTF_8;
047
048/**
049 * @since 2.3
050 * @deprecated in 4.2. Replaced by org.sonar.api.server.rule.RulesDefinition and org.sonar.api.server.rule.RulesDefinitionXmlLoader
051 */
052@Deprecated
053@ServerSide
054@ComputeEngineSide
055public final class XMLRuleParser {
056  private static final Map<String, String> TYPE_MAP = typeMapWithDeprecatedValues();
057
058  public List<Rule> parse(File file) {
059    try (Reader reader = new InputStreamReader(FileUtils.openInputStream(file), UTF_8)) {
060      return parse(reader);
061
062    } catch (IOException e) {
063      throw new SonarException("Fail to load the file: " + file, e);
064    }
065  }
066
067  /**
068   * Warning : the input stream is closed in this method
069   */
070  public List<Rule> parse(InputStream input) {
071    try (Reader reader = new InputStreamReader(input, UTF_8)) {
072      return parse(reader);
073
074    } catch (IOException e) {
075      throw new SonarException("Fail to load the xml stream", e);
076    }
077  }
078
079  public List<Rule> parse(Reader reader) {
080    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
081    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
082    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
083    // just so it won't try to load DTD in if there's DOCTYPE
084    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
085    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
086    SMInputFactory inputFactory = new SMInputFactory(xmlFactory);
087    try {
088      SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
089      rootC.advance(); // <rules>
090      List<Rule> rules = new ArrayList<>();
091
092      SMInputCursor rulesC = rootC.childElementCursor("rule");
093      while (rulesC.getNext() != null) {
094        // <rule>
095        Rule rule = Rule.create();
096        rules.add(rule);
097
098        processRule(rule, rulesC);
099      }
100      return rules;
101
102    } catch (XMLStreamException e) {
103      throw new SonarException("XML is not valid", e);
104    }
105  }
106
107  private static void processRule(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
108    /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
109    String keyAttribute = ruleC.getAttrValue("key");
110    if (StringUtils.isNotBlank(keyAttribute)) {
111      rule.setKey(StringUtils.trim(keyAttribute));
112    }
113
114    /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
115    String priorityAttribute = ruleC.getAttrValue("priority");
116    if (StringUtils.isNotBlank(priorityAttribute)) {
117      rule.setSeverity(RulePriority.valueOf(StringUtils.trim(priorityAttribute)));
118    }
119
120    List<String> tags = new ArrayList<>();
121    SMInputCursor cursor = ruleC.childElementCursor();
122
123    while (cursor.getNext() != null) {
124      String nodeName = cursor.getLocalName();
125
126      if (StringUtils.equalsIgnoreCase("name", nodeName)) {
127        rule.setName(StringUtils.trim(cursor.collectDescendantText(false)));
128
129      } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
130        rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false)));
131
132      } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
133        rule.setKey(StringUtils.trim(cursor.collectDescendantText(false)));
134
135      } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
136        rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false)));
137
138      } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
139        rule.setSeverity(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
140
141      } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
142        rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
143
144      } else if (StringUtils.equalsIgnoreCase("status", nodeName)) {
145        rule.setStatus(StringUtils.trim(cursor.collectDescendantText(false)));
146
147      } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
148        processParameter(rule, cursor);
149
150      } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) {
151        tags.add(StringUtils.trim(cursor.collectDescendantText(false)));
152      }
153    }
154    if (Strings.isNullOrEmpty(rule.getKey())) {
155      throw new SonarException("Node <key> is missing in <rule>");
156    }
157    rule.setTags(tags.toArray(new String[tags.size()]));
158  }
159
160  private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
161    RuleParam param = rule.createParameter();
162
163    String keyAttribute = ruleC.getAttrValue("key");
164    if (StringUtils.isNotBlank(keyAttribute)) {
165      /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
166      param.setKey(StringUtils.trim(keyAttribute));
167    }
168
169    String typeAttribute = ruleC.getAttrValue("type");
170    if (StringUtils.isNotBlank(typeAttribute)) {
171      /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
172      param.setType(type(StringUtils.trim(typeAttribute)));
173    }
174
175    SMInputCursor paramC = ruleC.childElementCursor();
176    while (paramC.getNext() != null) {
177      String propNodeName = paramC.getLocalName();
178      String propText = StringUtils.trim(paramC.collectDescendantText(false));
179      if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
180        param.setKey(propText);
181
182      } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
183        param.setDescription(propText);
184
185      } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
186        param.setType(type(propText));
187
188      } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
189        param.setDefaultValue(propText);
190      }
191    }
192    if (StringUtils.isEmpty(param.getKey())) {
193      throw new SonarException("Node <key> is missing in <param>");
194    }
195  }
196
197  private static Map<String, String> typeMapWithDeprecatedValues() {
198    Map<String, String> map = new HashMap<>();
199    map.put("i", PropertyType.INTEGER.name());
200    map.put("s", PropertyType.STRING.name());
201    map.put("b", PropertyType.BOOLEAN.name());
202    map.put("r", PropertyType.REGULAR_EXPRESSION.name());
203    map.put("s{}", "s{}");
204    map.put("i{}", "i{}");
205    for (PropertyType propertyType : PropertyType.values()) {
206      map.put(propertyType.name(), propertyType.name());
207    }
208    return map;
209  }
210
211  @VisibleForTesting
212  static String type(String type) {
213    String validType = TYPE_MAP.get(type);
214    if (null != validType) {
215      return validType;
216    }
217
218    if (type.matches(".\\[.+\\]")) {
219      return type;
220    }
221    throw new SonarException("Invalid property type [" + type + "]");
222  }
223
224}