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