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