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.profiles;
021
022 import org.apache.commons.io.IOUtils;
023 import org.apache.commons.lang.CharEncoding;
024 import org.apache.commons.lang.StringUtils;
025 import org.codehaus.stax2.XMLInputFactory2;
026 import org.codehaus.staxmate.SMInputFactory;
027 import org.codehaus.staxmate.in.SMHierarchicCursor;
028 import org.codehaus.staxmate.in.SMInputCursor;
029 import org.sonar.api.ServerComponent;
030 import org.sonar.api.measures.Metric;
031 import org.sonar.api.measures.MetricFinder;
032 import org.sonar.api.rules.ActiveRule;
033 import org.sonar.api.rules.Rule;
034 import org.sonar.api.rules.RuleFinder;
035 import org.sonar.api.rules.RulePriority;
036 import org.sonar.api.utils.Logs;
037 import org.sonar.api.utils.ValidationMessages;
038
039 import java.io.InputStreamReader;
040 import java.io.Reader;
041 import java.nio.charset.Charset;
042 import java.util.HashMap;
043 import java.util.Map;
044
045 import javax.xml.stream.XMLInputFactory;
046 import javax.xml.stream.XMLStreamException;
047
048 /**
049 * TODO should be an interface
050 *
051 * @since 2.3
052 */
053 public final class XMLProfileParser implements ServerComponent {
054
055 private RuleFinder ruleFinder;
056 private MetricFinder metricFinder;
057
058 /**
059 * For backward compatibility.
060 *
061 * @deprecated since 2.5. Plugins shouldn't directly instantiate this class,
062 * because it should be retrieved as an IoC dependency.
063 */
064 @Deprecated
065 public XMLProfileParser(RuleFinder ruleFinder) {
066 this.ruleFinder = ruleFinder;
067 }
068
069 /**
070 * @deprecated since 2.5. Plugins shouldn't directly instantiate this class,
071 * because it should be retrieved as an IoC dependency.
072 */
073 @Deprecated
074 public XMLProfileParser(RuleFinder ruleFinder, MetricFinder metricFinder) {
075 this.ruleFinder = ruleFinder;
076 this.metricFinder = metricFinder;
077 }
078
079 public RulesProfile parseResource(ClassLoader classloader, String xmlClassPath, ValidationMessages messages) {
080 Reader reader = new InputStreamReader(classloader.getResourceAsStream(xmlClassPath), Charset.forName(CharEncoding.UTF_8));
081 try {
082 return parse(reader, messages);
083
084 } finally {
085 IOUtils.closeQuietly(reader);
086 }
087 }
088
089 public RulesProfile parse(Reader reader, ValidationMessages messages) {
090 RulesProfile profile = RulesProfile.create();
091 SMInputFactory inputFactory = initStax();
092 try {
093 SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
094 rootC.advance(); // <profile>
095 SMInputCursor cursor = rootC.childElementCursor();
096 while (cursor.getNext() != null) {
097 String nodeName = cursor.getLocalName();
098 if (StringUtils.equals("rules", nodeName)) {
099 SMInputCursor rulesCursor = cursor.childElementCursor("rule");
100 processRules(rulesCursor, profile, messages);
101
102 } else if (StringUtils.equals("alerts", nodeName)) {
103 SMInputCursor alertsCursor = cursor.childElementCursor("alert");
104 processAlerts(alertsCursor, profile, messages);
105
106 } else if (StringUtils.equals("name", nodeName)) {
107 profile.setName(StringUtils.trim(cursor.collectDescendantText(false)));
108
109 } else if (StringUtils.equals("language", nodeName)) {
110 profile.setLanguage(StringUtils.trim(cursor.collectDescendantText(false)));
111 }
112 }
113 } catch (XMLStreamException e) {
114 messages.addErrorText("XML is not valid: " + e.getMessage());
115 }
116 checkProfile(profile, messages);
117 return profile;
118 }
119
120 private void checkProfile(RulesProfile profile, ValidationMessages messages) {
121 if (StringUtils.isBlank(profile.getName())) {
122 messages.addErrorText("The mandatory node <name> is missing.");
123 }
124 if (StringUtils.isBlank(profile.getLanguage())) {
125 messages.addErrorText("The mandatory node <language> is missing.");
126 }
127 }
128
129 private SMInputFactory initStax() {
130 XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
131 xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
132 xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
133 // just so it won't try to load DTD in if there's DOCTYPE
134 xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
135 xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
136 return new SMInputFactory(xmlFactory);
137 }
138
139 private void processRules(SMInputCursor rulesCursor, RulesProfile profile, ValidationMessages messages) throws XMLStreamException {
140 Map<String, String> parameters = new HashMap<String, String>();
141 while (rulesCursor.getNext() != null) {
142 SMInputCursor ruleCursor = rulesCursor.childElementCursor();
143
144 String repositoryKey = null, key = null;
145 RulePriority priority = null;
146 parameters.clear();
147
148 while (ruleCursor.getNext() != null) {
149 String nodeName = ruleCursor.getLocalName();
150
151 if (StringUtils.equals("repositoryKey", nodeName)) {
152 repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
153
154 } else if (StringUtils.equals("key", nodeName)) {
155 key = StringUtils.trim(ruleCursor.collectDescendantText(false));
156
157 } else if (StringUtils.equals("priority", nodeName)) {
158 priority = RulePriority.valueOf(StringUtils.trim(ruleCursor.collectDescendantText(false)));
159
160 } else if (StringUtils.equals("parameters", nodeName)) {
161 SMInputCursor propsCursor = ruleCursor.childElementCursor("parameter");
162 processParameters(propsCursor, parameters);
163 }
164 }
165
166 Rule rule = ruleFinder.findByKey(repositoryKey, key);
167 if (rule == null) {
168 messages.addWarningText("Rule not found: [repository=" + repositoryKey + ", key=" + key + "]");
169
170 } else {
171 ActiveRule activeRule = profile.activateRule(rule, priority);
172 for (Map.Entry<String, String> entry : parameters.entrySet()) {
173 if (rule.getParam(entry.getKey()) == null) {
174 messages.addWarningText("The parameter '" + entry.getKey() + "' does not exist in the rule: [repository=" + repositoryKey
175 + ", key=" + key + "]");
176 } else {
177 activeRule.setParameter(entry.getKey(), entry.getValue());
178 }
179 }
180 }
181 }
182 }
183
184 private void processParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException {
185 while (propsCursor.getNext() != null) {
186 SMInputCursor propCursor = propsCursor.childElementCursor();
187 String key = null;
188 String value = null;
189 while (propCursor.getNext() != null) {
190 String nodeName = propCursor.getLocalName();
191 if (StringUtils.equals("key", nodeName)) {
192 key = StringUtils.trim(propCursor.collectDescendantText(false));
193
194 } else if (StringUtils.equals("value", nodeName)) {
195 value = StringUtils.trim(propCursor.collectDescendantText(false));
196 }
197 }
198 if (key != null) {
199 parameters.put(key, value);
200 }
201 }
202 }
203
204 private void processAlerts(SMInputCursor alertsCursor, RulesProfile profile, ValidationMessages messages) throws XMLStreamException {
205 if (metricFinder == null) {
206 // TODO remove when constructor without MetricFinder would be removed
207 Logs.INFO.error("Unable to parse alerts, because MetricFinder not available.");
208 return;
209 }
210 while (alertsCursor.getNext() != null) {
211 SMInputCursor alertCursor = alertsCursor.childElementCursor();
212
213 String metricKey = null, operator = "", valueError = "", valueWarning = "";
214
215 while (alertCursor.getNext() != null) {
216 String nodeName = alertCursor.getLocalName();
217
218 if (StringUtils.equals("metric", nodeName)) {
219 metricKey = StringUtils.trim(alertCursor.collectDescendantText(false));
220
221 } else if (StringUtils.equals("operator", nodeName)) {
222 operator = StringUtils.trim(alertCursor.collectDescendantText(false));
223
224 } else if (StringUtils.equals("warning", nodeName)) {
225 valueWarning = StringUtils.trim(alertCursor.collectDescendantText(false));
226
227 } else if (StringUtils.equals("error", nodeName)) {
228 valueError = StringUtils.trim(alertCursor.collectDescendantText(false));
229 }
230 }
231
232 Metric metric = metricFinder.findByKey(metricKey);
233 if (metric == null) {
234 messages.addWarningText("Metric '" + metricKey + "' does not exist");
235 } else {
236 Alert alert = new Alert(profile, metric, operator, valueError, valueWarning);
237 profile.getAlerts().add(alert);
238 }
239 }
240 }
241
242 }