You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PostProjectAnalysisTasksExecutor.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.ce.task.projectanalysis.api.posttask;
  21. import java.util.Collection;
  22. import java.util.Date;
  23. import java.util.Map;
  24. import java.util.Optional;
  25. import java.util.Set;
  26. import javax.annotation.CheckForNull;
  27. import javax.annotation.Nullable;
  28. import org.sonar.api.ce.posttask.Analysis;
  29. import org.sonar.api.ce.posttask.Branch;
  30. import org.sonar.api.ce.posttask.CeTask;
  31. import org.sonar.api.ce.posttask.Organization;
  32. import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
  33. import org.sonar.api.ce.posttask.Project;
  34. import org.sonar.api.ce.posttask.QualityGate;
  35. import org.sonar.api.ce.posttask.ScannerContext;
  36. import org.sonar.api.utils.System2;
  37. import org.sonar.api.utils.log.Logger;
  38. import org.sonar.api.utils.log.Loggers;
  39. import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
  40. import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
  41. import org.sonar.ce.task.projectanalysis.qualitygate.Condition;
  42. import org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus;
  43. import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateHolder;
  44. import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus;
  45. import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatusHolder;
  46. import org.sonar.ce.task.step.ComputationStepExecutor;
  47. import org.sonar.core.util.logs.Profiler;
  48. import org.sonar.core.util.stream.MoreCollectors;
  49. import static com.google.common.base.Preconditions.checkArgument;
  50. import static java.lang.String.format;
  51. import static java.util.Objects.requireNonNull;
  52. import static java.util.Optional.empty;
  53. import static java.util.Optional.of;
  54. import static java.util.Optional.ofNullable;
  55. import static org.sonar.api.ce.posttask.CeTask.Status.FAILED;
  56. import static org.sonar.api.ce.posttask.CeTask.Status.SUCCESS;
  57. import static org.sonar.db.component.BranchType.PULL_REQUEST;
  58. /**
  59. * Responsible for calling {@link PostProjectAnalysisTask} implementations (if any).
  60. */
  61. public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor.Listener {
  62. private static final PostProjectAnalysisTask[] NO_POST_PROJECT_ANALYSIS_TASKS = new PostProjectAnalysisTask[0];
  63. private static final Logger LOG = Loggers.get(PostProjectAnalysisTasksExecutor.class);
  64. private final org.sonar.ce.task.CeTask ceTask;
  65. private final AnalysisMetadataHolder analysisMetadataHolder;
  66. private final QualityGateHolder qualityGateHolder;
  67. private final QualityGateStatusHolder qualityGateStatusHolder;
  68. private final PostProjectAnalysisTask[] postProjectAnalysisTasks;
  69. private final BatchReportReader reportReader;
  70. private final System2 system2;
  71. /**
  72. * Constructor used by Pico when there is no {@link PostProjectAnalysisTask} in the container.
  73. */
  74. public PostProjectAnalysisTasksExecutor(org.sonar.ce.task.CeTask ceTask,
  75. AnalysisMetadataHolder analysisMetadataHolder,
  76. QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder,
  77. BatchReportReader reportReader, System2 system2) {
  78. this(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader, system2, null);
  79. }
  80. public PostProjectAnalysisTasksExecutor(org.sonar.ce.task.CeTask ceTask,
  81. AnalysisMetadataHolder analysisMetadataHolder,
  82. QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder,
  83. BatchReportReader reportReader, System2 system2,
  84. @Nullable PostProjectAnalysisTask[] postProjectAnalysisTasks) {
  85. this.analysisMetadataHolder = analysisMetadataHolder;
  86. this.qualityGateHolder = qualityGateHolder;
  87. this.qualityGateStatusHolder = qualityGateStatusHolder;
  88. this.ceTask = ceTask;
  89. this.reportReader = reportReader;
  90. this.postProjectAnalysisTasks = postProjectAnalysisTasks == null ? NO_POST_PROJECT_ANALYSIS_TASKS : postProjectAnalysisTasks;
  91. this.system2 = system2;
  92. }
  93. @Override
  94. public void finished(boolean allStepsExecuted) {
  95. if (postProjectAnalysisTasks.length == 0) {
  96. return;
  97. }
  98. ProjectAnalysisImpl projectAnalysis = createProjectAnalysis(allStepsExecuted ? SUCCESS : FAILED);
  99. for (PostProjectAnalysisTask postProjectAnalysisTask : postProjectAnalysisTasks) {
  100. executeTask(projectAnalysis, postProjectAnalysisTask);
  101. }
  102. }
  103. private static void executeTask(ProjectAnalysisImpl projectAnalysis, PostProjectAnalysisTask postProjectAnalysisTask) {
  104. String status = "FAILED";
  105. Profiler task = Profiler.create(LOG).logTimeLast(true);
  106. try {
  107. task.start();
  108. postProjectAnalysisTask.finished(new ContextImpl(projectAnalysis, task));
  109. status = "SUCCESS";
  110. } catch (Exception e) {
  111. LOG.error("Execution of task " + postProjectAnalysisTask.getClass() + " failed", e);
  112. } finally {
  113. task.addContext("status", status);
  114. task.stopInfo("{}", postProjectAnalysisTask.getDescription());
  115. }
  116. }
  117. private static class ContextImpl implements PostProjectAnalysisTask.Context {
  118. private final ProjectAnalysisImpl projectAnalysis;
  119. private final Profiler task;
  120. private ContextImpl(ProjectAnalysisImpl projectAnalysis, Profiler task) {
  121. this.projectAnalysis = projectAnalysis;
  122. this.task = task;
  123. }
  124. @Override
  125. public PostProjectAnalysisTask.ProjectAnalysis getProjectAnalysis() {
  126. return projectAnalysis;
  127. }
  128. @Override
  129. public PostProjectAnalysisTask.LogStatistics getLogStatistics() {
  130. return new LogStatisticsImpl(task);
  131. }
  132. }
  133. private static class LogStatisticsImpl implements PostProjectAnalysisTask.LogStatistics {
  134. private final Profiler profiler;
  135. private LogStatisticsImpl(Profiler profiler) {
  136. this.profiler = profiler;
  137. }
  138. @Override
  139. public PostProjectAnalysisTask.LogStatistics add(String key, Object value) {
  140. requireNonNull(key, "Statistic has null key");
  141. requireNonNull(value, () -> format("Statistic with key [%s] has null value", key));
  142. checkArgument(!key.equalsIgnoreCase("time") && !key.equalsIgnoreCase("status"),
  143. "Statistic with key [%s] is not accepted", key);
  144. checkArgument(!profiler.hasContext(key), "Statistic with key [%s] is already present", key);
  145. profiler.addContext(key, value);
  146. return this;
  147. }
  148. }
  149. private ProjectAnalysisImpl createProjectAnalysis(CeTask.Status status) {
  150. return new ProjectAnalysisImpl(
  151. createOrganization(),
  152. new CeTaskImpl(this.ceTask.getUuid(), status),
  153. createProject(this.ceTask),
  154. getAnalysis().orElse(null),
  155. getAnalysis().map(a -> a.getDate().getTime()).orElse(system2.now()),
  156. ScannerContextImpl.from(reportReader.readContextProperties()),
  157. status == SUCCESS ? createQualityGate() : null,
  158. createBranch(),
  159. reportReader.readMetadata().getScmRevisionId());
  160. }
  161. @CheckForNull
  162. private Organization createOrganization() {
  163. if (!analysisMetadataHolder.isOrganizationsEnabled()) {
  164. return null;
  165. }
  166. org.sonar.ce.task.projectanalysis.analysis.Organization organization = analysisMetadataHolder.getOrganization();
  167. return new OrganizationImpl(organization.getName(), organization.getKey());
  168. }
  169. private Optional<Analysis> getAnalysis() {
  170. Long analysisDate = getAnalysisDate();
  171. if (analysisDate != null) {
  172. return of(new AnalysisImpl(analysisMetadataHolder.getUuid(), analysisDate, analysisMetadataHolder.getScmRevision()));
  173. }
  174. return empty();
  175. }
  176. private static Project createProject(org.sonar.ce.task.CeTask ceTask) {
  177. return ceTask.getMainComponent()
  178. .map(c -> new ProjectImpl(
  179. c.getUuid(),
  180. c.getKey().orElseThrow(() -> new IllegalStateException("Missing project key")),
  181. c.getName().orElseThrow(() -> new IllegalStateException("Missing project name"))))
  182. .orElseThrow(() -> new IllegalStateException("Report processed for a task of a deleted component"));
  183. }
  184. @CheckForNull
  185. private Long getAnalysisDate() {
  186. if (this.analysisMetadataHolder.hasAnalysisDateBeenSet()) {
  187. return this.analysisMetadataHolder.getAnalysisDate();
  188. }
  189. return null;
  190. }
  191. @CheckForNull
  192. private QualityGate createQualityGate() {
  193. Optional<org.sonar.ce.task.projectanalysis.qualitygate.QualityGate> qualityGateOptional = this.qualityGateHolder.getQualityGate();
  194. if (qualityGateOptional.isPresent()) {
  195. org.sonar.ce.task.projectanalysis.qualitygate.QualityGate qualityGate = qualityGateOptional.get();
  196. return new QualityGateImpl(
  197. String.valueOf(qualityGate.getId()),
  198. qualityGate.getName(),
  199. convert(qualityGateStatusHolder.getStatus()),
  200. convert(qualityGate.getConditions(), qualityGateStatusHolder.getStatusPerConditions()));
  201. }
  202. return null;
  203. }
  204. @CheckForNull
  205. private BranchImpl createBranch() {
  206. org.sonar.ce.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch();
  207. String branchKey = analysisBranch.getType() == PULL_REQUEST ? analysisBranch.getPullRequestKey() : analysisBranch.getName();
  208. return new BranchImpl(analysisBranch.isMain(), branchKey, Branch.Type.valueOf(analysisBranch.getType().name()));
  209. }
  210. private static QualityGate.Status convert(QualityGateStatus status) {
  211. switch (status) {
  212. case OK:
  213. return QualityGate.Status.OK;
  214. case ERROR:
  215. return QualityGate.Status.ERROR;
  216. default:
  217. throw new IllegalArgumentException(format(
  218. "Unsupported value '%s' of QualityGateStatus can not be converted to QualityGate.Status",
  219. status));
  220. }
  221. }
  222. private static Collection<QualityGate.Condition> convert(Set<Condition> conditions, Map<Condition, ConditionStatus> statusPerConditions) {
  223. return conditions.stream()
  224. .map(new ConditionToCondition(statusPerConditions)::apply)
  225. .collect(MoreCollectors.toList(statusPerConditions.size()));
  226. }
  227. private static class ProjectAnalysisImpl implements PostProjectAnalysisTask.ProjectAnalysis {
  228. @Nullable
  229. private final Organization organization;
  230. private final CeTask ceTask;
  231. private final Project project;
  232. private final long date;
  233. private final ScannerContext scannerContext;
  234. @Nullable
  235. private final QualityGate qualityGate;
  236. @Nullable
  237. private final Branch branch;
  238. @Nullable
  239. private final Analysis analysis;
  240. private final String scmRevisionId;
  241. private ProjectAnalysisImpl(@Nullable Organization organization, CeTask ceTask, Project project,
  242. @Nullable Analysis analysis, long date,
  243. ScannerContext scannerContext, @Nullable QualityGate qualityGate, @Nullable Branch branch, String scmRevisionId) {
  244. this.organization = organization;
  245. this.ceTask = requireNonNull(ceTask, "ceTask can not be null");
  246. this.project = requireNonNull(project, "project can not be null");
  247. this.analysis = analysis;
  248. this.date = date;
  249. this.scannerContext = requireNonNull(scannerContext, "scannerContext can not be null");
  250. this.qualityGate = qualityGate;
  251. this.branch = branch;
  252. this.scmRevisionId = scmRevisionId;
  253. }
  254. @Override
  255. public Optional<Organization> getOrganization() {
  256. return Optional.ofNullable(organization);
  257. }
  258. @Override
  259. public CeTask getCeTask() {
  260. return ceTask;
  261. }
  262. @Override
  263. public Project getProject() {
  264. return project;
  265. }
  266. @Override
  267. public Optional<Branch> getBranch() {
  268. return ofNullable(branch);
  269. }
  270. @Override
  271. @CheckForNull
  272. public QualityGate getQualityGate() {
  273. return qualityGate;
  274. }
  275. @Override
  276. public Date getDate() {
  277. return new Date(date);
  278. }
  279. @Override
  280. public Optional<Date> getAnalysisDate() {
  281. return analysis == null ? empty() : ofNullable(analysis.getDate());
  282. }
  283. @Override
  284. public Optional<Analysis> getAnalysis() {
  285. return ofNullable(analysis);
  286. }
  287. @Override
  288. public ScannerContext getScannerContext() {
  289. return scannerContext;
  290. }
  291. @Override
  292. public String getScmRevisionId() {
  293. return scmRevisionId;
  294. }
  295. @Override
  296. public String toString() {
  297. return "ProjectAnalysis{" +
  298. "ceTask=" + ceTask +
  299. ", project=" + project +
  300. ", date=" + date +
  301. ", scannerContext=" + scannerContext +
  302. ", qualityGate=" + qualityGate +
  303. ", analysis=" + analysis +
  304. '}';
  305. }
  306. }
  307. private static class AnalysisImpl implements Analysis {
  308. private final String analysisUuid;
  309. private final long date;
  310. private final Optional<String> revision;
  311. private AnalysisImpl(String analysisUuid, long date, Optional<String> revision) {
  312. this.analysisUuid = analysisUuid;
  313. this.date = date;
  314. this.revision = revision;
  315. }
  316. @Override
  317. public String getAnalysisUuid() {
  318. return analysisUuid;
  319. }
  320. @Override
  321. public Date getDate() {
  322. return new Date(date);
  323. }
  324. @Override
  325. public Optional<String> getRevision() {
  326. return revision;
  327. }
  328. }
  329. private static class OrganizationImpl implements Organization {
  330. private final String name;
  331. private final String key;
  332. private OrganizationImpl(String name, String key) {
  333. this.name = requireNonNull(name, "name can't be null");
  334. this.key = requireNonNull(key, "key can't be null");
  335. }
  336. @Override
  337. public String getName() {
  338. return name;
  339. }
  340. @Override
  341. public String getKey() {
  342. return key;
  343. }
  344. }
  345. }