You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IssuesChangesNotificationSerializer.java 13KB

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