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