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