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.utils.SonarException;
031    
032    import java.io.*;
033    import java.util.ArrayList;
034    import java.util.List;
035    import javax.xml.stream.XMLInputFactory;
036    import javax.xml.stream.XMLStreamException;
037    
038    /**
039     * @since 2.3
040     */
041    public final class StandardRuleXmlFormat {
042    
043      private StandardRuleXmlFormat() {
044        // only static methods
045      }
046    
047      public static List<Rule> parseXml(File file) {
048        Reader reader = null;
049        try {
050          reader = new InputStreamReader(FileUtils.openInputStream(file), CharEncoding.UTF_8);
051          return parseXml(reader);
052    
053        } catch (IOException e) {
054          throw new SonarException("Fail to load the file: " + file, e);
055    
056        } finally {
057          IOUtils.closeQuietly(reader);
058        }
059      }
060    
061      /**
062       * Warning : the input stream is closed in this method
063       */
064      public static List<Rule> parseXml(InputStream input) {
065        Reader reader = null;
066        try {
067          reader = new InputStreamReader(input, CharEncoding.UTF_8);
068          return parseXml(reader);
069    
070        } catch (IOException e) {
071          throw new SonarException("Fail to load the xml stream", e);
072    
073        } finally {
074          IOUtils.closeQuietly(reader);
075        }
076      }
077    
078      public static List<Rule> parseXml(Reader reader) {
079        XMLInputFactory xmlFactory = XMLInputFactory2.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<Rule>();
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.setPriority(RulePriority.valueOf(StringUtils.trim(priorityAttribute)));
117        }
118    
119        SMInputCursor cursor = ruleC.childElementCursor();
120    
121        while (cursor.getNext() != null) {
122          String nodeName = cursor.getLocalName();
123    
124          if (StringUtils.equalsIgnoreCase("name", nodeName)) {
125            rule.setName(StringUtils.trim(cursor.collectDescendantText(false)));
126    
127          } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
128            rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false)));
129    
130          } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
131            rule.setKey(StringUtils.trim(cursor.collectDescendantText(false)));
132    
133          } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
134            rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false)));
135    
136          } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
137            rule.setPriority(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
138    
139          } else if (StringUtils.equalsIgnoreCase("category", nodeName)) {
140            /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT : attribute "name" */
141            String category = StringUtils.trim(StringUtils.defaultString(cursor.getAttrValue("name"), cursor.collectDescendantText(false)));
142            rule.setRulesCategory(new RulesCategory(category));
143    
144          } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
145            rule.setCardinality(Rule.Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
146    
147          } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
148            processParameter(rule, cursor);
149          }
150        }
151        if (StringUtils.isEmpty(rule.getKey())) {
152          throw new SonarException("Node <key> is missing in <rule>");
153        }
154        if (StringUtils.isEmpty(rule.getName())) {
155          throw new SonarException("Node <name> is missing in <rule>");
156        }
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(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(propText);
186          }
187        }
188        if (StringUtils.isEmpty(param.getKey())) {
189          throw new SonarException("Node <key> is missing in <param>");
190        }
191      }
192    }