001/* 002 * SonarQube 003 * Copyright (C) 2009-2017 SonarSource SA 004 * mailto:info AT sonarsource DOT com 005 * 006 * This program 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 * This program 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.rules; 021 022import com.google.common.annotations.VisibleForTesting; 023import com.google.common.base.Strings; 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028import java.io.Reader; 029import java.util.ArrayList; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033import javax.xml.stream.XMLInputFactory; 034import javax.xml.stream.XMLStreamException; 035import org.apache.commons.io.FileUtils; 036import org.apache.commons.lang.StringUtils; 037import org.codehaus.staxmate.SMInputFactory; 038import org.codehaus.staxmate.in.SMHierarchicCursor; 039import org.codehaus.staxmate.in.SMInputCursor; 040import org.sonar.api.PropertyType; 041import org.sonar.api.ce.ComputeEngineSide; 042import org.sonar.api.server.ServerSide; 043import org.sonar.api.utils.SonarException; 044import org.sonar.check.Cardinality; 045 046import static java.nio.charset.StandardCharsets.UTF_8; 047 048/** 049 * @since 2.3 050 * @deprecated in 4.2. Replaced by org.sonar.api.server.rule.RulesDefinition and org.sonar.api.server.rule.RulesDefinitionXmlLoader 051 */ 052@Deprecated 053@ServerSide 054@ComputeEngineSide 055public final class XMLRuleParser { 056 private static final Map<String, String> TYPE_MAP = typeMapWithDeprecatedValues(); 057 058 public List<Rule> parse(File file) { 059 try (Reader reader = new InputStreamReader(FileUtils.openInputStream(file), UTF_8)) { 060 return parse(reader); 061 062 } catch (IOException e) { 063 throw new SonarException("Fail to load the file: " + file, e); 064 } 065 } 066 067 /** 068 * Warning : the input stream is closed in this method 069 */ 070 public List<Rule> parse(InputStream input) { 071 try (Reader reader = new InputStreamReader(input, UTF_8)) { 072 return parse(reader); 073 074 } catch (IOException e) { 075 throw new SonarException("Fail to load the xml stream", e); 076 } 077 } 078 079 public List<Rule> parse(Reader reader) { 080 XMLInputFactory xmlFactory = XMLInputFactory.newInstance(); 081 xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); 082 xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); 083 // just so it won't try to load DTD in if there's DOCTYPE 084 xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); 085 xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); 086 SMInputFactory inputFactory = new SMInputFactory(xmlFactory); 087 try { 088 SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader); 089 rootC.advance(); // <rules> 090 List<Rule> rules = new ArrayList<>(); 091 092 SMInputCursor rulesC = rootC.childElementCursor("rule"); 093 while (rulesC.getNext() != null) { 094 // <rule> 095 Rule rule = Rule.create(); 096 rules.add(rule); 097 098 processRule(rule, rulesC); 099 } 100 return rules; 101 102 } catch (XMLStreamException e) { 103 throw new SonarException("XML is not valid", e); 104 } 105 } 106 107 private static void processRule(Rule rule, SMInputCursor ruleC) throws XMLStreamException { 108 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 109 String keyAttribute = ruleC.getAttrValue("key"); 110 if (StringUtils.isNotBlank(keyAttribute)) { 111 rule.setKey(StringUtils.trim(keyAttribute)); 112 } 113 114 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 115 String priorityAttribute = ruleC.getAttrValue("priority"); 116 if (StringUtils.isNotBlank(priorityAttribute)) { 117 rule.setSeverity(RulePriority.valueOf(StringUtils.trim(priorityAttribute))); 118 } 119 120 List<String> tags = new ArrayList<>(); 121 SMInputCursor cursor = ruleC.childElementCursor(); 122 123 while (cursor.getNext() != null) { 124 String nodeName = cursor.getLocalName(); 125 126 if (StringUtils.equalsIgnoreCase("name", nodeName)) { 127 rule.setName(StringUtils.trim(cursor.collectDescendantText(false))); 128 129 } else if (StringUtils.equalsIgnoreCase("description", nodeName)) { 130 rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false))); 131 132 } else if (StringUtils.equalsIgnoreCase("key", nodeName)) { 133 rule.setKey(StringUtils.trim(cursor.collectDescendantText(false))); 134 135 } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) { 136 rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false))); 137 138 } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) { 139 rule.setSeverity(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false)))); 140 141 } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) { 142 rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false)))); 143 144 } else if (StringUtils.equalsIgnoreCase("status", nodeName)) { 145 rule.setStatus(StringUtils.trim(cursor.collectDescendantText(false))); 146 147 } else if (StringUtils.equalsIgnoreCase("param", nodeName)) { 148 processParameter(rule, cursor); 149 150 } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) { 151 tags.add(StringUtils.trim(cursor.collectDescendantText(false))); 152 } 153 } 154 if (Strings.isNullOrEmpty(rule.getKey())) { 155 throw new SonarException("Node <key> is missing in <rule>"); 156 } 157 rule.setTags(tags.toArray(new String[tags.size()])); 158 } 159 160 private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException { 161 RuleParam param = rule.createParameter(); 162 163 String keyAttribute = ruleC.getAttrValue("key"); 164 if (StringUtils.isNotBlank(keyAttribute)) { 165 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 166 param.setKey(StringUtils.trim(keyAttribute)); 167 } 168 169 String typeAttribute = ruleC.getAttrValue("type"); 170 if (StringUtils.isNotBlank(typeAttribute)) { 171 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 172 param.setType(type(StringUtils.trim(typeAttribute))); 173 } 174 175 SMInputCursor paramC = ruleC.childElementCursor(); 176 while (paramC.getNext() != null) { 177 String propNodeName = paramC.getLocalName(); 178 String propText = StringUtils.trim(paramC.collectDescendantText(false)); 179 if (StringUtils.equalsIgnoreCase("key", propNodeName)) { 180 param.setKey(propText); 181 182 } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) { 183 param.setDescription(propText); 184 185 } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) { 186 param.setType(type(propText)); 187 188 } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) { 189 param.setDefaultValue(propText); 190 } 191 } 192 if (StringUtils.isEmpty(param.getKey())) { 193 throw new SonarException("Node <key> is missing in <param>"); 194 } 195 } 196 197 private static Map<String, String> typeMapWithDeprecatedValues() { 198 Map<String, String> map = new HashMap<>(); 199 map.put("i", PropertyType.INTEGER.name()); 200 map.put("s", PropertyType.STRING.name()); 201 map.put("b", PropertyType.BOOLEAN.name()); 202 map.put("r", PropertyType.REGULAR_EXPRESSION.name()); 203 map.put("s{}", "s{}"); 204 map.put("i{}", "i{}"); 205 for (PropertyType propertyType : PropertyType.values()) { 206 map.put(propertyType.name(), propertyType.name()); 207 } 208 return map; 209 } 210 211 @VisibleForTesting 212 static String type(String type) { 213 String validType = TYPE_MAP.get(type); 214 if (null != validType) { 215 return validType; 216 } 217 218 if (type.matches(".\\[.+\\]")) { 219 return type; 220 } 221 throw new SonarException("Invalid property type [" + type + "]"); 222 } 223 224}