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.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.lang3.StringUtils;
37 import org.jetbrains.annotations.NotNull;
38 import org.sonar.api.measures.CoreMetrics;
39 import org.sonar.api.rule.RuleKey;
40 import org.sonar.api.server.ServerSide;
41 import org.sonar.core.util.ParamChange;
42 import org.sonar.core.util.rule.RuleChange;
43 import org.sonar.core.util.rule.RuleSetChangedEvent;
44 import org.sonar.db.DbClient;
45 import org.sonar.db.DbSession;
46 import org.sonar.db.measure.ProjectMainBranchMeasureDto;
47 import org.sonar.db.project.ProjectDto;
48 import org.sonar.db.pushevent.PushEventDto;
49 import org.sonar.db.qualityprofile.ActiveRuleDto;
50 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
51 import org.sonar.db.qualityprofile.OrgActiveRuleDto;
52 import org.sonar.db.qualityprofile.ProjectQprofileAssociationDto;
53 import org.sonar.db.qualityprofile.QProfileDto;
54 import org.sonar.db.rule.RuleDto;
55 import org.sonar.server.qualityprofile.ActiveRuleChange;
57 import static java.nio.charset.StandardCharsets.UTF_8;
58 import static java.util.Collections.emptyList;
59 import static java.util.function.Predicate.not;
60 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
61 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
62 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED;
65 public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService {
66 private static final Gson GSON = new GsonBuilder().create();
67 private static final String EVENT_NAME = "RuleSetChanged";
69 private final DbClient dbClient;
71 public QualityProfileChangeEventServiceImpl(DbClient dbClient) {
72 this.dbClient = dbClient;
76 public void publishRuleActivationToSonarLintClients(ProjectDto project, @Nullable QProfileDto activatedProfile,
77 @Nullable QProfileDto deactivatedProfile) {
78 List<RuleChange> activatedRules = new ArrayList<>();
79 Set<String> deactivatedRules = new HashSet<>();
81 if (activatedProfile != null) {
82 activatedRules.addAll(createRuleChanges(activatedProfile));
85 if (deactivatedProfile != null) {
86 deactivatedRules.addAll(getRuleKeys(deactivatedProfile));
89 if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
93 String language = activatedProfile != null ? activatedProfile.getLanguage() : deactivatedProfile.getLanguage();
95 persistPushEvent(project.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, project.getUuid());
98 private List<RuleChange> createRuleChanges(@NotNull QProfileDto profileDto) {
99 List<RuleChange> ruleChanges = new ArrayList<>();
101 try (DbSession dbSession = dbClient.openSession(false)) {
102 List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfile(dbSession, profileDto);
103 List<String> activeRuleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getUuid).toList();
105 Map<String, List<ActiveRuleParamDto>> paramsByActiveRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids)
106 .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
108 Map<String, String> activeRuleUuidByRuleUuid = activeRuleDtos.stream().collect(Collectors.toMap(ActiveRuleDto::getRuleUuid, ActiveRuleDto::getUuid));
110 List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).toList();
111 List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
113 for (RuleDto ruleDto : ruleDtos) {
114 String activeRuleUuid = activeRuleUuidByRuleUuid.get(ruleDto.getUuid());
115 List<ActiveRuleParamDto> params = paramsByActiveRuleUuid.getOrDefault(activeRuleUuid, new ArrayList<>());
116 RuleChange ruleChange = toRuleChange(ruleDto, params);
117 ruleChanges.add(ruleChange);
123 private Set<String> getRuleKeys(@NotNull QProfileDto profileDto) {
124 Set<String> ruleKeys = new HashSet<>();
126 try (DbSession dbSession = dbClient.openSession(false)) {
127 List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfile(dbSession, profileDto);
129 List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).toList();
130 List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
132 for (RuleDto ruleDto : ruleDtos) {
133 ruleKeys.add(ruleDto.getKey().toString());
140 private RuleChange toRuleChange(RuleDto ruleDto, List<ActiveRuleParamDto> activeRuleParamDtos) {
141 RuleChange ruleChange = new RuleChange();
142 ruleChange.setKey(ruleDto.getKey().toString());
143 ruleChange.setLanguage(ruleDto.getLanguage());
144 ruleChange.setSeverity(ruleDto.getSeverityString());
146 List<ParamChange> paramChanges = new ArrayList<>();
147 for (ActiveRuleParamDto activeRuleParam : activeRuleParamDtos) {
148 paramChanges.add(new ParamChange(activeRuleParam.getKey(), activeRuleParam.getValue()));
150 ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
152 String templateUuid = ruleDto.getTemplateUuid();
153 if (templateUuid != null && !"".equals(templateUuid)) {
154 try (DbSession dbSession = dbClient.openSession(false)) {
155 RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
156 .orElseThrow(() -> new IllegalStateException(String.format("Unknown Template Rule '%s'", templateUuid)));
157 ruleChange.setTemplateKey(templateRule.getKey().toString());
164 public void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language) {
165 if (activeRuleChanges.isEmpty()) {
169 Set<RuleChange> activatedRules = new HashSet<>();
171 for (ActiveRuleChange arc : activeRuleChanges) {
172 ActiveRuleDto activeRule = arc.getActiveRule();
173 if (activeRule == null) {
177 RuleChange ruleChange = new RuleChange();
178 ruleChange.setKey(activeRule.getRuleKey().toString());
179 ruleChange.setSeverity(arc.getSeverity());
180 ruleChange.setLanguage(language);
182 Optional<String> templateKey = templateKey(arc);
183 templateKey.ifPresent(ruleChange::setTemplateKey);
186 List<ParamChange> paramChanges = new ArrayList<>();
187 for (Map.Entry<String, String> entry : arc.getParameters().entrySet()) {
188 paramChanges.add(new ParamChange(entry.getKey(), entry.getValue()));
190 ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
192 if (ACTIVATED.equals(arc.getType()) || UPDATED.equals(arc.getType())) {
193 activatedRules.add(ruleChange);
197 Set<String> deactivatedRules = activeRuleChanges.stream()
198 .filter(r -> DEACTIVATED.equals(r.getType()))
199 .map(ActiveRuleChange::getActiveRule)
200 .filter(not(Objects::isNull))
201 .map(ActiveRuleDto::getRuleKey)
202 .map(RuleKey::toString)
203 .collect(Collectors.toSet());
205 if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
209 Map<String, String> projectsUuidByKey = getProjectsUuidByKey(profiles, language);
211 for (Map.Entry<String, String> entry : projectsUuidByKey.entrySet()) {
212 persistPushEvent(entry.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, entry.getValue());
216 private void persistPushEvent(String projectKey, RuleChange[] activatedRules, Set<String> deactivatedRules, String language, String projectUuid) {
217 RuleSetChangedEvent event = new RuleSetChangedEvent(projectKey, activatedRules, deactivatedRules.toArray(new String[0]));
219 try (DbSession dbSession = dbClient.openSession(false)) {
220 PushEventDto eventDto = new PushEventDto()
222 .setProjectUuid(projectUuid)
223 .setLanguage(language)
224 .setPayload(serializeIssueToPushEvent(event));
225 dbClient.pushEventDao().insert(dbSession, eventDto);
230 private Optional<String> templateKey(ActiveRuleChange arc) {
231 try (DbSession dbSession = dbClient.openSession(false)) {
232 String ruleUuid = arc.getRuleUuid();
233 RuleDto rule = dbClient.ruleDao().selectByUuid(ruleUuid, dbSession).orElseThrow(() -> new IllegalStateException("unknow rule"));
234 String templateUuid = rule.getTemplateUuid();
236 if (StringUtils.isNotEmpty(templateUuid)) {
237 RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
238 .orElseThrow(() -> new IllegalStateException(String.format("Unknown Template Rule '%s'", templateUuid)));
239 return Optional.of(templateRule.getKey().toString());
242 return Optional.empty();
245 private Map<String, String> getProjectsUuidByKey(Collection<QProfileDto> profiles, String language) {
246 try (DbSession dbSession = dbClient.openSession(false)) {
247 Map<Boolean, List<QProfileDto>> profilesByDefaultStatus = classifyQualityProfilesByDefaultStatus(dbSession, profiles, language);
249 List<ProjectDto> defaultAssociatedProjects = getDefaultAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(true), language);
250 List<ProjectDto> manuallyAssociatedProjects = getManuallyAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(false));
253 .concat(manuallyAssociatedProjects.stream(), defaultAssociatedProjects.stream())
254 .collect(Collectors.toMap(ProjectDto::getKey, ProjectDto::getUuid));
258 private Map<Boolean, List<QProfileDto>> classifyQualityProfilesByDefaultStatus(DbSession dbSession, Collection<QProfileDto> profiles, String language) {
259 String defaultQualityProfileUuid = dbClient.qualityProfileDao().selectDefaultProfileUuid(dbSession, language);
260 Predicate<QProfileDto> isDefaultQualityProfile = profile -> profile.getKee().equals(defaultQualityProfileUuid);
264 .collect(Collectors.partitioningBy(isDefaultQualityProfile));
267 private List<ProjectDto> getDefaultAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> defaultProfiles, String language) {
268 if (defaultProfiles.isEmpty()) {
272 return getDefaultQualityProfileAssociatedProjects(dbSession, language);
275 private List<ProjectDto> getDefaultQualityProfileAssociatedProjects(DbSession dbSession, String language) {
276 Set<String> associatedProjectUuids = new HashSet<>();
278 List<ProjectMainBranchMeasureDto> measureDtos =
279 dbClient.measureDao().selectAllForProjectMainBranchesAssociatedToDefaultQualityProfile(dbSession);
280 for (ProjectMainBranchMeasureDto measureDto : measureDtos) {
281 String distribution = (String) measureDto.getMetricValues().get(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY);
282 if (distribution != null && distributionContainsLanguage(distribution, language)) {
283 associatedProjectUuids.add(measureDto.getProjectUuid());
287 return dbClient.projectDao().selectByUuids(dbSession, associatedProjectUuids);
290 private static boolean distributionContainsLanguage(String distribution, String language) {
291 return distribution.startsWith(language + "=") || distribution.contains(";" + language + "=");
294 private List<ProjectDto> getManuallyAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> profiles) {
297 .map(profile -> getQualityProfileAssociatedProjects(dbSession, profile))
298 .flatMap(Collection::stream)
302 private List<ProjectDto> getQualityProfileAssociatedProjects(DbSession dbSession, QProfileDto profile) {
303 Set<String> projectUuids = getQualityProfileAssociatedProjectUuids(dbSession, profile);
304 return dbClient.projectDao().selectByUuids(dbSession, projectUuids);
307 private Set<String> getQualityProfileAssociatedProjectUuids(DbSession dbSession, QProfileDto profile) {
308 List<ProjectQprofileAssociationDto> associations = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profile, null);
312 .map(ProjectQprofileAssociationDto::getProjectUuid)
313 .collect(Collectors.toSet());
319 private static byte[] serializeIssueToPushEvent(RuleSetChangedEvent event) {
320 return GSON.toJson(event).getBytes(UTF_8);