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