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.batch;
021    
022    import static org.apache.commons.lang.StringUtils.isNotBlank;
023    
024    import java.io.File;
025    import java.io.FileInputStream;
026    import java.io.FileNotFoundException;
027    import java.io.InputStream;
028    import java.text.ParseException;
029    
030    import javax.xml.stream.XMLStreamException;
031    
032    import org.apache.commons.io.IOUtils;
033    import org.codehaus.staxmate.in.SMEvent;
034    import org.codehaus.staxmate.in.SMHierarchicCursor;
035    import org.codehaus.staxmate.in.SMInputCursor;
036    import org.sonar.api.profiles.RulesProfile;
037    import org.sonar.api.resources.Resource;
038    import org.sonar.api.rules.Rule;
039    import org.sonar.api.rules.RulesManager;
040    import org.sonar.api.rules.Violation;
041    import org.sonar.api.utils.ParsingUtils;
042    import org.sonar.api.utils.StaxParser;
043    
044    /**
045     * @since 1.10
046     */
047    public abstract class AbstractViolationsStaxParser {
048    
049      protected RulesManager rulesManager;
050      protected SensorContext context;
051      protected boolean doSaveViolationsOnUnexistedResource = true;
052    
053      /**
054       * @deprecated since 1.11.
055       */
056      @Deprecated
057      protected AbstractViolationsStaxParser(SensorContext context, RulesManager rulesManager, RulesProfile profile) {
058        this.rulesManager = rulesManager;
059        this.context = context;
060      }
061    
062      protected AbstractViolationsStaxParser(SensorContext context, RulesManager rulesManager) {
063        this.rulesManager = rulesManager;
064        this.context = context;
065      }
066    
067      /**
068       * Cursor for child resources to parse, the returned input cursor should be filtered on SMEvent.START_ELEMENT for optimal perfs
069       * 
070       * @param rootCursor
071       *          the root xml doc cursor
072       * @return a cursor with child resources elements to parse
073       */
074      protected abstract SMInputCursor cursorForResources(SMInputCursor rootCursor) throws XMLStreamException;
075    
076      /**
077       * Cursor for violations to parse for a given resource, the returned input cursor should be filtered on SMEvent.START_ELEMENT for optimal
078       * perfs
079       * 
080       * @param resourcesCursor
081       *          the current resource cursor
082       * @return a cursor with child violations elements to parse
083       */
084      protected abstract SMInputCursor cursorForViolations(SMInputCursor resourcesCursor) throws XMLStreamException;
085    
086      /**
087       * Transforms a given xml resource to a resource Object
088       */
089      protected abstract Resource toResource(SMInputCursor resourceCursor) throws XMLStreamException;
090    
091      protected abstract String messageFor(SMInputCursor violationCursor) throws XMLStreamException;
092    
093      protected abstract String ruleKey(SMInputCursor violationCursor) throws XMLStreamException;
094    
095      protected abstract String keyForPlugin();
096    
097      protected abstract String lineNumberForViolation(SMInputCursor violationCursor) throws XMLStreamException;
098    
099      /**
100       * Specify if violations must be saved even if when the Resource associated to a violation doesn't yet exist.
101       * In that case the Resource is automatically created.
102       * 
103       * @param doSaveViolationsOnUnexistedResource by default, the value is true
104       */
105      public final void setDoSaveViolationsOnUnexistedResource(boolean doSaveViolationsOnUnexistedResource) {
106        this.doSaveViolationsOnUnexistedResource = doSaveViolationsOnUnexistedResource;
107      }
108    
109      public void parse(File violationsXMLFile) throws XMLStreamException {
110        if (violationsXMLFile != null && violationsXMLFile.exists()) {
111          InputStream input = null;
112          try {
113            input = new FileInputStream(violationsXMLFile);
114            parse(input);
115    
116          } catch (FileNotFoundException e) {
117            throw new XMLStreamException(e);
118    
119          } finally {
120            IOUtils.closeQuietly(input);
121          }
122        }
123      }
124    
125      public final void parse(InputStream input) throws XMLStreamException {
126        if (input != null) {
127          StaxParser parser = new StaxParser(new StaxParser.XmlStreamHandler() {
128    
129            public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException {
130              parseResources(rootCursor.advance());
131            }
132          }, true);
133          parser.parse(input);
134        }
135      }
136    
137      private void parseResources(SMInputCursor rootCursor) throws XMLStreamException {
138        SMInputCursor resourcesCursor = cursorForResources(rootCursor);
139        SMEvent event;
140        while ((event = resourcesCursor.getNext()) != null) {
141          if (event.compareTo(SMEvent.START_ELEMENT) == 0) {
142            parseViolations(resourcesCursor);
143          }
144        }
145      }
146    
147      private void parseViolations(SMInputCursor resourcesCursor) throws XMLStreamException {
148        Resource resource = toResource(resourcesCursor);
149        if ( !doSaveViolationsOnUnexistedResource && context.getResource(resource) == null) {
150          return;
151        }
152        SMInputCursor violationsCursor = cursorForViolations(resourcesCursor);
153        SMEvent event;
154        while ((event = violationsCursor.getNext()) != null) {
155          if (event.compareTo(SMEvent.START_ELEMENT) == 0) {
156            createViolationFor(resource, violationsCursor);
157          }
158        }
159      }
160    
161      private void createViolationFor(Resource resource, SMInputCursor violationCursor) throws XMLStreamException {
162        Rule rule = getRule(violationCursor);
163        Integer line = getLineIndex(violationCursor);
164        if (rule != null && resource != null) {
165          Violation violation = new Violation(rule, resource)
166              .setLineId(line)
167              .setMessage(messageFor(violationCursor));
168          context.saveViolation(violation);
169        }
170      }
171    
172      private Rule getRule(SMInputCursor violationCursor) throws XMLStreamException {
173        return rulesManager.getPluginRule(keyForPlugin(), ruleKey(violationCursor));
174      }
175    
176      private Integer getLineIndex(SMInputCursor violationCursor) throws XMLStreamException {
177        String line = lineNumberForViolation(violationCursor);
178        return parseLineIndex(line);
179      }
180    
181      protected static Integer parseLineIndex(String line) {
182        if ( !isNotBlank(line) || line.indexOf('-') != -1) {
183          return null;
184        }
185        try {
186          return (int) ParsingUtils.parseNumber(line);
187        } catch (ParseException ignore) {
188          return null;
189        }
190      }
191    
192    }