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.

PersistDuplicationDataStep.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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 org.apache.commons.lang3.StringEscapeUtils;
  24. import org.sonar.ce.task.projectanalysis.component.Component;
  25. import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
  26. import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
  27. import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
  28. import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
  29. import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicate;
  30. import org.sonar.ce.task.projectanalysis.duplication.Duplicate;
  31. import org.sonar.ce.task.projectanalysis.duplication.Duplication;
  32. import org.sonar.ce.task.projectanalysis.duplication.DuplicationRepository;
  33. import org.sonar.ce.task.projectanalysis.duplication.InExtendedProjectDuplicate;
  34. import org.sonar.ce.task.projectanalysis.duplication.InProjectDuplicate;
  35. import org.sonar.ce.task.projectanalysis.duplication.InnerDuplicate;
  36. import org.sonar.ce.task.projectanalysis.duplication.TextBlock;
  37. import org.sonar.ce.task.projectanalysis.measure.Measure;
  38. import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
  39. import org.sonar.ce.task.projectanalysis.metric.Metric;
  40. import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
  41. import org.sonar.ce.task.step.ComputationStep;
  42. import org.sonar.db.DbClient;
  43. import org.sonar.db.DbSession;
  44. import org.sonar.db.measure.LiveMeasureDto;
  45. import static com.google.common.collect.Iterables.isEmpty;
  46. import static org.sonar.api.measures.CoreMetrics.DUPLICATIONS_DATA_KEY;
  47. import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
  48. /**
  49. * Compute duplication data measures on files, based on the {@link DuplicationRepository}
  50. */
  51. public class PersistDuplicationDataStep implements ComputationStep {
  52. private final DbClient dbClient;
  53. private final TreeRootHolder treeRootHolder;
  54. private final DuplicationRepository duplicationRepository;
  55. private final MeasureToMeasureDto measureToMeasureDto;
  56. private final Metric duplicationDataMetric;
  57. public PersistDuplicationDataStep(DbClient dbClient, TreeRootHolder treeRootHolder, MetricRepository metricRepository,
  58. DuplicationRepository duplicationRepository, MeasureToMeasureDto measureToMeasureDto) {
  59. this.dbClient = dbClient;
  60. this.treeRootHolder = treeRootHolder;
  61. this.duplicationRepository = duplicationRepository;
  62. this.measureToMeasureDto = measureToMeasureDto;
  63. this.duplicationDataMetric = metricRepository.getByKey(DUPLICATIONS_DATA_KEY);
  64. }
  65. @Override
  66. public void execute(ComputationStep.Context context) {
  67. boolean supportUpsert = dbClient.getDatabase().getDialect().supportsUpsert();
  68. // batch mode of DB session does not have benefits:
  69. // - on postgres the multi-row upserts are the major optimization and have exactly the same
  70. // performance between batch and non-batch sessions
  71. // - on other dbs the sequence of inserts and updates, in order to emulate upserts,
  72. // breaks the constraint of batch sessions (consecutive requests should have the same
  73. // structure (same PreparedStatement))
  74. try (DbSession dbSession = dbClient.openSession(false);
  75. DuplicationVisitor visitor = new DuplicationVisitor(dbSession, supportUpsert)) {
  76. new DepthTraversalTypeAwareCrawler(visitor).visit(treeRootHolder.getRoot());
  77. context.getStatistics().add("insertsOrUpdates", visitor.insertsOrUpdates);
  78. }
  79. }
  80. private class DuplicationVisitor extends TypeAwareVisitorAdapter implements AutoCloseable {
  81. private final DbSession dbSession;
  82. private final boolean supportUpsert;
  83. private final List<LiveMeasureDto> nonPersistedBuffer = new ArrayList<>();
  84. private int insertsOrUpdates = 0;
  85. private DuplicationVisitor(DbSession dbSession, boolean supportUpsert) {
  86. super(CrawlerDepthLimit.FILE, PRE_ORDER);
  87. this.dbSession = dbSession;
  88. this.supportUpsert = supportUpsert;
  89. }
  90. @Override
  91. public void visitFile(Component file) {
  92. Iterable<Duplication> duplications = duplicationRepository.getDuplications(file);
  93. if (!isEmpty(duplications)) {
  94. computeDuplications(file, duplications);
  95. }
  96. }
  97. private void computeDuplications(Component component, Iterable<Duplication> duplications) {
  98. Measure measure = generateMeasure(component.getKey(), duplications);
  99. LiveMeasureDto dto = measureToMeasureDto.toLiveMeasureDto(measure, duplicationDataMetric, component);
  100. nonPersistedBuffer.add(dto);
  101. persist(false);
  102. }
  103. private void persist(boolean force) {
  104. // Persist a bunch of 100 or less measures. That prevents from having more than 100 XML documents
  105. // in memory. Consumption of memory does not explode with the number of duplications and is kept
  106. // under control.
  107. // Measures are upserted and transactions are committed every 100 rows (arbitrary number to
  108. // maximize the performance of a multi-rows request on PostgreSQL).
  109. // On PostgreSQL, a bunch of 100 measures is persisted into a single request (multi-rows upsert).
  110. // On other DBs, measures are persisted one by one, with update-or-insert requests.
  111. boolean shouldPersist = !nonPersistedBuffer.isEmpty() && (force || nonPersistedBuffer.size() > 100);
  112. if (!shouldPersist) {
  113. return;
  114. }
  115. if (supportUpsert) {
  116. nonPersistedBuffer.forEach(d -> dbClient.liveMeasureDao().upsert(dbSession, d));
  117. } else {
  118. nonPersistedBuffer.forEach(d -> dbClient.liveMeasureDao().insertOrUpdate(dbSession, d));
  119. }
  120. insertsOrUpdates += nonPersistedBuffer.size();
  121. nonPersistedBuffer.clear();
  122. dbSession.commit();
  123. }
  124. @Override
  125. public void close() {
  126. // persist the measures remaining in the buffer
  127. persist(true);
  128. }
  129. private Measure generateMeasure(String componentDbKey, Iterable<Duplication> duplications) {
  130. StringBuilder xml = new StringBuilder();
  131. xml.append("<duplications>");
  132. for (Duplication duplication : duplications) {
  133. xml.append("<g>");
  134. appendDuplication(xml, componentDbKey, duplication.getOriginal(), false);
  135. for (Duplicate duplicate : duplication.getDuplicates()) {
  136. processDuplicationBlock(xml, duplicate, componentDbKey);
  137. }
  138. xml.append("</g>");
  139. }
  140. xml.append("</duplications>");
  141. return Measure.newMeasureBuilder().create(xml.toString());
  142. }
  143. private void processDuplicationBlock(StringBuilder xml, Duplicate duplicate, String componentDbKey) {
  144. if (duplicate instanceof InnerDuplicate) {
  145. // Duplication is on the same file
  146. appendDuplication(xml, componentDbKey, duplicate);
  147. } else if (duplicate instanceof InExtendedProjectDuplicate inExtendedProjectDuplicate) {
  148. // Duplication is on a different file that is not saved in the DB
  149. appendDuplication(xml, inExtendedProjectDuplicate.getFile().getKey(), duplicate.getTextBlock(), true);
  150. } else if (duplicate instanceof InProjectDuplicate inProjectDuplicate) {
  151. // Duplication is on a different file
  152. appendDuplication(xml, inProjectDuplicate.getFile().getKey(), duplicate);
  153. } else if (duplicate instanceof CrossProjectDuplicate crossProjectDuplicate) {
  154. // Only componentKey is set for cross project duplications
  155. String crossProjectComponentKey = crossProjectDuplicate.getFileKey();
  156. appendDuplication(xml, crossProjectComponentKey, duplicate);
  157. } else {
  158. throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
  159. }
  160. }
  161. private void appendDuplication(StringBuilder xml, String componentDbKey, Duplicate duplicate) {
  162. appendDuplication(xml, componentDbKey, duplicate.getTextBlock(), false);
  163. }
  164. private void appendDuplication(StringBuilder xml, String componentDbKey, TextBlock textBlock, boolean disableLink) {
  165. int length = textBlock.getEnd() - textBlock.getStart() + 1;
  166. xml.append("<b s=\"").append(textBlock.getStart())
  167. .append("\" l=\"").append(length)
  168. .append("\" t=\"").append(disableLink)
  169. .append("\" r=\"").append(StringEscapeUtils.escapeXml(componentDbKey))
  170. .append("\"/>");
  171. }
  172. }
  173. @Override
  174. public String getDescription() {
  175. return "Persist duplication data";
  176. }
  177. }