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