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.

IssueUpdater.java 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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.ws;
  21. import java.util.List;
  22. import java.util.Optional;
  23. import javax.annotation.Nullable;
  24. import org.sonar.api.rule.RuleKey;
  25. import org.sonar.api.rule.RuleStatus;
  26. import org.sonar.api.rules.RuleType;
  27. import org.sonar.core.issue.DefaultIssue;
  28. import org.sonar.core.issue.IssueChangeContext;
  29. import org.sonar.core.util.stream.MoreCollectors;
  30. import org.sonar.db.DbClient;
  31. import org.sonar.db.DbSession;
  32. import org.sonar.db.component.BranchDto;
  33. import org.sonar.db.component.ComponentDto;
  34. import org.sonar.db.issue.IssueDto;
  35. import org.sonar.db.rule.RuleDefinitionDto;
  36. import org.sonar.db.user.UserDto;
  37. import org.sonar.server.issue.IssueChangePostProcessor;
  38. import org.sonar.server.issue.WebIssueStorage;
  39. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder;
  40. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
  41. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
  42. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Rule;
  43. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User;
  44. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.UserChange;
  45. import org.sonar.server.issue.notification.IssuesChangesNotificationSerializer;
  46. import org.sonar.server.notification.NotificationManager;
  47. import static com.google.common.base.Preconditions.checkState;
  48. import static java.util.Collections.singleton;
  49. import static java.util.Collections.singletonList;
  50. import static org.sonar.db.component.BranchType.PULL_REQUEST;
  51. public class IssueUpdater {
  52. private final DbClient dbClient;
  53. private final WebIssueStorage issueStorage;
  54. private final NotificationManager notificationService;
  55. private final IssueChangePostProcessor issueChangePostProcessor;
  56. private final IssuesChangesNotificationSerializer notificationSerializer;
  57. public IssueUpdater(DbClient dbClient, WebIssueStorage issueStorage, NotificationManager notificationService,
  58. IssueChangePostProcessor issueChangePostProcessor, IssuesChangesNotificationSerializer notificationSerializer) {
  59. this.dbClient = dbClient;
  60. this.issueStorage = issueStorage;
  61. this.notificationService = notificationService;
  62. this.issueChangePostProcessor = issueChangePostProcessor;
  63. this.notificationSerializer = notificationSerializer;
  64. }
  65. public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue,
  66. IssueChangeContext context, boolean refreshMeasures) {
  67. Optional<RuleDefinitionDto> rule = getRuleByKey(dbSession, issue.getRuleKey());
  68. ComponentDto project = dbClient.componentDao().selectOrFailByUuid(dbSession, issue.projectUuid());
  69. BranchDto branch = getBranch(dbSession, issue, issue.projectUuid());
  70. ComponentDto component = getComponent(dbSession, issue, issue.componentUuid());
  71. IssueDto issueDto = doSaveIssue(dbSession, issue, context, rule, project, branch);
  72. SearchResponseData result = new SearchResponseData(issueDto);
  73. rule.ifPresent(r -> result.addRules(singletonList(r)));
  74. result.addComponents(singleton(project));
  75. result.addComponents(singleton(component));
  76. if (refreshMeasures) {
  77. List<DefaultIssue> changedIssues = result.getIssues().stream().map(IssueDto::toDefaultIssue).collect(MoreCollectors.toList(result.getIssues().size()));
  78. issueChangePostProcessor.process(dbSession, changedIssues, singleton(component));
  79. }
  80. return result;
  81. }
  82. private IssueDto doSaveIssue(DbSession session, DefaultIssue issue, IssueChangeContext context,
  83. Optional<RuleDefinitionDto> rule, ComponentDto project, BranchDto branchDto) {
  84. IssueDto issueDto = issueStorage.save(session, singletonList(issue)).iterator().next();
  85. if (
  86. // since this method is called after an update of the issue, date should never be null
  87. issue.updateDate() == null
  88. // name of rule is displayed in notification, rule must therefor be present
  89. || !rule.isPresent()
  90. // notification are not supported on PRs
  91. || !hasNotificationSupport(branchDto)) {
  92. return issueDto;
  93. }
  94. Optional<UserDto> assignee = Optional.ofNullable(issue.assignee())
  95. .map(assigneeUuid -> dbClient.userDao().selectByUuid(session, assigneeUuid));
  96. UserDto author = Optional.ofNullable(context.userUuid())
  97. .map(authorUuid -> dbClient.userDao().selectByUuid(session, authorUuid))
  98. .orElseThrow(() -> new IllegalStateException("Can not find dto for change author " + context.userUuid()));
  99. IssuesChangesNotificationBuilder notificationBuilder = new IssuesChangesNotificationBuilder(singleton(
  100. new ChangedIssue.Builder(issue.key())
  101. .setNewResolution(issue.resolution())
  102. .setNewStatus(issue.status())
  103. .setAssignee(assignee.map(assigneeDto -> new User(assigneeDto.getUuid(), assigneeDto.getLogin(), assigneeDto.getName())).orElse(null))
  104. .setRule(rule.map(r -> new Rule(r.getKey(), RuleType.valueOfNullable(r.getType()), r.getName())).get())
  105. .setProject(new Project.Builder(project.uuid())
  106. .setKey(project.getKey())
  107. .setProjectName(project.name())
  108. .setBranchName(branchDto.isMain() ? null : branchDto.getKey())
  109. .build())
  110. .build()),
  111. new UserChange(issue.updateDate().getTime(), new User(author.getUuid(), author.getLogin(), author.getName())));
  112. notificationService.scheduleForSending(notificationSerializer.serialize(notificationBuilder));
  113. return issueDto;
  114. }
  115. private static boolean hasNotificationSupport(BranchDto branch) {
  116. return branch.getBranchType() != PULL_REQUEST;
  117. }
  118. private ComponentDto getComponent(DbSession dbSession, DefaultIssue issue, @Nullable String componentUuid) {
  119. String issueKey = issue.key();
  120. checkState(componentUuid != null, "Issue '%s' has no component", issueKey);
  121. ComponentDto component = dbClient.componentDao().selectByUuid(dbSession, componentUuid).orElse(null);
  122. checkState(component != null, "Component uuid '%s' for issue key '%s' cannot be found", componentUuid, issueKey);
  123. return component;
  124. }
  125. private BranchDto getBranch(DbSession dbSession, DefaultIssue issue, @Nullable String projectUuid) {
  126. String issueKey = issue.key();
  127. checkState(projectUuid != null, "Issue '%s' has no project", issueKey);
  128. BranchDto component = dbClient.branchDao().selectByUuid(dbSession, projectUuid).orElse(null);
  129. checkState(component != null, "Branch uuid '%s' for issue key '%s' cannot be found", projectUuid, issueKey);
  130. return component;
  131. }
  132. private Optional<RuleDefinitionDto> getRuleByKey(DbSession session, RuleKey ruleKey) {
  133. Optional<RuleDefinitionDto> rule = dbClient.ruleDao().selectDefinitionByKey(session, ruleKey);
  134. return (rule.isPresent() && rule.get().getStatus() != RuleStatus.REMOVED) ? rule : Optional.empty();
  135. }
  136. }