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.server.pushapi.issues;
22 import com.google.gson.Gson;
23 import com.google.gson.GsonBuilder;
24 import java.util.Collection;
26 import java.util.Map.Entry;
28 import java.util.stream.Collectors;
29 import javax.annotation.CheckForNull;
30 import javax.annotation.Nullable;
31 import org.sonar.api.server.ServerSide;
32 import org.sonar.core.issue.DefaultIssue;
33 import org.sonar.core.issue.FieldDiffs.Diff;
34 import org.sonar.core.util.issue.Issue;
35 import org.sonar.core.util.issue.IssueChangedEvent;
36 import org.sonar.db.DbClient;
37 import org.sonar.db.DbSession;
38 import org.sonar.db.component.BranchDto;
39 import org.sonar.db.component.ComponentDto;
40 import org.sonar.db.pushevent.PushEventDto;
42 import static java.nio.charset.StandardCharsets.UTF_8;
43 import static org.elasticsearch.common.Strings.isNullOrEmpty;
44 import static org.sonar.api.issue.DefaultTransitions.CONFIRM;
45 import static org.sonar.api.issue.DefaultTransitions.FALSE_POSITIVE;
46 import static org.sonar.api.issue.DefaultTransitions.UNCONFIRM;
47 import static org.sonar.api.issue.DefaultTransitions.WONT_FIX;
48 import static org.sonar.db.component.BranchType.BRANCH;
51 public class IssueChangeEventServiceImpl implements IssueChangeEventService {
52 private static final Gson GSON = new GsonBuilder().create();
54 private static final String EVENT_NAME = "IssueChanged";
55 private static final String FALSE_POSITIVE_KEY = "FALSE-POSITIVE";
56 private static final String WONT_FIX_KEY = "WONTFIX";
58 private static final String RESOLUTION_KEY = "resolution";
59 private static final String SEVERITY_KEY = "severity";
60 private static final String TYPE_KEY = "type";
62 private final DbClient dbClient;
64 public IssueChangeEventServiceImpl(DbClient dbClient) {
65 this.dbClient = dbClient;
69 public void distributeIssueChangeEvent(DefaultIssue issue, @Nullable String severity, @Nullable String type, @Nullable String transition,
70 BranchDto branch, String projectKey) {
71 Issue changedIssue = new Issue(issue.key(), branch.getKey());
73 Boolean resolved = isResolved(transition);
75 if (severity == null && type == null && resolved == null) {
79 IssueChangedEvent event = new IssueChangedEvent(projectKey, new Issue[]{changedIssue},
80 resolved, severity, type);
82 persistEvent(event, issue.projectUuid());
86 public void distributeIssueChangeEvent(Collection<DefaultIssue> issues, Map<String, ComponentDto> projectsByUuid,
87 Map<String, BranchDto> branchesByProjectUuid) {
89 for (Entry<String, ComponentDto> entry : projectsByUuid.entrySet()) {
90 String projectKey = entry.getValue().getKey();
92 Set<DefaultIssue> issuesInProject = issues
94 .filter(i -> i.projectUuid().equals(entry.getKey()))
95 .collect(Collectors.toSet());
97 Issue[] issueChanges = issuesInProject.stream()
98 .filter(i -> branchesByProjectUuid.get(i.projectUuid()).getBranchType().equals(BRANCH))
99 .map(i -> new Issue(i.key(), branchesByProjectUuid.get(i.projectUuid()).getKey()))
100 .toArray(Issue[]::new);
102 if (issueChanges.length == 0) {
106 IssueChangedEvent event = getIssueChangedEvent(projectKey, issuesInProject, issueChanges);
109 persistEvent(event, entry.getValue().projectUuid());
115 private static IssueChangedEvent getIssueChangedEvent(String projectKey, Set<DefaultIssue> issuesInProject, Issue[] issueChanges) {
116 DefaultIssue firstIssue = issuesInProject.stream().iterator().next();
118 if (firstIssue.currentChange() == null) {
122 Boolean resolved = null;
123 String severity = null;
126 boolean isRelevantEvent = false;
127 Map<String, Diff> diffs = firstIssue.currentChange().diffs();
129 if (diffs.containsKey(RESOLUTION_KEY)) {
130 resolved = diffs.get(RESOLUTION_KEY).newValue() == null ? false : isResolved(diffs.get(RESOLUTION_KEY).newValue().toString());
131 isRelevantEvent = true;
134 if (diffs.containsKey(SEVERITY_KEY)) {
135 severity = diffs.get(SEVERITY_KEY).newValue() == null ? null : diffs.get(SEVERITY_KEY).newValue().toString();
136 isRelevantEvent = true;
139 if (diffs.containsKey(TYPE_KEY)) {
140 type = diffs.get(TYPE_KEY).newValue() == null ? null : diffs.get(TYPE_KEY).newValue().toString();
141 isRelevantEvent = true;
144 if (!isRelevantEvent) {
148 return new IssueChangedEvent(projectKey, issueChanges, resolved, severity, type);
152 private static Boolean isResolved(@Nullable String transitionOrStatus) {
153 if (isNullOrEmpty(transitionOrStatus)) {
157 if (transitionOrStatus.equals(CONFIRM) || transitionOrStatus.equals(UNCONFIRM)) {
161 return transitionOrStatus.equals(WONT_FIX) || transitionOrStatus.equals(FALSE_POSITIVE) ||
162 transitionOrStatus.equals(FALSE_POSITIVE_KEY) || transitionOrStatus.equals(WONT_FIX_KEY);
165 private void persistEvent(IssueChangedEvent event, String entry) {
166 try (DbSession dbSession = dbClient.openSession(false)) {
167 PushEventDto eventDto = new PushEventDto()
169 .setProjectUuid(entry)
170 .setPayload(serializeIssueToPushEvent(event));
171 dbClient.pushEventDao().insert(dbSession, eventDto);
176 private static byte[] serializeIssueToPushEvent(IssueChangedEvent event) {
177 return GSON.toJson(event).getBytes(UTF_8);