]> source.dussan.org Git - sonarqube.git/blob
5b2760f17507798c6846ec2e20cb43cd30fe061b
[sonarqube.git] /
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.util.cache;
21
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.base.Splitter;
24 import com.google.common.collect.ImmutableSet;
25 import java.io.BufferedOutputStream;
26 import java.io.File;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.OutputStream;
30 import java.io.Serializable;
31 import java.util.Collections;
32 import java.util.Date;
33 import java.util.Map;
34 import javax.annotation.CheckForNull;
35 import org.sonar.api.issue.impact.Severity;
36 import org.sonar.api.issue.impact.SoftwareQuality;
37 import org.sonar.api.rule.RuleKey;
38 import org.sonar.api.rules.CleanCodeAttribute;
39 import org.sonar.api.rules.RuleType;
40 import org.sonar.api.utils.Duration;
41 import org.sonar.api.utils.System2;
42 import org.sonar.core.issue.DefaultIssue;
43 import org.sonar.core.issue.DefaultIssueComment;
44 import org.sonar.core.issue.FieldDiffs;
45 import org.sonar.core.util.CloseableIterator;
46 import org.sonar.core.util.Protobuf;
47 import org.sonar.db.protobuf.DbIssues;
48
49 import static java.util.Optional.ofNullable;
50
51 public class ProtobufIssueDiskCache implements DiskCache<DefaultIssue> {
52   private static final String TAGS_SEPARATOR = ",";
53   private static final Splitter STRING_LIST_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
54
55   private final File file;
56   private final System2 system2;
57
58   public ProtobufIssueDiskCache(File file, System2 system2) {
59     this.file = file;
60     this.system2 = system2;
61   }
62
63   @Override
64   public long fileSize() {
65     return file.length();
66   }
67
68   @Override
69   public CacheAppender<DefaultIssue> newAppender() {
70     try {
71       return new ProtoCacheAppender();
72     } catch (FileNotFoundException e) {
73       throw new IllegalStateException(e);
74     }
75   }
76
77   @Override
78   public CloseableIterator<DefaultIssue> traverse() {
79     CloseableIterator<IssueCache.Issue> protoIterator = Protobuf.readStream(file, IssueCache.Issue.parser());
80     return new CloseableIterator<>() {
81       @CheckForNull
82       @Override
83       protected DefaultIssue doNext() {
84         if (protoIterator.hasNext()) {
85           return toDefaultIssue(protoIterator.next());
86         }
87         return null;
88       }
89
90       @Override
91       protected void doClose() {
92         protoIterator.close();
93       }
94     };
95   }
96
97   @VisibleForTesting
98   static DefaultIssue toDefaultIssue(IssueCache.Issue next) {
99     DefaultIssue defaultIssue = new DefaultIssue();
100     defaultIssue.setKey(next.getKey());
101     defaultIssue.setType(RuleType.valueOf(next.getRuleType()));
102     defaultIssue.setComponentUuid(next.hasComponentUuid() ? next.getComponentUuid() : null);
103     defaultIssue.setComponentKey(next.getComponentKey());
104     defaultIssue.setProjectUuid(next.getProjectUuid());
105     defaultIssue.setProjectKey(next.getProjectKey());
106     defaultIssue.setRuleKey(RuleKey.parse(next.getRuleKey()));
107     defaultIssue.setLanguage(next.hasLanguage() ? next.getLanguage() : null);
108     defaultIssue.setSeverity(next.hasSeverity() ? next.getSeverity() : null);
109     defaultIssue.setManualSeverity(next.getManualSeverity());
110     defaultIssue.setMessage(next.hasMessage() ? next.getMessage() : null);
111     defaultIssue.setMessageFormattings(next.hasMessageFormattings() ? next.getMessageFormattings() : null);
112     defaultIssue.setLine(next.hasLine() ? next.getLine() : null);
113     defaultIssue.setGap(next.hasGap() ? next.getGap() : null);
114     defaultIssue.setEffort(next.hasEffort() ? Duration.create(next.getEffort()) : null);
115     defaultIssue.setStatus(next.getStatus());
116     defaultIssue.setResolution(next.hasResolution() ? next.getResolution() : null);
117     defaultIssue.setAssigneeUuid(next.hasAssigneeUuid() ? next.getAssigneeUuid() : null);
118     defaultIssue.setAssigneeLogin(next.hasAssigneeLogin() ? next.getAssigneeLogin() : null);
119     defaultIssue.setChecksum(next.hasChecksum() ? next.getChecksum() : null);
120     defaultIssue.setAuthorLogin(next.hasAuthorLogin() ? next.getAuthorLogin() : null);
121     next.getCommentsList().forEach(c -> defaultIssue.addComment(toDefaultIssueComment(c)));
122     defaultIssue.setTags(ImmutableSet.copyOf(STRING_LIST_SPLITTER.split(next.getTags())));
123     defaultIssue.setCodeVariants(ImmutableSet.copyOf(STRING_LIST_SPLITTER.split(next.getCodeVariants())));
124     defaultIssue.setRuleDescriptionContextKey(next.hasRuleDescriptionContextKey() ? next.getRuleDescriptionContextKey() : null);
125     defaultIssue.setLocations(next.hasLocations() ? next.getLocations() : null);
126     defaultIssue.setIsFromExternalRuleEngine(next.getIsFromExternalRuleEngine());
127     defaultIssue.setCreationDate(new Date(next.getCreationDate()));
128     defaultIssue.setUpdateDate(next.hasUpdateDate() ? new Date(next.getUpdateDate()) : null);
129     defaultIssue.setCloseDate(next.hasCloseDate() ? new Date(next.getCloseDate()) : null);
130     defaultIssue.setCurrentChangeWithoutAddChange(next.hasCurrentChanges() ? toDefaultIssueChanges(next.getCurrentChanges()) : null);
131     defaultIssue.setNew(next.getIsNew());
132     defaultIssue.setIsOnChangedLine(next.getIsOnChangedLine());
133     defaultIssue.setIsNewCodeReferenceIssue(next.getIsNewCodeReferenceIssue());
134     defaultIssue.setCopied(next.getIsCopied());
135     defaultIssue.setBeingClosed(next.getBeingClosed());
136     defaultIssue.setOnDisabledRule(next.getOnDisabledRule());
137     defaultIssue.setChanged(next.getIsChanged());
138     defaultIssue.setSendNotifications(next.getSendNotifications());
139     defaultIssue.setSelectedAt(next.hasSelectedAt() ? next.getSelectedAt() : null);
140     defaultIssue.setQuickFixAvailable(next.getQuickFixAvailable());
141     defaultIssue.setPrioritizedRule(next.getIsPrioritizedRule());
142     defaultIssue.setIsNoLongerNewCodeReferenceIssue(next.getIsNoLongerNewCodeReferenceIssue());
143     defaultIssue.setCleanCodeAttribute(next.hasCleanCodeAttribute() ? CleanCodeAttribute.valueOf(next.getCleanCodeAttribute()) : null);
144     if (next.hasAnticipatedTransitionUuid()) {
145       defaultIssue.setAnticipatedTransitionUuid(next.getAnticipatedTransitionUuid());
146     }
147
148     for (IssueCache.Impact impact : next.getImpactsList()) {
149       defaultIssue.addImpact(SoftwareQuality.valueOf(impact.getSoftwareQuality()), Severity.valueOf(impact.getSeverity()));
150     }
151     for (IssueCache.FieldDiffs protoFieldDiffs : next.getChangesList()) {
152       defaultIssue.addChange(toDefaultIssueChanges(protoFieldDiffs));
153     }
154     defaultIssue.setCveId(next.hasCveId() ? next.getCveId() : null);
155     return defaultIssue;
156   }
157
158   @VisibleForTesting
159   static IssueCache.Issue toProto(IssueCache.Issue.Builder builder, DefaultIssue defaultIssue) {
160     builder.clear();
161     builder.setKey(defaultIssue.key());
162     builder.setRuleType(defaultIssue.type().getDbConstant());
163     ofNullable(defaultIssue.getCleanCodeAttribute()).ifPresent(value -> builder.setCleanCodeAttribute(value.name()));
164     ofNullable(defaultIssue.componentUuid()).ifPresent(builder::setComponentUuid);
165     builder.setComponentKey(defaultIssue.componentKey());
166     builder.setProjectUuid(defaultIssue.projectUuid());
167     builder.setProjectKey(defaultIssue.projectKey());
168     builder.setRuleKey(defaultIssue.ruleKey().toString());
169     ofNullable(defaultIssue.language()).ifPresent(builder::setLanguage);
170     ofNullable(defaultIssue.severity()).ifPresent(builder::setSeverity);
171     builder.setManualSeverity(defaultIssue.manualSeverity());
172     ofNullable(defaultIssue.message()).ifPresent(builder::setMessage);
173     ofNullable(defaultIssue.getMessageFormattings()).ifPresent(m -> builder.setMessageFormattings((DbIssues.MessageFormattings) m));
174     ofNullable(defaultIssue.line()).ifPresent(builder::setLine);
175     ofNullable(defaultIssue.gap()).ifPresent(builder::setGap);
176     ofNullable(defaultIssue.effort()).map(Duration::toMinutes).ifPresent(builder::setEffort);
177     builder.setStatus(defaultIssue.status());
178     ofNullable(defaultIssue.resolution()).ifPresent(builder::setResolution);
179     ofNullable(defaultIssue.assignee()).ifPresent(builder::setAssigneeUuid);
180     ofNullable(defaultIssue.assigneeLogin()).ifPresent(builder::setAssigneeLogin);
181     ofNullable(defaultIssue.checksum()).ifPresent(builder::setChecksum);
182     ofNullable(defaultIssue.authorLogin()).ifPresent(builder::setAuthorLogin);
183     defaultIssue.defaultIssueComments().forEach(c -> builder.addComments(toProtoComment(c)));
184     ofNullable(defaultIssue.tags()).ifPresent(t -> builder.setTags(String.join(TAGS_SEPARATOR, t)));
185     ofNullable(defaultIssue.codeVariants()).ifPresent(codeVariant -> builder.setCodeVariants(String.join(TAGS_SEPARATOR, codeVariant)));
186     ofNullable(defaultIssue.getLocations()).ifPresent(l -> builder.setLocations((DbIssues.Locations) l));
187     defaultIssue.getRuleDescriptionContextKey().ifPresent(builder::setRuleDescriptionContextKey);
188     builder.setIsFromExternalRuleEngine(defaultIssue.isFromExternalRuleEngine());
189     builder.setCreationDate(defaultIssue.creationDate().getTime());
190     ofNullable(defaultIssue.updateDate()).map(Date::getTime).ifPresent(builder::setUpdateDate);
191     ofNullable(defaultIssue.closeDate()).map(Date::getTime).ifPresent(builder::setCloseDate);
192     ofNullable(defaultIssue.currentChange()).ifPresent(c -> builder.setCurrentChanges(toProtoIssueChanges(c)));
193     builder.setIsNew(defaultIssue.isNew());
194     builder.setIsOnChangedLine(defaultIssue.isOnChangedLine());
195     builder.setIsPrioritizedRule(defaultIssue.isPrioritizedRule());
196     builder.setIsNewCodeReferenceIssue(defaultIssue.isNewCodeReferenceIssue());
197     builder.setIsCopied(defaultIssue.isCopied());
198     builder.setBeingClosed(defaultIssue.isBeingClosed());
199     builder.setOnDisabledRule(defaultIssue.isOnDisabledRule());
200     builder.setIsChanged(defaultIssue.isChanged());
201     builder.setSendNotifications(defaultIssue.mustSendNotifications());
202     ofNullable(defaultIssue.selectedAt()).ifPresent(builder::setSelectedAt);
203     builder.setQuickFixAvailable(defaultIssue.isQuickFixAvailable());
204     builder.setIsNoLongerNewCodeReferenceIssue(defaultIssue.isNoLongerNewCodeReferenceIssue());
205     defaultIssue.getAnticipatedTransitionUuid().ifPresent(builder::setAnticipatedTransitionUuid);
206
207
208     for (Map.Entry<SoftwareQuality, Severity> impact : defaultIssue.impacts().entrySet()) {
209       builder.addImpacts(IssueCache.Impact.newBuilder()
210         .setSoftwareQuality(impact.getKey().name())
211         .setSeverity(impact.getValue().name())
212         .build());
213     }
214     for (FieldDiffs fieldDiffs : defaultIssue.changes()) {
215       builder.addChanges(toProtoIssueChanges(fieldDiffs));
216     }
217     ofNullable(defaultIssue.getCveId()).ifPresent(builder::setCveId);
218     return builder.build();
219   }
220
221   private static DefaultIssueComment toDefaultIssueComment(IssueCache.Comment comment) {
222     DefaultIssueComment issueComment = new DefaultIssueComment()
223       .setCreatedAt(new Date(comment.getCreatedAt()))
224       .setUpdatedAt(new Date(comment.getUpdatedAt()))
225       .setNew(comment.getIsNew())
226       .setKey(comment.getKey())
227       .setIssueKey(comment.getIssueKey())
228       .setMarkdownText(comment.getMarkdownText());
229
230     if (comment.hasUserUuid()) {
231       issueComment.setUserUuid(comment.getUserUuid());
232     }
233     return issueComment;
234   }
235
236   private static IssueCache.Comment toProtoComment(DefaultIssueComment comment) {
237     IssueCache.Comment.Builder builder = IssueCache.Comment.newBuilder()
238       .setCreatedAt(comment.createdAt().getTime())
239       .setUpdatedAt(comment.updatedAt().getTime())
240       .setIsNew(comment.isNew())
241       .setKey(comment.key())
242       .setIssueKey(comment.issueKey())
243       .setMarkdownText(comment.markdownText());
244
245     if (comment.userUuid() != null) {
246       builder.setUserUuid(comment.userUuid());
247     }
248     return builder.build();
249   }
250
251   private static FieldDiffs toDefaultIssueChanges(IssueCache.FieldDiffs fieldDiffs) {
252     FieldDiffs defaultIssueFieldDiffs = new FieldDiffs()
253       .setUserUuid(fieldDiffs.getUserUuid())
254       .setCreationDate(new Date(fieldDiffs.getCreationDate()));
255
256     if (fieldDiffs.hasIssueKey()) {
257       defaultIssueFieldDiffs.setIssueKey(fieldDiffs.getIssueKey());
258     }
259
260     for (Map.Entry<String, IssueCache.Diff> e : fieldDiffs.getDiffsMap().entrySet()) {
261       defaultIssueFieldDiffs.setDiff(e.getKey(),
262         e.getValue().hasOldValue() ? e.getValue().getOldValue() : null,
263         e.getValue().hasNewValue() ? e.getValue().getNewValue() : null);
264     }
265
266     return defaultIssueFieldDiffs;
267   }
268
269   private static IssueCache.FieldDiffs toProtoIssueChanges(FieldDiffs fieldDiffs) {
270     IssueCache.FieldDiffs.Builder builder = IssueCache.FieldDiffs.newBuilder()
271       .setCreationDate(fieldDiffs.creationDate().getTime());
272
273     fieldDiffs.issueKey().ifPresent(builder::setIssueKey);
274     fieldDiffs.userUuid().ifPresent(builder::setUserUuid);
275
276     for (Map.Entry<String, FieldDiffs.Diff> e : fieldDiffs.diffs().entrySet()) {
277       IssueCache.Diff.Builder diffBuilder = IssueCache.Diff.newBuilder();
278       Serializable oldValue = e.getValue().oldValue();
279       if (oldValue != null) {
280         diffBuilder.setOldValue(oldValue.toString());
281       }
282       Serializable newValue = e.getValue().newValue();
283       if (newValue != null) {
284         diffBuilder.setNewValue(newValue.toString());
285
286       }
287       builder.putDiffs(e.getKey(), diffBuilder.build());
288     }
289
290     return builder.build();
291   }
292
293   private class ProtoCacheAppender implements CacheAppender<DefaultIssue> {
294     private final OutputStream out;
295     private final IssueCache.Issue.Builder builder;
296
297     private ProtoCacheAppender() throws FileNotFoundException {
298       this.out = new BufferedOutputStream(new FileOutputStream(file, true));
299       this.builder = IssueCache.Issue.newBuilder();
300     }
301
302     @Override
303     public CacheAppender append(DefaultIssue object) {
304       Protobuf.writeStream(Collections.singleton(toProto(builder, object)), out);
305       return this;
306     }
307
308     @Override
309     public void close() {
310       system2.close(out);
311     }
312   }
313 }