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    }