3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.util.cache;
22 import com.google.common.base.Splitter;
23 import com.google.common.collect.ImmutableSet;
24 import java.io.BufferedOutputStream;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.OutputStream;
29 import java.io.Serializable;
30 import java.util.Collections;
31 import java.util.Date;
33 import javax.annotation.CheckForNull;
34 import org.sonar.api.rule.RuleKey;
35 import org.sonar.api.rules.RuleType;
36 import org.sonar.api.utils.Duration;
37 import org.sonar.api.utils.System2;
38 import org.sonar.core.issue.DefaultIssue;
39 import org.sonar.core.issue.DefaultIssueComment;
40 import org.sonar.core.issue.FieldDiffs;
41 import org.sonar.core.util.CloseableIterator;
42 import org.sonar.core.util.Protobuf;
43 import org.sonar.db.protobuf.DbIssues;
45 import static java.util.Optional.ofNullable;
47 public class ProtobufIssueDiskCache implements DiskCache<DefaultIssue> {
48 private static final String TAGS_SEPARATOR = ",";
49 private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
51 private final File file;
52 private final System2 system2;
54 public ProtobufIssueDiskCache(File file, System2 system2) {
56 this.system2 = system2;
60 public long fileSize() {
65 public CacheAppender<DefaultIssue> newAppender() {
67 return new ProtoCacheAppender();
68 } catch (FileNotFoundException e) {
69 throw new IllegalStateException(e);
74 public CloseableIterator<DefaultIssue> traverse() {
75 CloseableIterator<IssueCache.Issue> protoIterator = Protobuf.readStream(file, IssueCache.Issue.parser());
76 return new CloseableIterator<DefaultIssue>() {
79 protected DefaultIssue doNext() {
80 if (protoIterator.hasNext()) {
81 return toDefaultIssue(protoIterator.next());
87 protected void doClose() {
88 protoIterator.close();
93 private static DefaultIssue toDefaultIssue(IssueCache.Issue next) {
94 DefaultIssue defaultIssue = new DefaultIssue();
95 defaultIssue.setKey(next.getKey());
96 defaultIssue.setType(RuleType.valueOf(next.getRuleType()));
97 defaultIssue.setComponentUuid(next.hasComponentUuid() ? next.getComponentUuid() : null);
98 defaultIssue.setComponentKey(next.getComponentKey());
99 defaultIssue.setModuleUuid(next.hasModuleUuid() ? next.getModuleUuid() : null);
100 defaultIssue.setModuleUuidPath(next.hasModuleUuidPath() ? next.getModuleUuidPath() : null);
101 defaultIssue.setProjectUuid(next.getProjectUuid());
102 defaultIssue.setProjectKey(next.getProjectKey());
103 defaultIssue.setRuleKey(RuleKey.parse(next.getRuleKey()));
104 defaultIssue.setLanguage(next.hasLanguage() ? next.getLanguage() : null);
105 defaultIssue.setSeverity(next.hasSeverity() ? next.getSeverity() : null);
106 defaultIssue.setManualSeverity(next.getManualSeverity());
107 defaultIssue.setMessage(next.hasMessage() ? next.getMessage() : null);
108 defaultIssue.setLine(next.hasLine() ? next.getLine() : null);
109 defaultIssue.setGap(next.hasGap() ? next.getGap() : null);
110 defaultIssue.setEffort(next.hasEffort() ? Duration.create(next.getEffort()) : null);
111 defaultIssue.setStatus(next.getStatus());
112 defaultIssue.setResolution(next.hasResolution() ? next.getResolution() : null);
113 defaultIssue.setAssigneeUuid(next.hasAssigneeUuid() ? next.getAssigneeUuid() : null);
114 defaultIssue.setChecksum(next.hasChecksum() ? next.getChecksum() : null);
115 defaultIssue.setAuthorLogin(next.hasAuthorLogin() ? next.getAuthorLogin() : null);
116 next.getCommentsList().forEach(c -> defaultIssue.addComment(toDefaultIssueComment(c)));
117 defaultIssue.setTags(ImmutableSet.copyOf(TAGS_SPLITTER.split(next.getTags())));
118 defaultIssue.setLocations(next.hasLocations() ? next.getLocations() : null);
119 defaultIssue.setIsFromExternalRuleEngine(next.getIsFromExternalRuleEngine());
120 defaultIssue.setCreationDate(new Date(next.getCreationDate()));
121 defaultIssue.setUpdateDate(next.hasUpdateDate() ? new Date(next.getUpdateDate()) : null);
122 defaultIssue.setCloseDate(next.hasCloseDate() ? new Date(next.getCloseDate()) : null);
123 defaultIssue.setCurrentChangeWithoutAddChange(next.hasCurrentChanges() ? toDefaultIssueChanges(next.getCurrentChanges()) : null);
124 defaultIssue.setNew(next.getIsNew());
125 defaultIssue.setIsOnReferencedBranch(next.getIsOnReferencedBranch());
126 defaultIssue.setIsOnChangedLine(next.getIsOnChangedLine());
127 defaultIssue.setCopied(next.getIsCopied());
128 defaultIssue.setBeingClosed(next.getBeingClosed());
129 defaultIssue.setOnDisabledRule(next.getOnDisabledRule());
130 defaultIssue.setChanged(next.getIsChanged());
131 defaultIssue.setSendNotifications(next.getSendNotifications());
132 defaultIssue.setSelectedAt(next.hasSelectedAt() ? next.getSelectedAt() : null);
133 defaultIssue.setQuickFixAvailable(next.getQuickFixAvailable());
134 defaultIssue.setIsNoLongerNewCodeReferenceIssue(next.getIsNoLongerNewCodeReferenceIssue());
136 for (IssueCache.FieldDiffs protoFieldDiffs : next.getChangesList()) {
137 defaultIssue.addChange(toDefaultIssueChanges(protoFieldDiffs));
143 private static IssueCache.Issue toProto(IssueCache.Issue.Builder builder, DefaultIssue defaultIssue) {
145 builder.setKey(defaultIssue.key());
146 builder.setRuleType(defaultIssue.type().getDbConstant());
147 ofNullable(defaultIssue.componentUuid()).ifPresent(builder::setComponentUuid);
148 builder.setComponentKey(defaultIssue.componentKey());
149 ofNullable(defaultIssue.moduleUuid()).ifPresent(builder::setModuleUuid);
150 ofNullable(defaultIssue.moduleUuidPath()).ifPresent(builder::setModuleUuidPath);
151 builder.setProjectUuid(defaultIssue.projectUuid());
152 builder.setProjectKey(defaultIssue.projectKey());
153 builder.setRuleKey(defaultIssue.ruleKey().toString());
154 ofNullable(defaultIssue.language()).ifPresent(builder::setLanguage);
155 ofNullable(defaultIssue.severity()).ifPresent(builder::setSeverity);
156 builder.setManualSeverity(defaultIssue.manualSeverity());
157 ofNullable(defaultIssue.message()).ifPresent(builder::setMessage);
158 ofNullable(defaultIssue.line()).ifPresent(builder::setLine);
159 ofNullable(defaultIssue.gap()).ifPresent(builder::setGap);
160 ofNullable(defaultIssue.effort()).map(Duration::toMinutes).ifPresent(builder::setEffort);
161 builder.setStatus(defaultIssue.status());
162 ofNullable(defaultIssue.resolution()).ifPresent(builder::setResolution);
163 ofNullable(defaultIssue.assignee()).ifPresent(builder::setAssigneeUuid);
164 ofNullable(defaultIssue.checksum()).ifPresent(builder::setChecksum);
165 ofNullable(defaultIssue.authorLogin()).ifPresent(builder::setAuthorLogin);
166 defaultIssue.defaultIssueComments().forEach(c -> builder.addComments(toProtoComment(c)));
167 ofNullable(defaultIssue.tags()).ifPresent(t -> builder.setTags(String.join(TAGS_SEPARATOR, t)));
168 ofNullable(defaultIssue.getLocations()).ifPresent(l -> builder.setLocations((DbIssues.Locations) l));
169 builder.setIsFromExternalRuleEngine(defaultIssue.isFromExternalRuleEngine());
170 builder.setCreationDate(defaultIssue.creationDate().getTime());
171 ofNullable(defaultIssue.updateDate()).map(Date::getTime).ifPresent(builder::setUpdateDate);
172 ofNullable(defaultIssue.closeDate()).map(Date::getTime).ifPresent(builder::setCloseDate);
173 ofNullable(defaultIssue.currentChange()).ifPresent(c -> builder.setCurrentChanges(toProtoIssueChanges(c)));
174 builder.setIsNew(defaultIssue.isNew());
175 builder.setIsOnReferencedBranch(defaultIssue.isOnReferencedBranch());
176 builder.setIsOnChangedLine(defaultIssue.isOnChangedLine());
177 builder.setIsCopied(defaultIssue.isCopied());
178 builder.setBeingClosed(defaultIssue.isBeingClosed());
179 builder.setOnDisabledRule(defaultIssue.isOnDisabledRule());
180 builder.setIsChanged(defaultIssue.isChanged());
181 builder.setSendNotifications(defaultIssue.mustSendNotifications());
182 ofNullable(defaultIssue.selectedAt()).ifPresent(builder::setSelectedAt);
183 builder.setQuickFixAvailable(defaultIssue.isQuickFixAvailable());
184 builder.setIsNoLongerNewCodeReferenceIssue(defaultIssue.isNoLongerNewCodeReferenceIssue());
186 for (FieldDiffs fieldDiffs : defaultIssue.changes()) {
187 builder.addChanges(toProtoIssueChanges(fieldDiffs));
190 return builder.build();
193 private static DefaultIssueComment toDefaultIssueComment(IssueCache.Comment comment) {
194 DefaultIssueComment issueComment = new DefaultIssueComment()
195 .setCreatedAt(new Date(comment.getCreatedAt()))
196 .setUpdatedAt(new Date(comment.getUpdatedAt()))
197 .setNew(comment.getIsNew())
198 .setKey(comment.getKey())
199 .setIssueKey(comment.getIssueKey())
200 .setMarkdownText(comment.getMarkdownText());
202 if (comment.hasUserUuid()) {
203 issueComment.setUserUuid(comment.getUserUuid());
208 private static IssueCache.Comment toProtoComment(DefaultIssueComment comment) {
209 IssueCache.Comment.Builder builder = IssueCache.Comment.newBuilder()
210 .setCreatedAt(comment.createdAt().getTime())
211 .setUpdatedAt(comment.updatedAt().getTime())
212 .setIsNew(comment.isNew())
213 .setKey(comment.key())
214 .setIssueKey(comment.issueKey())
215 .setMarkdownText(comment.markdownText());
217 if (comment.userUuid() != null) {
218 builder.setUserUuid(comment.userUuid());
220 return builder.build();
223 private static FieldDiffs toDefaultIssueChanges(IssueCache.FieldDiffs fieldDiffs) {
224 FieldDiffs defaultIssueFieldDiffs = new FieldDiffs()
225 .setUserUuid(fieldDiffs.getUserUuid())
226 .setCreationDate(new Date(fieldDiffs.getCreationDate()));
228 if (fieldDiffs.hasIssueKey()) {
229 defaultIssueFieldDiffs.setIssueKey(fieldDiffs.getIssueKey());
232 for (Map.Entry<String, IssueCache.Diff> e : fieldDiffs.getDiffsMap().entrySet()) {
233 defaultIssueFieldDiffs.setDiff(e.getKey(),
234 e.getValue().hasOldValue() ? e.getValue().getOldValue() : null,
235 e.getValue().hasNewValue() ? e.getValue().getNewValue() : null);
238 return defaultIssueFieldDiffs;
241 private static IssueCache.FieldDiffs toProtoIssueChanges(FieldDiffs fieldDiffs) {
242 IssueCache.FieldDiffs.Builder builder = IssueCache.FieldDiffs.newBuilder()
243 .setCreationDate(fieldDiffs.creationDate().getTime());
245 if (fieldDiffs.issueKey() != null) {
246 builder.setIssueKey(fieldDiffs.issueKey());
249 String userUuid = fieldDiffs.userUuid();
250 if (userUuid != null) {
251 builder.setUserUuid(userUuid);
254 for (Map.Entry<String, FieldDiffs.Diff> e : fieldDiffs.diffs().entrySet()) {
255 IssueCache.Diff.Builder diffBuilder = IssueCache.Diff.newBuilder();
256 Serializable oldValue = e.getValue().oldValue();
257 if (oldValue != null) {
258 diffBuilder.setOldValue(oldValue.toString());
260 Serializable newValue = e.getValue().newValue();
261 if (newValue != null) {
262 diffBuilder.setNewValue(newValue.toString());
265 builder.putDiffs(e.getKey(), diffBuilder.build());
268 return builder.build();
271 private class ProtoCacheAppender implements CacheAppender<DefaultIssue> {
272 private final OutputStream out;
273 private final IssueCache.Issue.Builder builder;
275 private ProtoCacheAppender() throws FileNotFoundException {
276 this.out = new BufferedOutputStream(new FileOutputStream(file, true));
277 this.builder = IssueCache.Issue.newBuilder();
281 public CacheAppender append(DefaultIssue object) {
282 Protobuf.writeStream(Collections.singleton(toProto(builder, object)), out);
287 public void close() {