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.pushapi.qualityprofile;
22 import com.google.gson.Gson;
23 import com.google.gson.GsonBuilder;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashSet;
27 import java.util.List;
29 import java.util.Objects;
30 import java.util.Optional;
32 import java.util.function.Predicate;
33 import java.util.stream.Collectors;
34 import java.util.stream.Stream;
35 import javax.annotation.Nullable;
36 import org.apache.commons.lang.StringUtils;
37 import org.jetbrains.annotations.NotNull;
38 import org.sonar.api.rule.RuleKey;
39 import org.sonar.api.server.ServerSide;
40 import org.sonar.core.util.ParamChange;
41 import org.sonar.core.util.rule.RuleChange;
42 import org.sonar.core.util.rule.RuleSetChangedEvent;
43 import org.sonar.db.DbClient;
44 import org.sonar.db.DbSession;
45 import org.sonar.db.project.ProjectDto;
46 import org.sonar.db.pushevent.PushEventDto;
47 import org.sonar.db.qualityprofile.ActiveRuleDto;
48 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
49 import org.sonar.db.qualityprofile.OrgActiveRuleDto;
50 import org.sonar.db.qualityprofile.ProjectQprofileAssociationDto;
51 import org.sonar.db.qualityprofile.QProfileDto;
52 import org.sonar.db.rule.RuleDto;
53 import org.sonar.server.qualityprofile.ActiveRuleChange;
55 import static java.nio.charset.StandardCharsets.UTF_8;
56 import static java.util.Collections.emptyList;
57 import static java.util.function.Predicate.not;
58 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
59 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
60 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED;
63 public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService {
64 private static final Gson GSON = new GsonBuilder().create();
65 private static final String EVENT_NAME = "RuleSetChanged";
67 private final DbClient dbClient;
69 public QualityProfileChangeEventServiceImpl(DbClient dbClient) {
70 this.dbClient = dbClient;
74 public void publishRuleActivationToSonarLintClients(ProjectDto project, @Nullable QProfileDto activatedProfile,
75 @Nullable QProfileDto deactivatedProfile) {
76 List<RuleChange> activatedRules = new ArrayList<>();
77 Set<String> deactivatedRules = new HashSet<>();
79 if (activatedProfile != null) {
80 activatedRules.addAll(createRuleChanges(activatedProfile));
83 if (deactivatedProfile != null) {
84 deactivatedRules.addAll(getRuleKeys(deactivatedProfile));
87 if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
91 String language = activatedProfile != null ? activatedProfile.getLanguage() : deactivatedProfile.getLanguage();
93 persistPushEvent(project.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, project.getUuid());
96 private List<RuleChange> createRuleChanges(@NotNull QProfileDto profileDto) {
97 List<RuleChange> ruleChanges = new ArrayList<>();
99 try (DbSession dbSession = dbClient.openSession(false)) {
100 List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfile(dbSession, profileDto);
101 List<String> activeRuleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getUuid).collect(Collectors.toList());
103 Map<String, List<ActiveRuleParamDto>> paramsByActiveRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids)
104 .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
106 Map<String, String> activeRuleUuidByRuleUuid = activeRuleDtos.stream().collect(Collectors.toMap(ActiveRuleDto::getRuleUuid, ActiveRuleDto::getUuid));
108 List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).collect(Collectors.toList());
109 List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
111 for (RuleDto ruleDto : ruleDtos) {
112 String activeRuleUuid = activeRuleUuidByRuleUuid.get(ruleDto.getUuid());
113 List<ActiveRuleParamDto> params = paramsByActiveRuleUuid.getOrDefault(activeRuleUuid, new ArrayList<>());
114 RuleChange ruleChange = toRuleChange(ruleDto, params);
115 ruleChanges.add(ruleChange);
121 private Set<String> getRuleKeys(@NotNull QProfileDto profileDto) {
122 Set<String> ruleKeys = new HashSet<>();
124 try (DbSession dbSession = dbClient.openSession(false)) {
125 List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfile(dbSession, profileDto);
127 List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).collect(Collectors.toList());
128 List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
130 for (RuleDto ruleDto : ruleDtos) {
131 ruleKeys.add(ruleDto.getKey().toString());
138 private RuleChange toRuleChange(RuleDto ruleDto, List<ActiveRuleParamDto> activeRuleParamDtos) {
139 RuleChange ruleChange = new RuleChange();
140 ruleChange.setKey(ruleDto.getKey().toString());
141 ruleChange.setLanguage(ruleDto.getLanguage());
142 ruleChange.setSeverity(ruleDto.getSeverityString());
144 List<ParamChange> paramChanges = new ArrayList<>();
145 for (ActiveRuleParamDto activeRuleParam : activeRuleParamDtos) {
146 paramChanges.add(new ParamChange(activeRuleParam.getKey(), activeRuleParam.getValue()));
148 ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
150 String templateUuid = ruleDto.getTemplateUuid();
151 if (templateUuid != null && !"".equals(templateUuid)) {
152 try (DbSession dbSession = dbClient.openSession(false)) {
153 RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
154 .orElseThrow(() -> new IllegalStateException(String.format("Unknown Template Rule '%s'", templateUuid)));
155 ruleChange.setTemplateKey(templateRule.getKey().toString());
162 public void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language) {
163 if (activeRuleChanges.isEmpty()) {
167 Set<RuleChange> activatedRules = new HashSet<>();
169 for (ActiveRuleChange arc : activeRuleChanges) {
170 ActiveRuleDto activeRule = arc.getActiveRule();
171 if (activeRule == null) {
175 RuleChange ruleChange = new RuleChange();
176 ruleChange.setKey(activeRule.getRuleKey().toString());
177 ruleChange.setSeverity(arc.getSeverity());
178 ruleChange.setLanguage(language);
180 Optional<String> templateKey = templateKey(arc);
181 templateKey.ifPresent(ruleChange::setTemplateKey);
184 List<ParamChange> paramChanges = new ArrayList<>();
185 for (Map.Entry<String, String> entry : arc.getParameters().entrySet()) {
186 paramChanges.add(new ParamChange(entry.getKey(), entry.getValue()));
188 ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
190 if (ACTIVATED.equals(arc.getType()) || UPDATED.equals(arc.getType())) {
191 activatedRules.add(ruleChange);
195 Set<String> deactivatedRules = activeRuleChanges.stream()
196 .filter(r -> DEACTIVATED.equals(r.getType()))
197 .map(ActiveRuleChange::getActiveRule)
198 .filter(not(Objects::isNull))
199 .map(ActiveRuleDto::getRuleKey)
200 .map(RuleKey::toString)
201 .collect(Collectors.toSet());
203 if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
207 Map<String, String> projectsUuidByKey = getProjectsUuidByKey(profiles, language);
209 for (Map.Entry<String, String> entry : projectsUuidByKey.entrySet()) {
210 persistPushEvent(entry.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, entry.getValue());
214 private void persistPushEvent(String projectKey, RuleChange[] activatedRules, Set<String> deactivatedRules, String language, String projectUuid) {
215 RuleSetChangedEvent event = new RuleSetChangedEvent(projectKey, activatedRules, deactivatedRules.toArray(new String[0]));
217 try (DbSession dbSession = dbClient.openSession(false)) {
218 PushEventDto eventDto = new PushEventDto()
220 .setProjectUuid(projectUuid)
221 .setLanguage(language)
222 .setPayload(serializeIssueToPushEvent(event));
223 dbClient.pushEventDao().insert(dbSession, eventDto);
228 private Optional<String> templateKey(ActiveRuleChange arc) {
229 try (DbSession dbSession = dbClient.openSession(false)) {
230 String ruleUuid = arc.getRuleUuid();
231 RuleDto rule = dbClient.ruleDao().selectByUuid(ruleUuid, dbSession).orElseThrow(() -> new IllegalStateException("unknow rule"));
232 String templateUuid = rule.getTemplateUuid();
234 if (StringUtils.isNotEmpty(templateUuid)) {
235 RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
236 .orElseThrow(() -> new IllegalStateException(String.format("Unknown Template Rule '%s'", templateUuid)));
237 return Optional.of(templateRule.getKey().toString());
240 return Optional.empty();
243 private Map<String, String> getProjectsUuidByKey(Collection<QProfileDto> profiles, String language) {
244 try (DbSession dbSession = dbClient.openSession(false)) {
245 Map<Boolean, List<QProfileDto>> profilesByDefaultStatus = classifyQualityProfilesByDefaultStatus(dbSession, profiles, language);
247 List<ProjectDto> defaultAssociatedProjects = getDefaultAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(true), language);
248 List<ProjectDto> manuallyAssociatedProjects = getManuallyAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(false));
251 .concat(manuallyAssociatedProjects.stream(), defaultAssociatedProjects.stream())
252 .collect(Collectors.toMap(ProjectDto::getKey, ProjectDto::getUuid));
256 private Map<Boolean, List<QProfileDto>> classifyQualityProfilesByDefaultStatus(DbSession dbSession, Collection<QProfileDto> profiles, String language) {
257 String defaultQualityProfileUuid = dbClient.qualityProfileDao().selectDefaultProfileUuid(dbSession, language);
258 Predicate<QProfileDto> isDefaultQualityProfile = profile -> profile.getKee().equals(defaultQualityProfileUuid);
262 .collect(Collectors.partitioningBy(isDefaultQualityProfile));
265 private List<ProjectDto> getDefaultAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> defaultProfiles, String language) {
266 if (defaultProfiles.isEmpty()) {
270 return getDefaultQualityProfileAssociatedProjects(dbSession, language);
273 private List<ProjectDto> getDefaultQualityProfileAssociatedProjects(DbSession dbSession, String language) {
274 Set<String> associatedProjectUuids = dbClient.projectDao().selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(dbSession, language);
275 return dbClient.projectDao().selectByUuids(dbSession, associatedProjectUuids);
278 private List<ProjectDto> getManuallyAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> profiles) {
281 .map(profile -> getQualityProfileAssociatedProjects(dbSession, profile))
282 .flatMap(Collection::stream)
283 .collect(Collectors.toList());
286 private List<ProjectDto> getQualityProfileAssociatedProjects(DbSession dbSession, QProfileDto profile) {
287 Set<String> projectUuids = getQualityProfileAssociatedProjectUuids(dbSession, profile);
288 return dbClient.projectDao().selectByUuids(dbSession, projectUuids);
291 private Set<String> getQualityProfileAssociatedProjectUuids(DbSession dbSession, QProfileDto profile) {
292 List<ProjectQprofileAssociationDto> associations = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profile, null);
296 .map(ProjectQprofileAssociationDto::getProjectUuid)
297 .collect(Collectors.toSet());
303 private static byte[] serializeIssueToPushEvent(RuleSetChangedEvent event) {
304 return GSON.toJson(event).getBytes(UTF_8);