3 * Copyright (C) 2009-2024 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.ACCEPT;
45 import static org.sonar.api.issue.DefaultTransitions.CONFIRM;
46 import static org.sonar.api.issue.DefaultTransitions.FALSE_POSITIVE;
47 import static org.sonar.api.issue.DefaultTransitions.UNCONFIRM;
48 import static org.sonar.api.issue.DefaultTransitions.WONT_FIX;
49 import static org.sonar.db.component.BranchType.BRANCH;
52 public class IssueChangeEventServiceImpl implements IssueChangeEventService {
53 private static final Gson GSON = new GsonBuilder().create();
55 private static final String EVENT_NAME = "IssueChanged";
56 private static final String FALSE_POSITIVE_KEY = "FALSE-POSITIVE";
57 private static final String WONT_FIX_KEY = "WONTFIX";
59 private static final String RESOLUTION_KEY = "resolution";
60 private static final String SEVERITY_KEY = "severity";
61 private static final String TYPE_KEY = "type";
63 private final DbClient dbClient;
65 public IssueChangeEventServiceImpl(DbClient dbClient) {
66 this.dbClient = dbClient;
70 public void distributeIssueChangeEvent(DefaultIssue issue, @Nullable String severity, @Nullable String type, @Nullable String transition,
71 BranchDto branch, String projectKey) {
72 Issue changedIssue = new Issue(issue.key(), branch.getKey());
74 Boolean resolved = isResolved(transition);
76 if (severity == null && type == null && resolved == null) {
80 IssueChangedEvent event = new IssueChangedEvent(projectKey, new Issue[]{changedIssue},
81 resolved, severity, type);
83 persistEvent(event, branch.getProjectUuid());
87 public void distributeIssueChangeEvent(Collection<DefaultIssue> issues, Map<String, ComponentDto> projectsByUuid,
88 Map<String, BranchDto> branchesByProjectUuid) {
90 for (Entry<String, ComponentDto> entry : projectsByUuid.entrySet()) {
91 String projectKey = entry.getValue().getKey();
93 Set<DefaultIssue> issuesInProject = issues
95 .filter(i -> i.projectUuid().equals(entry.getKey()))
96 .collect(Collectors.toSet());
98 Issue[] issueChanges = issuesInProject.stream()
99 .filter(i -> branchesByProjectUuid.get(i.projectUuid()).getBranchType().equals(BRANCH))
100 .map(i -> new Issue(i.key(), branchesByProjectUuid.get(i.projectUuid()).getKey()))
101 .toArray(Issue[]::new);
103 if (issueChanges.length == 0) {
107 IssueChangedEvent event = getIssueChangedEvent(projectKey, issuesInProject, issueChanges);
110 BranchDto branchDto = branchesByProjectUuid.get(entry.getKey());
111 persistEvent(event, branchDto.getProjectUuid());
117 private static IssueChangedEvent getIssueChangedEvent(String projectKey, Set<DefaultIssue> issuesInProject, Issue[] issueChanges) {
118 DefaultIssue firstIssue = issuesInProject.stream().iterator().next();
120 if (firstIssue.currentChange() == null) {
124 Boolean resolved = null;
125 String severity = null;
128 boolean isRelevantEvent = false;
129 Map<String, Diff> diffs = firstIssue.currentChange().diffs();
131 if (diffs.containsKey(RESOLUTION_KEY)) {
132 resolved = diffs.get(RESOLUTION_KEY).newValue() == null ? false : isResolved(diffs.get(RESOLUTION_KEY).newValue().toString());
133 isRelevantEvent = true;
136 if (diffs.containsKey(SEVERITY_KEY)) {
137 severity = diffs.get(SEVERITY_KEY).newValue() == null ? null : diffs.get(SEVERITY_KEY).newValue().toString();
138 isRelevantEvent = true;
141 if (diffs.containsKey(TYPE_KEY)) {
142 type = diffs.get(TYPE_KEY).newValue() == null ? null : diffs.get(TYPE_KEY).newValue().toString();
143 isRelevantEvent = true;
146 if (!isRelevantEvent) {
150 return new IssueChangedEvent(projectKey, issueChanges, resolved, severity, type);
154 private static Boolean isResolved(@Nullable String transitionOrStatus) {
155 if (isNullOrEmpty(transitionOrStatus)) {
159 if (transitionOrStatus.equals(CONFIRM) || transitionOrStatus.equals(UNCONFIRM)) {
163 return transitionOrStatus.equals(ACCEPT) || transitionOrStatus.equals(WONT_FIX) || transitionOrStatus.equals(FALSE_POSITIVE) ||
164 transitionOrStatus.equals(FALSE_POSITIVE_KEY) || transitionOrStatus.equals(WONT_FIX_KEY);
167 private void persistEvent(IssueChangedEvent event, String entry) {
168 try (DbSession dbSession = dbClient.openSession(false)) {
169 PushEventDto eventDto = new PushEventDto()
171 .setProjectUuid(entry)
172 .setPayload(serializeIssueToPushEvent(event));
173 dbClient.pushEventDao().insert(dbSession, eventDto);
178 private static byte[] serializeIssueToPushEvent(IssueChangedEvent event) {
179 return GSON.toJson(event).getBytes(UTF_8);