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