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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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. new CeTaskImpl(this.ceTask.getUuid(), status),
  152. createProject(this.ceTask),
  153. getAnalysis().orElse(null),
  154. getAnalysis().map(a -> a.getDate().getTime()).orElse(system2.now()),
  155. ScannerContextImpl.from(reportReader.readContextProperties()),
  156. status == SUCCESS ? createQualityGate() : null,
  157. createBranch(),
  158. reportReader.readMetadata().getScmRevisionId());
  159. }
  160. private Optional<Analysis> getAnalysis() {
  161. Long analysisDate = getAnalysisDate();
  162. if (analysisDate != null) {
  163. return of(new AnalysisImpl(analysisMetadataHolder.getUuid(), analysisDate, analysisMetadataHolder.getScmRevision()));
  164. }
  165. return empty();
  166. }
  167. private static Project createProject(org.sonar.ce.task.CeTask ceTask) {
  168. return ceTask.getMainComponent()
  169. .map(c -> new ProjectImpl(
  170. c.getUuid(),
  171. c.getKey().orElseThrow(() -> new IllegalStateException("Missing project key")),
  172. c.getName().orElseThrow(() -> new IllegalStateException("Missing project name"))))
  173. .orElseThrow(() -> new IllegalStateException("Report processed for a task of a deleted component"));
  174. }
  175. @CheckForNull
  176. private Long getAnalysisDate() {
  177. if (this.analysisMetadataHolder.hasAnalysisDateBeenSet()) {
  178. return this.analysisMetadataHolder.getAnalysisDate();
  179. }
  180. return null;
  181. }
  182. @CheckForNull
  183. private QualityGate createQualityGate() {
  184. Optional<org.sonar.ce.task.projectanalysis.qualitygate.QualityGate> qualityGateOptional = this.qualityGateHolder.getQualityGate();
  185. if (qualityGateOptional.isPresent()) {
  186. org.sonar.ce.task.projectanalysis.qualitygate.QualityGate qualityGate = qualityGateOptional.get();
  187. return new QualityGateImpl(
  188. qualityGate.getUuid(),
  189. qualityGate.getName(),
  190. convert(qualityGateStatusHolder.getStatus()),
  191. convert(qualityGate.getConditions(), qualityGateStatusHolder.getStatusPerConditions()));
  192. }
  193. return null;
  194. }
  195. @CheckForNull
  196. private BranchImpl createBranch() {
  197. org.sonar.ce.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch();
  198. String branchKey = analysisBranch.getType() == PULL_REQUEST ? analysisBranch.getPullRequestKey() : analysisBranch.getName();
  199. return new BranchImpl(analysisBranch.isMain(), branchKey, Branch.Type.valueOf(analysisBranch.getType().name()));
  200. }
  201. private static QualityGate.Status convert(QualityGateStatus status) {
  202. switch (status) {
  203. case OK:
  204. return QualityGate.Status.OK;
  205. case ERROR:
  206. return QualityGate.Status.ERROR;
  207. default:
  208. throw new IllegalArgumentException(format(
  209. "Unsupported value '%s' of QualityGateStatus can not be converted to QualityGate.Status",
  210. status));
  211. }
  212. }
  213. private static Collection<QualityGate.Condition> convert(Set<Condition> conditions, Map<Condition, ConditionStatus> statusPerConditions) {
  214. return conditions.stream()
  215. .map(new ConditionToCondition(statusPerConditions)::apply)
  216. .collect(MoreCollectors.toList(statusPerConditions.size()));
  217. }
  218. private static class ProjectAnalysisImpl implements PostProjectAnalysisTask.ProjectAnalysis {
  219. private final CeTask ceTask;
  220. private final Project project;
  221. private final long date;
  222. private final ScannerContext scannerContext;
  223. @Nullable
  224. private final QualityGate qualityGate;
  225. @Nullable
  226. private final Branch branch;
  227. @Nullable
  228. private final Analysis analysis;
  229. private final String scmRevisionId;
  230. private ProjectAnalysisImpl(CeTask ceTask, Project project,
  231. @Nullable Analysis analysis, long date,
  232. ScannerContext scannerContext, @Nullable QualityGate qualityGate, @Nullable Branch branch, String scmRevisionId) {
  233. this.ceTask = requireNonNull(ceTask, "ceTask can not be null");
  234. this.project = requireNonNull(project, "project can not be null");
  235. this.analysis = analysis;
  236. this.date = date;
  237. this.scannerContext = requireNonNull(scannerContext, "scannerContext can not be null");
  238. this.qualityGate = qualityGate;
  239. this.branch = branch;
  240. this.scmRevisionId = scmRevisionId;
  241. }
  242. /**
  243. *
  244. * @deprecated since 8.7. No longer used - it's always empty.
  245. */
  246. @Override
  247. @Deprecated
  248. public Optional<Organization> getOrganization() {
  249. return empty();
  250. }
  251. @Override
  252. public CeTask getCeTask() {
  253. return ceTask;
  254. }
  255. @Override
  256. public Project getProject() {
  257. return project;
  258. }
  259. @Override
  260. public Optional<Branch> getBranch() {
  261. return ofNullable(branch);
  262. }
  263. @Override
  264. @CheckForNull
  265. public QualityGate getQualityGate() {
  266. return qualityGate;
  267. }
  268. @Override
  269. public Date getDate() {
  270. return new Date(date);
  271. }
  272. @Override
  273. public Optional<Date> getAnalysisDate() {
  274. return analysis == null ? empty() : ofNullable(analysis.getDate());
  275. }
  276. @Override
  277. public Optional<Analysis> getAnalysis() {
  278. return ofNullable(analysis);
  279. }
  280. @Override
  281. public ScannerContext getScannerContext() {
  282. return scannerContext;
  283. }
  284. @Override
  285. public String getScmRevisionId() {
  286. return scmRevisionId;
  287. }
  288. @Override
  289. public String toString() {
  290. return "ProjectAnalysis{" +
  291. "ceTask=" + ceTask +
  292. ", project=" + project +
  293. ", date=" + date +
  294. ", scannerContext=" + scannerContext +
  295. ", qualityGate=" + qualityGate +
  296. ", analysis=" + analysis +
  297. '}';
  298. }
  299. }
  300. private static class AnalysisImpl implements Analysis {
  301. private final String analysisUuid;
  302. private final long date;
  303. private final Optional<String> revision;
  304. private AnalysisImpl(String analysisUuid, long date, Optional<String> revision) {
  305. this.analysisUuid = analysisUuid;
  306. this.date = date;
  307. this.revision = revision;
  308. }
  309. @Override
  310. public String getAnalysisUuid() {
  311. return analysisUuid;
  312. }
  313. @Override
  314. public Date getDate() {
  315. return new Date(date);
  316. }
  317. @Override
  318. public Optional<String> getRevision() {
  319. return revision;
  320. }
  321. }
  322. }