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    }