3 * Copyright (C) 2009-2023 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.server.issue.notification;
22 import java.util.ArrayList;
23 import java.util.List;
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;
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;
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.";
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());
62 * @throws IllegalArgumentException if {@code notification} misses any field or of any has unsupported value
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);
71 return new IssuesChangesNotificationBuilder(buildChangedIssues(issues, projects, rules), change);
74 private static void serializeIssueSize(IssuesChangesNotification res, Set<ChangedIssue> issues) {
75 res.setFieldValue(FIELD_ISSUES_COUNT, String.valueOf(issues.size()));
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");
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))
96 .collect(Collectors.toSet());
99 private static void serializeIssues(IssuesChangesNotification res, Set<ChangedIssue> issues) {
101 for (ChangedIssue issue : issues) {
102 serializeIssue(res, index, issue);
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));
115 private static void serializeIssue(IssuesChangesNotification notification, int index, ChangedIssue issue) {
116 String issuePropertyPrefix = "issues." + index;
117 notification.setFieldValue(issuePropertyPrefix + ".key", issue.getKey());
119 .ifPresent(assignee -> {
120 notification.setFieldValue(issuePropertyPrefix + ".assignee.uuid", assignee.getUuid());
121 notification.setFieldValue(issuePropertyPrefix + ".assignee.login", assignee.getLogin());
123 .ifPresent(name -> notification.setFieldValue(issuePropertyPrefix + ".assignee.name", name));
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());
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))
147 private static User readAssignee(IssuesChangesNotification notification, String issuePropertyPrefix, int index) {
148 String uuid = notification.getFieldValue(issuePropertyPrefix + ".assignee.uuid");
152 String login = getIssueFieldValue(notification, issuePropertyPrefix + ".assignee.login", index);
153 return new User(uuid, login, notification.getFieldValue(issuePropertyPrefix + ".assignee.name"));
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);
162 private static void serializeRules(IssuesChangesNotification res, Set<ChangedIssue> issues) {
164 .map(ChangedIssue::getRule)
165 .collect(Collectors.toSet())
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()));
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())
177 .map(ruleKey -> readRule(notification, ruleKey))
178 .collect(Collectors.toMap(Rule::getKey, Function.identity()));
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);
189 private static void serializeProjects(IssuesChangesNotification res, Set<ChangedIssue> issues) {
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));
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())
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"))
215 .collect(Collectors.toMap(Project::getUuid, Function.identity()));
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);
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));
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);
239 String uuid = notification.getFieldValue(FIELD_CHANGE_AUTHOR_UUID);
241 return new IssuesChangesNotificationBuilder.AnalysisChange(date);
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)));
249 private static final class Issue {
250 private final String key;
251 private final String newStatus;
253 private final String newResolution;
255 private final String newIssueStatus;
257 private final User assignee;
258 private final RuleKey ruleKey;
259 private final String projectUuid;
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;
271 static class Builder {
272 private String key = null;
273 private String newStatus = null;
275 private String newResolution = null;
277 private User assignee = null;
278 private String ruleKey = null;
279 private String projectUuid = null;
280 private String newIssueStatus = null;
282 public Builder setKey(String key) {
287 public Builder setNewStatus(String newStatus) {
288 this.newStatus = newStatus;
292 public Builder setNewResolution(@Nullable String newResolution) {
293 this.newResolution = newResolution;
297 public Builder setNewIssueStatus(@Nullable String newIssueStatus) {
298 this.newIssueStatus = newIssueStatus;
302 public Builder setAssignee(@Nullable User assignee) {
303 this.assignee = assignee;
307 public Builder setRuleKey(String ruleKey) {
308 this.ruleKey = ruleKey;
312 public Builder setProjectUuid(String projectUuid) {
313 this.projectUuid = projectUuid;
317 public Issue build() {
318 return new Issue(this);