001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar 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 * Sonar 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
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.api.rules;
021
022import org.apache.commons.io.FileUtils;
023import org.apache.commons.io.IOUtils;
024import org.apache.commons.lang.CharEncoding;
025import org.apache.commons.lang.StringUtils;
026import org.codehaus.staxmate.SMInputFactory;
027import org.codehaus.staxmate.in.SMHierarchicCursor;
028import org.codehaus.staxmate.in.SMInputCursor;
029import org.sonar.api.ServerComponent;
030import org.sonar.api.utils.SonarException;
031import org.sonar.check.Cardinality;
032
033import javax.xml.stream.XMLInputFactory;
034import javax.xml.stream.XMLStreamException;
035
036import java.io.*;
037import java.util.ArrayList;
038import java.util.List;
039
040/**
041 * @since 2.3
042 */
043public final class XMLRuleParser implements ServerComponent {
044
045  public List<Rule> parse(File file) {
046    Reader reader = null;
047    try {
048      reader = new InputStreamReader(FileUtils.openInputStream(file), CharEncoding.UTF_8);
049      return parse(reader);
050
051    } catch (IOException e) {
052      throw new SonarException("Fail to load the file: " + file, e);
053
054    } finally {
055      IOUtils.closeQuietly(reader);
056    }
057  }
058
059  /**
060   * Warning : the input stream is closed in this method
061   */
062  public List<Rule> parse(InputStream input) {
063    Reader reader = null;
064    try {
065      reader = new InputStreamReader(input, CharEncoding.UTF_8);
066      return parse(reader);
067
068    } catch (IOException e) {
069      throw new SonarException("Fail to load the xml stream", e);
070
071    } finally {
072      IOUtils.closeQuietly(reader);
073    }
074  }
075
076  public List<Rule> parse(Reader reader) {
077    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
078    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
079    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
080    // just so it won't try to load DTD in if there's DOCTYPE
081    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
082    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
083    SMInputFactory inputFactory = new SMInputFactory(xmlFactory);
084    try {
085      SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
086      rootC.advance(); // <rules>
087      List<Rule> rules = new ArrayList<Rule>();
088
089      SMInputCursor rulesC = rootC.childElementCursor("rule");
090      while (rulesC.getNext() != null) {
091        // <rule>
092        Rule rule = Rule.create();
093        rules.add(rule);
094
095        processRule(rule, rulesC);
096      }
097      return rules;
098
099    } catch (XMLStreamException e) {
100      throw new SonarException("XML is not valid", e);
101    }
102  }
103
104  private static void processRule(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
105    /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
106    String keyAttribute = ruleC.getAttrValue("key");
107    if (StringUtils.isNotBlank(keyAttribute)) {
108      rule.setKey(StringUtils.trim(keyAttribute));
109    }
110
111    /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
112    String priorityAttribute = ruleC.getAttrValue("priority");
113    if (StringUtils.isNotBlank(priorityAttribute)) {
114      rule.setSeverity(RulePriority.valueOf(StringUtils.trim(priorityAttribute)));
115    }
116
117    SMInputCursor cursor = ruleC.childElementCursor();
118
119    while (cursor.getNext() != null) {
120      String nodeName = cursor.getLocalName();
121
122      if (StringUtils.equalsIgnoreCase("name", nodeName)) {
123        rule.setName(StringUtils.trim(cursor.collectDescendantText(false)));
124
125      } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
126        rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false)));
127
128      } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
129        rule.setKey(StringUtils.trim(cursor.collectDescendantText(false)));
130
131      } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
132        rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false)));
133
134      } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
135        rule.setSeverity(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
136
137      } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
138        rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
139
140      } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
141        processParameter(rule, cursor);
142      }
143    }
144    if (StringUtils.isEmpty(rule.getKey())) {
145      throw new SonarException("Node <key> is missing in <rule>");
146    }
147  }
148
149  private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
150    RuleParam param = rule.createParameter();
151
152    String keyAttribute = ruleC.getAttrValue("key");
153    if (StringUtils.isNotBlank(keyAttribute)) {
154      /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
155      param.setKey(StringUtils.trim(keyAttribute));
156    }
157
158    String typeAttribute = ruleC.getAttrValue("type");
159    if (StringUtils.isNotBlank(typeAttribute)) {
160      /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
161      param.setType(StringUtils.trim(typeAttribute));
162    }
163
164    SMInputCursor paramC = ruleC.childElementCursor();
165    while (paramC.getNext() != null) {
166      String propNodeName = paramC.getLocalName();
167      String propText = StringUtils.trim(paramC.collectDescendantText(false));
168      if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
169        param.setKey(propText);
170
171      } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
172        param.setDescription(propText);
173
174      } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
175        param.setType(propText);
176
177      } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
178        param.setDefaultValue(propText);
179      }
180    }
181    if (StringUtils.isEmpty(param.getKey())) {
182      throw new SonarException("Node <key> is missing in <param>");
183    }
184  }
185}