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