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.ServerComponent;
031 import org.sonar.api.utils.SonarException;
032 import org.sonar.check.Cardinality;
033
034 import javax.xml.stream.XMLInputFactory;
035 import javax.xml.stream.XMLStreamException;
036 import java.io.*;
037 import java.util.ArrayList;
038 import java.util.List;
039
040 /**
041 * @since 2.3
042 */
043 public final class XMLRuleParser implements ServerComponent {
044
045 public List<Rule> parse(File file) {
046 Reader reader = null;
047 try {
048 reader = new InputStreamReader(FileUtils.openInputStream(file), CharEncoding.UTF_8);
049 return parse(reader);
050
051 } catch (IOException e) {
052 throw new SonarException("Fail to load the file: " + file, e);
053
054 } finally {
055 IOUtils.closeQuietly(reader);
056 }
057 }
058
059 /**
060 * Warning : the input stream is closed in this method
061 */
062 public List<Rule> parse(InputStream input) {
063 Reader reader = null;
064 try {
065 reader = new InputStreamReader(input, CharEncoding.UTF_8);
066 return parse(reader);
067
068 } catch (IOException e) {
069 throw new SonarException("Fail to load the xml stream", e);
070
071 } finally {
072 IOUtils.closeQuietly(reader);
073 }
074 }
075
076 public List<Rule> parse(Reader reader) {
077 XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
078 xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
079 xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
080 // just so it won't try to load DTD in if there's DOCTYPE
081 xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
082 xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
083 SMInputFactory inputFactory = new SMInputFactory(xmlFactory);
084 try {
085 SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
086 rootC.advance(); // <rules>
087 List<Rule> rules = new ArrayList<Rule>();
088
089 SMInputCursor rulesC = rootC.childElementCursor("rule");
090 while (rulesC.getNext() != null) {
091 // <rule>
092 Rule rule = Rule.create();
093 rules.add(rule);
094
095 processRule(rule, rulesC);
096 }
097 return rules;
098
099 } catch (XMLStreamException e) {
100 throw new SonarException("XML is not valid", e);
101 }
102 }
103
104 private static void processRule(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
105 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
106 String keyAttribute = ruleC.getAttrValue("key");
107 if (StringUtils.isNotBlank(keyAttribute)) {
108 rule.setKey(StringUtils.trim(keyAttribute));
109 }
110
111 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
112 String priorityAttribute = ruleC.getAttrValue("priority");
113 if (StringUtils.isNotBlank(priorityAttribute)) {
114 rule.setPriority(RulePriority.valueOf(StringUtils.trim(priorityAttribute)));
115 }
116
117 SMInputCursor cursor = ruleC.childElementCursor();
118
119 while (cursor.getNext() != null) {
120 String nodeName = cursor.getLocalName();
121
122 if (StringUtils.equalsIgnoreCase("name", nodeName)) {
123 rule.setName(StringUtils.trim(cursor.collectDescendantText(false)));
124
125 } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
126 rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false)));
127
128 } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
129 rule.setKey(StringUtils.trim(cursor.collectDescendantText(false)));
130
131 } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
132 rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false)));
133
134 } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
135 rule.setPriority(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
136
137 } else if (StringUtils.equalsIgnoreCase("category", nodeName)) {
138 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT : attribute "name" */
139 String category = StringUtils.trim(StringUtils.defaultString(cursor.getAttrValue("name"), cursor.collectDescendantText(false)));
140 rule.setRulesCategory(new RulesCategory(category));
141
142 } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
143 rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false))));
144
145 } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
146 processParameter(rule, cursor);
147 }
148 }
149 if (StringUtils.isEmpty(rule.getKey())) {
150 throw new SonarException("Node <key> is missing in <rule>");
151 }
152 if (StringUtils.isEmpty(rule.getName())) {
153 throw new SonarException("Node <name> is missing in <rule>");
154 }
155 }
156
157 private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException {
158 RuleParam param = rule.createParameter();
159
160 String keyAttribute = ruleC.getAttrValue("key");
161 if (StringUtils.isNotBlank(keyAttribute)) {
162 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
163 param.setKey(StringUtils.trim(keyAttribute));
164 }
165
166 String typeAttribute = ruleC.getAttrValue("type");
167 if (StringUtils.isNotBlank(typeAttribute)) {
168 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */
169 param.setType(StringUtils.trim(typeAttribute));
170 }
171
172 SMInputCursor paramC = ruleC.childElementCursor();
173 while (paramC.getNext() != null) {
174 String propNodeName = paramC.getLocalName();
175 String propText = StringUtils.trim(paramC.collectDescendantText(false));
176 if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
177 param.setKey(propText);
178
179 } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
180 param.setDescription(propText);
181
182 } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
183 param.setType(propText);
184
185 } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
186 param.setDefaultValue(propText);
187 }
188 }
189 if (StringUtils.isEmpty(param.getKey())) {
190 throw new SonarException("Node <key> is missing in <param>");
191 }
192 }
193 }