]> source.dussan.org Git - sonarqube.git/blob
fad0abbf8fba5f5bd7eae437ace6e46f9786d45d
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.server.issue.notification;
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.function.Function;
27 import java.util.stream.Collectors;
28 import javax.annotation.CheckForNull;
29 import javax.annotation.Nullable;
30 import javax.annotation.concurrent.Immutable;
31 import org.sonar.api.rule.RuleKey;
32 import org.sonar.core.issue.status.IssueStatus;
33 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
34 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
35 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Rule;
36 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User;
37
38 import static com.google.common.base.Preconditions.checkArgument;
39 import static com.google.common.base.Preconditions.checkState;
40 import static java.util.Optional.ofNullable;
41
42 public class IssuesChangesNotificationSerializer {
43   private static final String FIELD_ISSUES_COUNT = "issues.count";
44   private static final String FIELD_CHANGE_DATE = "change.date";
45   private static final String FIELD_CHANGE_AUTHOR_UUID = "change.author.uuid";
46   private static final String FIELD_CHANGE_AUTHOR_LOGIN = "change.author.login";
47   private static final String FIELD_CHANGE_AUTHOR_NAME = "change.author.name";
48   private static final String FIELD_PREFIX_RULES = "rules.";
49
50   public IssuesChangesNotification serialize(IssuesChangesNotificationBuilder builder) {
51     IssuesChangesNotification res = new IssuesChangesNotification();
52     serializeIssueSize(res, builder.getIssues());
53     serializeChange(res, builder.getChange());
54     serializeIssues(res, builder.getIssues());
55     serializeRules(res, builder.getIssues());
56     serializeProjects(res, builder.getIssues());
57
58     return res;
59   }
60
61   /**
62    * @throws IllegalArgumentException if {@code notification} misses any field or of any has unsupported value
63    */
64   public IssuesChangesNotificationBuilder from(IssuesChangesNotification notification) {
65     int issueCount = readIssueCount(notification);
66     IssuesChangesNotificationBuilder.Change change = readChange(notification);
67     List<Issue> issues = readIssues(notification, issueCount);
68     Map<String, Project> projects = readProjects(notification, issues);
69     Map<RuleKey, Rule> rules = readRules(notification, issues);
70
71     return new IssuesChangesNotificationBuilder(buildChangedIssues(issues, projects, rules), change);
72   }
73
74   private static void serializeIssueSize(IssuesChangesNotification res, Set<ChangedIssue> issues) {
75     res.setFieldValue(FIELD_ISSUES_COUNT, String.valueOf(issues.size()));
76   }
77
78   private static int readIssueCount(IssuesChangesNotification notification) {
79     String fieldValue = notification.getFieldValue(FIELD_ISSUES_COUNT);
80     checkArgument(fieldValue != null, "missing field %s", FIELD_ISSUES_COUNT);
81     int issueCount = Integer.parseInt(fieldValue);
82     checkArgument(issueCount > 0, "issue count must be >= 1");
83     return issueCount;
84   }
85
86   private static Set<ChangedIssue> buildChangedIssues(List<Issue> issues, Map<String, Project> projects,
87     Map<RuleKey, Rule> rules) {
88     return issues.stream()
89       .map(issue -> new ChangedIssue.Builder(issue.key)
90         .setNewStatus(issue.newStatus)
91         .setNewIssueStatus(issue.newIssueStatus == null ? null : IssueStatus.valueOf(issue.newIssueStatus))
92         .setAssignee(issue.assignee)
93         .setRule(rules.get(issue.ruleKey))
94         .setProject(projects.get(issue.projectUuid))
95         .build())
96       .collect(Collectors.toSet());
97   }
98
99   private static void serializeIssues(IssuesChangesNotification res, Set<ChangedIssue> issues) {
100     int index = 0;
101     for (ChangedIssue issue : issues) {
102       serializeIssue(res, index, issue);
103       index++;
104     }
105   }
106
107   private static List<Issue> readIssues(IssuesChangesNotification notification, int issueCount) {
108     List<Issue> res = new ArrayList<>(issueCount);
109     for (int i = 0; i < issueCount; i++) {
110       res.add(readIssue(notification, i));
111     }
112     return res;
113   }
114
115   private static void serializeIssue(IssuesChangesNotification notification, int index, ChangedIssue issue) {
116     String issuePropertyPrefix = "issues." + index;
117     notification.setFieldValue(issuePropertyPrefix + ".key", issue.getKey());
118     issue.getAssignee()
119       .ifPresent(assignee -> {
120         notification.setFieldValue(issuePropertyPrefix + ".assignee.uuid", assignee.getUuid());
121         notification.setFieldValue(issuePropertyPrefix + ".assignee.login", assignee.getLogin());
122         assignee.getName()
123           .ifPresent(name -> notification.setFieldValue(issuePropertyPrefix + ".assignee.name", name));
124       });
125     notification.setFieldValue(issuePropertyPrefix + ".newStatus", issue.getNewStatus());
126     issue.getNewIssueStatus()
127       .ifPresent(newIssueStatus -> notification.setFieldValue(issuePropertyPrefix + ".newIssueStatus", newIssueStatus.name()));
128     notification.setFieldValue(issuePropertyPrefix + ".ruleKey", issue.getRule().getKey().toString());
129     notification.setFieldValue(issuePropertyPrefix + ".projectUuid", issue.getProject().getUuid());
130   }
131
132   private static Issue readIssue(IssuesChangesNotification notification, int index) {
133     String issuePropertyPrefix = "issues." + index;
134     User assignee = readAssignee(notification, issuePropertyPrefix, index);
135     return new Issue.Builder()
136       .setKey(getIssueFieldValue(notification, issuePropertyPrefix + ".key", index))
137       .setNewStatus(getIssueFieldValue(notification, issuePropertyPrefix + ".newStatus", index))
138       .setNewResolution(notification.getFieldValue(issuePropertyPrefix + ".newResolution"))
139       .setNewIssueStatus(notification.getFieldValue(issuePropertyPrefix + ".newIssueStatus"))
140       .setAssignee(assignee)
141       .setRuleKey(getIssueFieldValue(notification, issuePropertyPrefix + ".ruleKey", index))
142       .setProjectUuid(getIssueFieldValue(notification, issuePropertyPrefix + ".projectUuid", index))
143       .build();
144   }
145
146   @CheckForNull
147   private static User readAssignee(IssuesChangesNotification notification, String issuePropertyPrefix, int index) {
148     String uuid = notification.getFieldValue(issuePropertyPrefix + ".assignee.uuid");
149     if (uuid == null) {
150       return null;
151     }
152     String login = getIssueFieldValue(notification, issuePropertyPrefix + ".assignee.login", index);
153     return new User(uuid, login, notification.getFieldValue(issuePropertyPrefix + ".assignee.name"));
154   }
155
156   private static String getIssueFieldValue(IssuesChangesNotification notification, String fieldName, int index) {
157     String fieldValue = notification.getFieldValue(fieldName);
158     checkState(fieldValue != null, "Can not find field %s for issue with index %s", fieldName, index);
159     return fieldValue;
160   }
161
162   private static void serializeRules(IssuesChangesNotification res, Set<ChangedIssue> issues) {
163     issues.stream()
164       .map(ChangedIssue::getRule)
165       .collect(Collectors.toSet())
166       .forEach(rule -> {
167         res.setFieldValue(FIELD_PREFIX_RULES + rule.getKey(), rule.getName());
168         ofNullable(rule.getRuleType()).ifPresent(ruleType -> res.setFieldValue(FIELD_PREFIX_RULES + rule.getKey() + ".type", rule.getRuleType().name()));
169       });
170   }
171
172   private static Map<RuleKey, Rule> readRules(IssuesChangesNotification notification, List<Issue> issues) {
173     return issues.stream()
174       .map(issue -> issue.ruleKey)
175       .collect(Collectors.toSet())
176       .stream()
177       .map(ruleKey -> readRule(notification, ruleKey))
178       .collect(Collectors.toMap(Rule::getKey, Function.identity()));
179   }
180
181   private static Rule readRule(IssuesChangesNotification notification, RuleKey ruleKey) {
182     String fieldName = FIELD_PREFIX_RULES + ruleKey;
183     String ruleName = notification.getFieldValue(fieldName);
184     String ruleType = notification.getFieldValue(fieldName + ".type");
185     checkState(ruleName != null, "can not find field %s", ruleKey);
186     return new Rule(ruleKey, ruleType, ruleName);
187   }
188
189   private static void serializeProjects(IssuesChangesNotification res, Set<ChangedIssue> issues) {
190     issues.stream()
191       .map(ChangedIssue::getProject)
192       .collect(Collectors.toSet())
193       .forEach(project -> {
194         String projectPropertyPrefix = "projects." + project.getUuid();
195         res.setFieldValue(projectPropertyPrefix + ".key", project.getKey());
196         res.setFieldValue(projectPropertyPrefix + ".projectName", project.getProjectName());
197         project.getBranchName()
198           .ifPresent(branchName -> res.setFieldValue(projectPropertyPrefix + ".branchName", branchName));
199       });
200   }
201
202   private static Map<String, Project> readProjects(IssuesChangesNotification notification, List<Issue> issues) {
203     return issues.stream()
204       .map(issue -> issue.projectUuid)
205       .collect(Collectors.toSet())
206       .stream()
207       .map(projectUuid -> {
208         String projectPropertyPrefix = "projects." + projectUuid;
209         return new Project.Builder(projectUuid)
210           .setKey(getProjectFieldValue(notification, projectPropertyPrefix + ".key", projectUuid))
211           .setProjectName(getProjectFieldValue(notification, projectPropertyPrefix + ".projectName", projectUuid))
212           .setBranchName(notification.getFieldValue(projectPropertyPrefix + ".branchName"))
213           .build();
214       })
215       .collect(Collectors.toMap(Project::getUuid, Function.identity()));
216   }
217
218   private static String getProjectFieldValue(IssuesChangesNotification notification, String fieldName, String uuid) {
219     String fieldValue = notification.getFieldValue(fieldName);
220     checkState(fieldValue != null, "Can not find field %s for project with uuid %s", fieldName, uuid);
221     return fieldValue;
222   }
223
224   private static void serializeChange(IssuesChangesNotification notification, IssuesChangesNotificationBuilder.Change change) {
225     notification.setFieldValue(FIELD_CHANGE_DATE, String.valueOf(change.date));
226     if (change instanceof IssuesChangesNotificationBuilder.UserChange userChange) {
227       User user = userChange.getUser();
228       notification.setFieldValue(FIELD_CHANGE_AUTHOR_UUID, user.getUuid());
229       notification.setFieldValue(FIELD_CHANGE_AUTHOR_LOGIN, user.getLogin());
230       user.getName().ifPresent(name -> notification.setFieldValue(FIELD_CHANGE_AUTHOR_NAME, name));
231     }
232   }
233
234   private static IssuesChangesNotificationBuilder.Change readChange(IssuesChangesNotification notification) {
235     String dateFieldValue = notification.getFieldValue(FIELD_CHANGE_DATE);
236     checkState(dateFieldValue != null, "Can not find field %s", FIELD_CHANGE_DATE);
237     long date = Long.parseLong(dateFieldValue);
238
239     String uuid = notification.getFieldValue(FIELD_CHANGE_AUTHOR_UUID);
240     if (uuid == null) {
241       return new IssuesChangesNotificationBuilder.AnalysisChange(date);
242     }
243     String login = notification.getFieldValue(FIELD_CHANGE_AUTHOR_LOGIN);
244     checkState(login != null, "Can not find field %s", FIELD_CHANGE_AUTHOR_LOGIN);
245     return new IssuesChangesNotificationBuilder.UserChange(date, new User(uuid, login, notification.getFieldValue(FIELD_CHANGE_AUTHOR_NAME)));
246   }
247
248   @Immutable
249   private static final class Issue {
250     private final String key;
251     private final String newStatus;
252     @CheckForNull
253     private final String newResolution;
254     @CheckForNull
255     private final String newIssueStatus;
256     @CheckForNull
257     private final User assignee;
258     private final RuleKey ruleKey;
259     private final String projectUuid;
260
261     private Issue(Builder builder) {
262       this.key = builder.key;
263       this.newResolution = builder.newResolution;
264       this.newStatus = builder.newStatus;
265       this.assignee = builder.assignee;
266       this.ruleKey = RuleKey.parse(builder.ruleKey);
267       this.projectUuid = builder.projectUuid;
268       this.newIssueStatus = builder.newIssueStatus;
269     }
270
271     static class Builder {
272       private String key = null;
273       private String newStatus = null;
274       @CheckForNull
275       private String newResolution = null;
276       @CheckForNull
277       private User assignee = null;
278       private String ruleKey = null;
279       private String projectUuid = null;
280       private String newIssueStatus = null;
281
282       public Builder setKey(String key) {
283         this.key = key;
284         return this;
285       }
286
287       public Builder setNewStatus(String newStatus) {
288         this.newStatus = newStatus;
289         return this;
290       }
291
292       public Builder setNewResolution(@Nullable String newResolution) {
293         this.newResolution = newResolution;
294         return this;
295       }
296
297       public Builder setNewIssueStatus(@Nullable String newIssueStatus) {
298         this.newIssueStatus = newIssueStatus;
299         return this;
300       }
301
302       public Builder setAssignee(@Nullable User assignee) {
303         this.assignee = assignee;
304         return this;
305       }
306
307       public Builder setRuleKey(String ruleKey) {
308         this.ruleKey = ruleKey;
309         return this;
310       }
311
312       public Builder setProjectUuid(String projectUuid) {
313         this.projectUuid = projectUuid;
314         return this;
315       }
316
317       public Issue build() {
318         return new Issue(this);
319       }
320     }
321   }
322 }