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