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.

PersistIssuesStep.java 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 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.step;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23. import java.util.Map;
  24. import org.apache.commons.io.FileUtils;
  25. import org.sonar.api.utils.System2;
  26. import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
  27. import org.sonar.ce.task.projectanalysis.issue.RuleRepository;
  28. import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver;
  29. import org.sonar.ce.task.step.ComputationStep;
  30. import org.sonar.core.issue.DefaultIssue;
  31. import org.sonar.core.util.CloseableIterator;
  32. import org.sonar.core.util.UuidFactory;
  33. import org.sonar.db.BatchSession;
  34. import org.sonar.db.DbClient;
  35. import org.sonar.db.DbSession;
  36. import org.sonar.db.issue.IssueChangeMapper;
  37. import org.sonar.db.issue.IssueDto;
  38. import org.sonar.db.issue.IssueMapper;
  39. import org.sonar.server.issue.IssueStorage;
  40. import static org.sonar.core.util.stream.MoreCollectors.toList;
  41. import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
  42. public class PersistIssuesStep implements ComputationStep {
  43. // holding up to 1000 DefaultIssue (max size of addedIssues and updatedIssues at any given time) in memory should not
  44. // be a problem while making sure we leverage extensively the batch feature to speed up persistence
  45. private static final int ISSUE_BATCHING_SIZE = BatchSession.MAX_BATCH_SIZE * 2;
  46. private final DbClient dbClient;
  47. private final System2 system2;
  48. private final UpdateConflictResolver conflictResolver;
  49. private final RuleRepository ruleRepository;
  50. private final ProtoIssueCache protoIssueCache;
  51. private final IssueStorage issueStorage;
  52. private final UuidFactory uuidFactory;
  53. public PersistIssuesStep(DbClient dbClient, System2 system2, UpdateConflictResolver conflictResolver,
  54. RuleRepository ruleRepository, ProtoIssueCache protoIssueCache, IssueStorage issueStorage, UuidFactory uuidFactory) {
  55. this.dbClient = dbClient;
  56. this.system2 = system2;
  57. this.conflictResolver = conflictResolver;
  58. this.ruleRepository = ruleRepository;
  59. this.protoIssueCache = protoIssueCache;
  60. this.issueStorage = issueStorage;
  61. this.uuidFactory = uuidFactory;
  62. }
  63. @Override
  64. public void execute(ComputationStep.Context context) {
  65. context.getStatistics().add("cacheSize", FileUtils.byteCountToDisplaySize(protoIssueCache.fileSize()));
  66. IssueStatistics statistics = new IssueStatistics();
  67. try (DbSession dbSession = dbClient.openSession(true);
  68. CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse()) {
  69. List<DefaultIssue> addedIssues = new ArrayList<>(ISSUE_BATCHING_SIZE);
  70. List<DefaultIssue> updatedIssues = new ArrayList<>(ISSUE_BATCHING_SIZE);
  71. IssueMapper mapper = dbSession.getMapper(IssueMapper.class);
  72. IssueChangeMapper changeMapper = dbSession.getMapper(IssueChangeMapper.class);
  73. while (issues.hasNext()) {
  74. DefaultIssue issue = issues.next();
  75. if (issue.isNew() || issue.isCopied()) {
  76. addedIssues.add(issue);
  77. if (addedIssues.size() >= ISSUE_BATCHING_SIZE) {
  78. persistNewIssues(statistics, addedIssues, mapper, changeMapper);
  79. addedIssues.clear();
  80. }
  81. } else if (issue.isChanged()) {
  82. updatedIssues.add(issue);
  83. if (updatedIssues.size() >= ISSUE_BATCHING_SIZE) {
  84. persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper);
  85. updatedIssues.clear();
  86. }
  87. }
  88. }
  89. persistNewIssues(statistics, addedIssues, mapper, changeMapper);
  90. persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper);
  91. flushSession(dbSession);
  92. } finally {
  93. statistics.dumpTo(context);
  94. }
  95. }
  96. private void persistNewIssues(IssueStatistics statistics, List<DefaultIssue> addedIssues, IssueMapper mapper, IssueChangeMapper changeMapper) {
  97. if (addedIssues.isEmpty()) {
  98. return;
  99. }
  100. long now = system2.now();
  101. addedIssues.forEach(i -> {
  102. String ruleUuid = ruleRepository.getByKey(i.ruleKey()).getUuid();
  103. IssueDto dto = IssueDto.toDtoForComputationInsert(i, ruleUuid, now);
  104. mapper.insert(dto);
  105. statistics.inserts++;
  106. });
  107. addedIssues.forEach(i -> issueStorage.insertChanges(changeMapper, i, uuidFactory));
  108. }
  109. private void persistUpdatedIssues(IssueStatistics statistics, List<DefaultIssue> updatedIssues, IssueMapper mapper, IssueChangeMapper changeMapper) {
  110. if (updatedIssues.isEmpty()) {
  111. return;
  112. }
  113. long now = system2.now();
  114. updatedIssues.forEach(i -> {
  115. IssueDto dto = IssueDto.toDtoForUpdate(i, now);
  116. mapper.updateIfBeforeSelectedDate(dto);
  117. statistics.updates++;
  118. });
  119. // retrieve those of the updatedIssues which have not been updated and apply conflictResolver on them
  120. List<String> updatedIssueKeys = updatedIssues.stream().map(DefaultIssue::key).collect(toList(updatedIssues.size()));
  121. List<IssueDto> conflictIssueKeys = mapper.selectByKeysIfNotUpdatedAt(updatedIssueKeys, now);
  122. if (!conflictIssueKeys.isEmpty()) {
  123. Map<String, DefaultIssue> issuesByKeys = updatedIssues.stream().collect(uniqueIndex(DefaultIssue::key, updatedIssues.size()));
  124. conflictIssueKeys
  125. .forEach(dbIssue -> {
  126. DefaultIssue updatedIssue = issuesByKeys.get(dbIssue.getKey());
  127. conflictResolver.resolve(updatedIssue, dbIssue, mapper);
  128. statistics.merged++;
  129. });
  130. }
  131. updatedIssues.forEach(i -> issueStorage.insertChanges(changeMapper, i, uuidFactory));
  132. }
  133. private static void flushSession(DbSession dbSession) {
  134. dbSession.flushStatements();
  135. dbSession.commit();
  136. }
  137. @Override
  138. public String getDescription() {
  139. return "Persist issues";
  140. }
  141. private static class IssueStatistics {
  142. private int inserts = 0;
  143. private int updates = 0;
  144. private int merged = 0;
  145. private void dumpTo(ComputationStep.Context context) {
  146. context.getStatistics()
  147. .add("inserts", String.valueOf(inserts))
  148. .add("updates", String.valueOf(updates))
  149. .add("merged", String.valueOf(merged));
  150. }
  151. }
  152. }