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 }