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 * @see org.sonar.api.server.rule.RulesDefinition 081 * @since 4.3 082 */ 083 public class RulesDefinitionXmlLoader implements ServerComponent { 084 085 public void load(RulesDefinition.NewRepository repo, InputStream input, String encoding) { 086 Reader reader = null; 087 try { 088 reader = new InputStreamReader(input, encoding); 089 load(repo, reader); 090 091 } catch (IOException e) { 092 throw new IllegalStateException("Fail to load XML file", e); 093 094 } finally { 095 Closeables.closeQuietly(reader); 096 } 097 } 098 099 public void load(RulesDefinition.NewRepository repo, Reader reader) { 100 XMLInputFactory xmlFactory = XMLInputFactory.newInstance(); 101 xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); 102 xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); 103 // just so it won't try to load DTD in if there's DOCTYPE 104 xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); 105 xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); 106 SMInputFactory inputFactory = new SMInputFactory(xmlFactory); 107 try { 108 SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader); 109 rootC.advance(); // <rules> 110 111 SMInputCursor rulesC = rootC.childElementCursor("rule"); 112 while (rulesC.getNext() != null) { 113 // <rule> 114 processRule(repo, rulesC); 115 } 116 117 } catch (XMLStreamException e) { 118 throw new IllegalStateException("XML is not valid", e); 119 } 120 } 121 122 private void processRule(RulesDefinition.NewRepository repo, SMInputCursor ruleC) throws XMLStreamException { 123 String key = null, name = null, description = null, internalKey = null, severity = Severity.defaultSeverity(), status = null; 124 Cardinality cardinality = Cardinality.SINGLE; 125 List<ParamStruct> params = new ArrayList<ParamStruct>(); 126 List<String> tags = new ArrayList<String>(); 127 128 /* BACKWARD COMPATIBILITY WITH VERY OLD FORMAT */ 129 String keyAttribute = ruleC.getAttrValue("key"); 130 if (StringUtils.isNotBlank(keyAttribute)) { 131 key = StringUtils.trim(keyAttribute); 132 } 133 String priorityAttribute = ruleC.getAttrValue("priority"); 134 if (StringUtils.isNotBlank(priorityAttribute)) { 135 severity = StringUtils.trim(priorityAttribute); 136 } 137 138 SMInputCursor cursor = ruleC.childElementCursor(); 139 while (cursor.getNext() != null) { 140 String nodeName = cursor.getLocalName(); 141 142 if (StringUtils.equalsIgnoreCase("name", nodeName)) { 143 name = StringUtils.trim(cursor.collectDescendantText(false)); 144 145 } else if (StringUtils.equalsIgnoreCase("description", nodeName)) { 146 description = StringUtils.trim(cursor.collectDescendantText(false)); 147 148 } else if (StringUtils.equalsIgnoreCase("key", nodeName)) { 149 key = StringUtils.trim(cursor.collectDescendantText(false)); 150 151 } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) { 152 // deprecated field, replaced by internalKey 153 internalKey = StringUtils.trim(cursor.collectDescendantText(false)); 154 155 } else if (StringUtils.equalsIgnoreCase("internalKey", nodeName)) { 156 internalKey = StringUtils.trim(cursor.collectDescendantText(false)); 157 158 } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) { 159 // deprecated field, replaced by severity 160 severity = StringUtils.trim(cursor.collectDescendantText(false)); 161 162 } else if (StringUtils.equalsIgnoreCase("severity", nodeName)) { 163 severity = StringUtils.trim(cursor.collectDescendantText(false)); 164 165 } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) { 166 cardinality = Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false))); 167 168 } else if (StringUtils.equalsIgnoreCase("status", nodeName)) { 169 status = StringUtils.trim(cursor.collectDescendantText(false)); 170 171 } else if (StringUtils.equalsIgnoreCase("param", nodeName)) { 172 params.add(processParameter(cursor)); 173 174 } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) { 175 tags.add(StringUtils.trim(cursor.collectDescendantText(false))); 176 } 177 } 178 RulesDefinition.NewRule rule = repo.createRule(key) 179 .setHtmlDescription(description) 180 .setSeverity(severity) 181 .setName(name) 182 .setInternalKey(internalKey) 183 .setTags(tags.toArray(new String[tags.size()])) 184 .setTemplate(cardinality == Cardinality.MULTIPLE); 185 if (status != null) { 186 rule.setStatus(RuleStatus.valueOf(status)); 187 } 188 for (ParamStruct param : params) { 189 rule.createParam(param.key) 190 .setDefaultValue(param.defaultValue) 191 .setType(param.type) 192 .setDescription(param.description); 193 } 194 } 195 196 private static class ParamStruct { 197 String key, description, defaultValue; 198 RuleParamType type = RuleParamType.STRING; 199 } 200 201 private ParamStruct processParameter(SMInputCursor ruleC) throws XMLStreamException { 202 ParamStruct param = new ParamStruct(); 203 204 // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT 205 String keyAttribute = ruleC.getAttrValue("key"); 206 if (StringUtils.isNotBlank(keyAttribute)) { 207 param.key = StringUtils.trim(keyAttribute); 208 } 209 210 // BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT 211 String typeAttribute = ruleC.getAttrValue("type"); 212 if (StringUtils.isNotBlank(typeAttribute)) { 213 param.type = RuleParamType.parse(typeAttribute); 214 } 215 216 SMInputCursor paramC = ruleC.childElementCursor(); 217 while (paramC.getNext() != null) { 218 String propNodeName = paramC.getLocalName(); 219 String propText = StringUtils.trim(paramC.collectDescendantText(false)); 220 if (StringUtils.equalsIgnoreCase("key", propNodeName)) { 221 param.key = propText; 222 223 } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) { 224 param.description = propText; 225 226 } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) { 227 param.type = RuleParamType.parse(propText); 228 229 } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) { 230 param.defaultValue = propText; 231 } 232 } 233 return param; 234 } 235 }