001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 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 }