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