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