001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar 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 * Sonar 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 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.api.rules; 021 022 import org.apache.commons.io.FileUtils; 023 import org.apache.commons.io.IOUtils; 024 import org.apache.commons.lang.CharEncoding; 025 import org.apache.commons.lang.StringUtils; 026 import org.codehaus.stax2.XMLInputFactory2; 027 import org.codehaus.staxmate.SMInputFactory; 028 import org.codehaus.staxmate.in.SMHierarchicCursor; 029 import org.codehaus.staxmate.in.SMInputCursor; 030 import org.sonar.api.ServerComponent; 031 import org.sonar.api.utils.SonarException; 032 import org.sonar.check.Cardinality; 033 034 import javax.xml.stream.XMLInputFactory; 035 import javax.xml.stream.XMLStreamException; 036 037 import java.io.*; 038 import java.util.ArrayList; 039 import java.util.List; 040 041 /** 042 * @since 2.3 043 */ 044 public final class XMLRuleParser implements ServerComponent { 045 046 public List<Rule> parse(File file) { 047 Reader reader = null; 048 try { 049 reader = new InputStreamReader(FileUtils.openInputStream(file), CharEncoding.UTF_8); 050 return parse(reader); 051 052 } catch (IOException e) { 053 throw new SonarException("Fail to load the file: " + file, e); 054 055 } finally { 056 IOUtils.closeQuietly(reader); 057 } 058 } 059 060 /** 061 * Warning : the input stream is closed in this method 062 */ 063 public List<Rule> parse(InputStream input) { 064 Reader reader = null; 065 try { 066 reader = new InputStreamReader(input, CharEncoding.UTF_8); 067 return parse(reader); 068 069 } catch (IOException e) { 070 throw new SonarException("Fail to load the xml stream", e); 071 072 } finally { 073 IOUtils.closeQuietly(reader); 074 } 075 } 076 077 public List<Rule> parse(Reader reader) { 078 XMLInputFactory xmlFactory = XMLInputFactory2.newInstance(); 079 xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); 080 xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); 081 // just so it won't try to load DTD in if there's DOCTYPE 082 xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); 083 xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); 084 SMInputFactory inputFactory = new SMInputFactory(xmlFactory); 085 try { 086 SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader); 087 rootC.advance(); // <rules> 088 List<Rule> rules = new ArrayList<Rule>(); 089 090 SMInputCursor rulesC = rootC.childElementCursor("rule"); 091 while (rulesC.getNext() != null) { 092 // <rule> 093 Rule rule = Rule.create(); 094 rules.add(rule); 095 096 processRule(rule, rulesC); 097 } 098 return rules; 099 100 } catch (XMLStreamException e) { 101 throw new SonarException("XML is not valid", e); 102 } 103 } 104 105 private static void processRule(Rule rule, SMInputCursor ruleC) throws XMLStreamException { 106 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 107 String keyAttribute = ruleC.getAttrValue("key"); 108 if (StringUtils.isNotBlank(keyAttribute)) { 109 rule.setKey(StringUtils.trim(keyAttribute)); 110 } 111 112 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 113 String priorityAttribute = ruleC.getAttrValue("priority"); 114 if (StringUtils.isNotBlank(priorityAttribute)) { 115 rule.setSeverity(RulePriority.valueOf(StringUtils.trim(priorityAttribute))); 116 } 117 118 SMInputCursor cursor = ruleC.childElementCursor(); 119 120 while (cursor.getNext() != null) { 121 String nodeName = cursor.getLocalName(); 122 123 if (StringUtils.equalsIgnoreCase("name", nodeName)) { 124 rule.setName(StringUtils.trim(cursor.collectDescendantText(false))); 125 126 } else if (StringUtils.equalsIgnoreCase("description", nodeName)) { 127 rule.setDescription(StringUtils.trim(cursor.collectDescendantText(false))); 128 129 } else if (StringUtils.equalsIgnoreCase("key", nodeName)) { 130 rule.setKey(StringUtils.trim(cursor.collectDescendantText(false))); 131 132 } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) { 133 rule.setConfigKey(StringUtils.trim(cursor.collectDescendantText(false))); 134 135 } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) { 136 rule.setSeverity(RulePriority.valueOf(StringUtils.trim(cursor.collectDescendantText(false)))); 137 138 } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) { 139 rule.setCardinality(Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false)))); 140 141 } else if (StringUtils.equalsIgnoreCase("param", nodeName)) { 142 processParameter(rule, cursor); 143 } 144 } 145 if (StringUtils.isEmpty(rule.getKey())) { 146 throw new SonarException("Node <key> is missing in <rule>"); 147 } 148 if (StringUtils.isEmpty(rule.getName())) { 149 throw new SonarException("Node <name> is missing in <rule>"); 150 } 151 } 152 153 private static void processParameter(Rule rule, SMInputCursor ruleC) throws XMLStreamException { 154 RuleParam param = rule.createParameter(); 155 156 String keyAttribute = ruleC.getAttrValue("key"); 157 if (StringUtils.isNotBlank(keyAttribute)) { 158 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 159 param.setKey(StringUtils.trim(keyAttribute)); 160 } 161 162 String typeAttribute = ruleC.getAttrValue("type"); 163 if (StringUtils.isNotBlank(typeAttribute)) { 164 /* BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT */ 165 param.setType(StringUtils.trim(typeAttribute)); 166 } 167 168 SMInputCursor paramC = ruleC.childElementCursor(); 169 while (paramC.getNext() != null) { 170 String propNodeName = paramC.getLocalName(); 171 String propText = StringUtils.trim(paramC.collectDescendantText(false)); 172 if (StringUtils.equalsIgnoreCase("key", propNodeName)) { 173 param.setKey(propText); 174 175 } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) { 176 param.setDescription(propText); 177 178 } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) { 179 param.setType(propText); 180 181 } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) { 182 param.setDefaultValue(propText); 183 } 184 } 185 if (StringUtils.isEmpty(param.getKey())) { 186 throw new SonarException("Node <key> is missing in <param>"); 187 } 188 } 189 }