001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
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    import java.io.*;
037    import java.util.ArrayList;
038    import java.util.List;
039    
040    /**
041     * @since 2.3
042     */
043    public 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 = XMLInputFactory2.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.setPriority(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.setPriority(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
136    
137          } else if (StringUtils.equalsIgnoreCase("category", nodeName)) {
138            /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT : attribute "name" */
139            String category = StringUtils.trim(StringUtils.defaultString(cursor.getAttrValue("name"), cursor.collectDescendantText(false)));
140            rule.setRulesCategory(new RulesCategory(category));
141    
142          } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
143            rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
144    
145          } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
146            processParameter(rule, cursor);
147          }
148        }
149        if (StringUtils.isEmpty(rule.getKey())) {
150          throw new SonarException("Node <key> is missing in <rule>");
151        }
152        if (StringUtils.isEmpty(rule.getName())) {
153          throw new SonarException("Node <name> is missing in <rule>");
154        }
155      }
156    
157      private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
158        RuleParam param = rule.createParameter();
159    
160        String keyAttribute = ruleC.getAttrValue("key");
161        if (StringUtils.isNotBlank(keyAttribute)) {
162          /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
163          param.setKey(StringUtils.trim(keyAttribute));
164        }
165    
166        String typeAttribute = ruleC.getAttrValue("type");
167        if (StringUtils.isNotBlank(typeAttribute)) {
168          /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
169          param.setType(StringUtils.trim(typeAttribute));
170        }
171    
172        SMInputCursor paramC = ruleC.childElementCursor();
173        while (paramC.getNext() != null) {
174          String propNodeName = paramC.getLocalName();
175          String propText = StringUtils.trim(paramC.collectDescendantText(false));
176          if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
177            param.setKey(propText);
178    
179          } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
180            param.setDescription(propText);
181    
182          } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
183            param.setType(propText);
184    
185          } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
186            param.setDefaultValue(propText);
187          }
188        }
189        if (StringUtils.isEmpty(param.getKey())) {
190          throw new SonarException("Node <key> is missing in <param>");
191        }
192      }
193    }