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.server.rule;
021
022 import com.google.common.io.Closeables;
023 import org.apache.commons.lang.StringUtils;
024 import org.codehaus.staxmate.SMInputFactory;
025 import org.codehaus.staxmate.in.SMHierarchicCursor;
026 import org.codehaus.staxmate.in.SMInputCursor;
027 import org.sonar.api.ServerComponent;
028 import org.sonar.api.rule.RuleStatus;
029 import org.sonar.api.rule.Severity;
030 import org.sonar.check.Cardinality;
031
032 import javax.xml.stream.XMLInputFactory;
033 import javax.xml.stream.XMLStreamException;
034 import java.io.IOException;
035 import java.io.InputStream;
036 import java.io.InputStreamReader;
037 import java.io.Reader;
038 import java.util.ArrayList;
039 import java.util.List;
040
041 /**
042 * Loads definitions of rules from a XML file.
043 *
044 * <h3>XML Format</h3>
045 * <pre>
046 * <rules>
047 * <rule>
048 * <!-- required fields -->
049 * <key>the-rule-key</key>
050 * <name>The purpose of the rule</name>
051 *
052 * <!-- optional fields -->
053 * <description>
054 * <![CDATA[The description]]>
055 * </description>
056 * <internalKey>Checker/TreeWalker/LocalVariableName</internalKey>
057 * <severity>BLOCKER</severity>
058 * <cardinality>MULTIPLE</cardinality>
059 * <status>BETA</status>
060 * <param>
061 * <key>the-param-key</key>
062 * <tag>style</tag>
063 * <tag>security</tag>
064 * <description>
065 * <![CDATA[the param-description]]>
066 * </description>
067 * <defaultValue>42</defaultValue>
068 * </param>
069 * <param>
070 * <key>another-param</key>
071 * </param>
072 *
073 * <!-- deprecated fields -->
074 * <configKey>Checker/TreeWalker/LocalVariableName</configKey>
075 * <priority>BLOCKER</priority>
076 * </rule>
077 * </rules>
078 * </pre>
079 *
080 * @see org.sonar.api.server.rule.RulesDefinition
081 * @since 4.3
082 */
083 public class RulesDefinitionXmlLoader implements ServerComponent {
084
085 public void load(RulesDefinition.NewRepository repo, InputStream input, String encoding) {
086 Reader reader = null;
087 try {
088 reader = new InputStreamReader(input, encoding);
089 load(repo, reader);
090
091 } catch (IOException e) {
092 throw new IllegalStateException("Fail to load XML file", e);
093
094 } finally {
095 Closeables.closeQuietly(reader);
096 }
097 }
098
099 public void load(RulesDefinition.NewRepository repo, Reader reader) {
100 XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
101 xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
102 xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
103 // just so it won't try to load DTD in if there's DOCTYPE
104 xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
105 xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
106 SMInputFactory inputFactory = new SMInputFactory(xmlFactory);
107 try {
108 SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
109 rootC.advance(); // <rules>
110
111 SMInputCursor rulesC = rootC.childElementCursor("rule");
112 while (rulesC.getNext() != null) {
113 // <rule>
114 processRule(repo, rulesC);
115 }
116
117 } catch (XMLStreamException e) {
118 throw new IllegalStateException("XML is not valid", e);
119 }
120 }
121
122 private void processRule(RulesDefinition.NewRepository repo, SMInputCursor ruleC) throws XMLStreamException {
123 String key = null, name = null, description = null, internalKey = null, severity = Severity.defaultSeverity(), status = null;
124 Cardinality cardinality = Cardinality.SINGLE;
125 List<ParamStruct> params = new ArrayList<ParamStruct>();
126 List<String> tags = new ArrayList<String>();
127
128 /* BACKWARD COMPATIBILITY WITH VERY OLD FORMAT */
129 String keyAttribute = ruleC.getAttrValue("key");
130 if (StringUtils.isNotBlank(keyAttribute)) {
131 key = StringUtils.trim(keyAttribute);
132 }
133 String priorityAttribute = ruleC.getAttrValue("priority");
134 if (StringUtils.isNotBlank(priorityAttribute)) {
135 severity = StringUtils.trim(priorityAttribute);
136 }
137
138 SMInputCursor cursor = ruleC.childElementCursor();
139 while (cursor.getNext() != null) {
140 String nodeName = cursor.getLocalName();
141
142 if (StringUtils.equalsIgnoreCase("name", nodeName)) {
143 name = StringUtils.trim(cursor.collectDescendantText(false));
144
145 } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
146 description = StringUtils.trim(cursor.collectDescendantText(false));
147
148 } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
149 key = StringUtils.trim(cursor.collectDescendantText(false));
150
151 } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
152 // deprecated field, replaced by internalKey
153 internalKey = StringUtils.trim(cursor.collectDescendantText(false));
154
155 } else if (StringUtils.equalsIgnoreCase("internalKey", nodeName)) {
156 internalKey = StringUtils.trim(cursor.collectDescendantText(false));
157
158 } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
159 // deprecated field, replaced by severity
160 severity = StringUtils.trim(cursor.collectDescendantText(false));
161
162 } else if (StringUtils.equalsIgnoreCase("severity", nodeName)) {
163 severity = StringUtils.trim(cursor.collectDescendantText(false));
164
165 } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
166 cardinality = Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false)));
167
168 } else if (StringUtils.equalsIgnoreCase("status", nodeName)) {
169 status = StringUtils.trim(cursor.collectDescendantText(false));
170
171 } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
172 params.add(processParameter(cursor));
173
174 } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) {
175 tags.add(StringUtils.trim(cursor.collectDescendantText(false)));
176 }
177 }
178 RulesDefinition.NewRule rule = repo.createRule(key)
179 .setHtmlDescription(description)
180 .setSeverity(severity)
181 .setName(name)
182 .setInternalKey(internalKey)
183 .setTags(tags.toArray(new String[tags.size()]))
184 .setTemplate(cardinality == Cardinality.MULTIPLE);
185 if (status != null) {
186 rule.setStatus(RuleStatus.valueOf(status));
187 }
188 for (ParamStruct param : params) {
189 rule.createParam(param.key)
190 .setDefaultValue(param.defaultValue)
191 .setType(param.type)
192 .setDescription(param.description);
193 }
194 }
195
196 private static class ParamStruct {
197 String key, description, defaultValue;
198 RuleParamType type = RuleParamType.STRING;
199 }
200
201 private ParamStruct processParameter(SMInputCursor ruleC) throws XMLStreamException {
202 ParamStruct param = new ParamStruct();
203
204 // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
205 String keyAttribute = ruleC.getAttrValue("key");
206 if (StringUtils.isNotBlank(keyAttribute)) {
207 param.key = StringUtils.trim(keyAttribute);
208 }
209
210 // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
211 String typeAttribute = ruleC.getAttrValue("type");
212 if (StringUtils.isNotBlank(typeAttribute)) {
213 param.type = RuleParamType.parse(typeAttribute);
214 }
215
216 SMInputCursor paramC = ruleC.childElementCursor();
217 while (paramC.getNext() != null) {
218 String propNodeName = paramC.getLocalName();
219 String propText = StringUtils.trim(paramC.collectDescendantText(false));
220 if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
221 param.key = propText;
222
223 } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
224 param.description = propText;
225
226 } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
227 param.type = RuleParamType.parse(propText);
228
229 } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
230 param.defaultValue = propText;
231 }
232 }
233 return param;
234 }
235 }