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 }