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.batch.scan.report;
021
022 import com.google.common.annotations.VisibleForTesting;
023 import com.google.common.io.Closeables;
024 import org.apache.commons.lang.StringUtils;
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027 import org.sonar.api.BatchComponent;
028 import org.sonar.api.batch.fs.FileSystem;
029 import org.sonar.api.batch.fs.InputDir;
030 import org.sonar.api.batch.fs.InputFile;
031 import org.sonar.api.batch.fs.InputPath;
032 import org.sonar.api.batch.fs.internal.DefaultInputDir;
033 import org.sonar.api.batch.fs.internal.DefaultInputFile;
034 import org.sonar.api.config.Settings;
035 import org.sonar.api.issue.internal.DefaultIssue;
036 import org.sonar.api.platform.Server;
037 import org.sonar.api.resources.Project;
038 import org.sonar.api.rule.RuleKey;
039 import org.sonar.api.rules.Rule;
040 import org.sonar.api.rules.RuleFinder;
041 import org.sonar.api.user.User;
042 import org.sonar.api.user.UserFinder;
043 import org.sonar.api.utils.SonarException;
044 import org.sonar.api.utils.text.JsonWriter;
045 import org.sonar.batch.bootstrap.AnalysisMode;
046 import org.sonar.batch.events.BatchStepEvent;
047 import org.sonar.batch.events.EventBus;
048 import org.sonar.batch.issue.IssueCache;
049 import org.sonar.batch.scan.filesystem.InputPathCache;
050
051 import java.io.BufferedWriter;
052 import java.io.File;
053 import java.io.FileWriter;
054 import java.io.IOException;
055 import java.io.Writer;
056 import java.util.ArrayList;
057 import java.util.List;
058 import java.util.Set;
059
060 import static com.google.common.collect.Sets.newHashSet;
061
062 /**
063 * @since 3.6
064 */
065
066 public class JsonReport implements BatchComponent {
067
068 private static final Logger LOG = LoggerFactory.getLogger(JsonReport.class);
069 private final Settings settings;
070 private final FileSystem fileSystem;
071 private final Server server;
072 private final RuleFinder ruleFinder;
073 private final IssueCache issueCache;
074 private final EventBus eventBus;
075 private final AnalysisMode analysisMode;
076 private final UserFinder userFinder;
077 private final InputPathCache fileCache;
078 private final Project rootModule;
079
080 public JsonReport(Settings settings, FileSystem fileSystem, Server server, RuleFinder ruleFinder, IssueCache issueCache,
081 EventBus eventBus, AnalysisMode analysisMode, UserFinder userFinder, Project rootModule, InputPathCache fileCache) {
082 this.settings = settings;
083 this.fileSystem = fileSystem;
084 this.server = server;
085 this.ruleFinder = ruleFinder;
086 this.issueCache = issueCache;
087 this.eventBus = eventBus;
088 this.analysisMode = analysisMode;
089 this.userFinder = userFinder;
090 this.rootModule = rootModule;
091 this.fileCache = fileCache;
092 }
093
094 public void execute() {
095 if (analysisMode.isPreview()) {
096 eventBus.fireEvent(new BatchStepEvent("JSON report", true));
097 exportResults();
098 eventBus.fireEvent(new BatchStepEvent("JSON report", false));
099 }
100 }
101
102 private void exportResults() {
103 File exportFile = new File(fileSystem.workDir(), settings.getString("sonar.report.export.path"));
104
105 LOG.info("Export results to " + exportFile.getAbsolutePath());
106 Writer output = null;
107 try {
108 output = new BufferedWriter(new FileWriter(exportFile));
109 writeJson(output);
110
111 } catch (IOException e) {
112 throw new IllegalStateException("Unable to write report results in file " + exportFile.getAbsolutePath(), e);
113 } finally {
114 Closeables.closeQuietly(output);
115 }
116 }
117
118 @VisibleForTesting
119 void writeJson(Writer writer) {
120 try {
121 JsonWriter json = JsonWriter.of(writer);
122 json.beginObject();
123 json.prop("version", server.getVersion());
124
125 Set<RuleKey> ruleKeys = newHashSet();
126 Set<String> userLogins = newHashSet();
127 writeJsonIssues(json, ruleKeys, userLogins);
128 writeJsonComponents(json);
129 writeJsonRules(json, ruleKeys);
130 List<User> users = userFinder.findByLogins(new ArrayList<String>(userLogins));
131 writeUsers(json, users);
132 json.endObject().close();
133
134 } catch (IOException e) {
135 throw new SonarException("Unable to write JSON report", e);
136 }
137 }
138
139 private void writeJsonIssues(JsonWriter json, Set<RuleKey> ruleKeys, Set<String> logins) throws IOException {
140 json.name("issues").beginArray();
141 for (DefaultIssue issue : getIssues()) {
142 if (issue.resolution() == null) {
143 json
144 .beginObject()
145 .prop("key", issue.key())
146 .prop("component", issue.componentKey())
147 .prop("line", issue.line())
148 .prop("message", issue.message())
149 .prop("severity", issue.severity())
150 .prop("rule", issue.ruleKey().toString())
151 .prop("status", issue.status())
152 .prop("resolution", issue.resolution())
153 .prop("isNew", issue.isNew())
154 .prop("reporter", issue.reporter())
155 .prop("assignee", issue.assignee())
156 .prop("effortToFix", issue.effortToFix())
157 .propDateTime("creationDate", issue.creationDate())
158 .propDateTime("updateDate", issue.updateDate())
159 .propDateTime("closeDate", issue.closeDate());
160 if (issue.reporter() != null) {
161 logins.add(issue.reporter());
162 }
163 if (issue.assignee() != null) {
164 logins.add(issue.assignee());
165 }
166 json.endObject();
167 ruleKeys.add(issue.ruleKey());
168 }
169 }
170 json.endArray();
171 }
172
173 private void writeJsonComponents(JsonWriter json) throws IOException {
174 json.name("components").beginArray();
175 // Dump modules
176 writeJsonModuleComponents(json, rootModule);
177 for (InputPath inputPath : fileCache.all()) {
178 if (inputPath instanceof InputFile) {
179 InputFile inputFile = (InputFile) inputPath;
180 String key = ((DefaultInputFile) inputFile).key();
181 json
182 .beginObject()
183 .prop("key", key)
184 .prop("path", inputFile.relativePath())
185 .prop("moduleKey", StringUtils.substringBeforeLast(key, ":"))
186 .prop("status", inputFile.status().name())
187 .endObject();
188 } else {
189 InputDir inputDir = (InputDir) inputPath;
190 String key = ((DefaultInputDir) inputDir).key();
191 json
192 .beginObject()
193 .prop("key", key)
194 .prop("path", inputDir.relativePath())
195 .prop("moduleKey", StringUtils.substringBeforeLast(key, ":"))
196 .endObject();
197 }
198
199 }
200 json.endArray();
201 }
202
203 private void writeJsonModuleComponents(JsonWriter json, Project module) {
204 json
205 .beginObject()
206 .prop("key", module.getEffectiveKey())
207 .prop("path", module.getPath())
208 .endObject();
209 for (Project subModule : module.getModules()) {
210 writeJsonModuleComponents(json, subModule);
211 }
212 }
213
214 private void writeJsonRules(JsonWriter json, Set<RuleKey> ruleKeys) throws IOException {
215 json.name("rules").beginArray();
216 for (RuleKey ruleKey : ruleKeys) {
217 json
218 .beginObject()
219 .prop("key", ruleKey.toString())
220 .prop("rule", ruleKey.rule())
221 .prop("repository", ruleKey.repository())
222 .prop("name", getRuleName(ruleKey))
223 .endObject();
224 }
225 json.endArray();
226 }
227
228 private void writeUsers(JsonWriter json, List<User> users) throws IOException {
229 json.name("users").beginArray();
230 for (User user : users) {
231 json
232 .beginObject()
233 .prop("login", user.login())
234 .prop("name", user.name())
235 .endObject();
236 }
237 json.endArray();
238 }
239
240 private String getRuleName(RuleKey ruleKey) {
241 Rule rule = ruleFinder.findByKey(ruleKey);
242 return rule != null ? rule.getName() : null;
243 }
244
245 @VisibleForTesting
246 Iterable<DefaultIssue> getIssues() {
247 return issueCache.all();
248 }
249 }