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