]> source.dussan.org Git - sonarqube.git/blob
ddffddaab9954d841f3ee776cd6e2f6adbcd4725
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.pushapi.qualityprofile;
21
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;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Optional;
31 import java.util.Set;
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;
56
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;
63
64 @ServerSide
65 public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService {
66   private static final Gson GSON = new GsonBuilder().create();
67   private static final String EVENT_NAME = "RuleSetChanged";
68
69   private final DbClient dbClient;
70
71   public QualityProfileChangeEventServiceImpl(DbClient dbClient) {
72     this.dbClient = dbClient;
73   }
74
75   @Override
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<>();
80
81     if (activatedProfile != null) {
82       activatedRules.addAll(createRuleChanges(activatedProfile));
83     }
84
85     if (deactivatedProfile != null) {
86       deactivatedRules.addAll(getRuleKeys(deactivatedProfile));
87     }
88
89     if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
90       return;
91     }
92
93     String language = activatedProfile != null ? activatedProfile.getLanguage() : deactivatedProfile.getLanguage();
94
95     persistPushEvent(project.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, project.getUuid());
96   }
97
98   private List<RuleChange> createRuleChanges(@NotNull QProfileDto profileDto) {
99     List<RuleChange> ruleChanges = new ArrayList<>();
100
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();
104
105       Map<String, List<ActiveRuleParamDto>> paramsByActiveRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids)
106         .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
107
108       Map<String, String> activeRuleUuidByRuleUuid = activeRuleDtos.stream().collect(Collectors.toMap(ActiveRuleDto::getRuleUuid, ActiveRuleDto::getUuid));
109
110       List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).toList();
111       List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
112
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);
118       }
119     }
120     return ruleChanges;
121   }
122
123   private Set<String> getRuleKeys(@NotNull QProfileDto profileDto) {
124     Set<String> ruleKeys = new HashSet<>();
125
126     try (DbSession dbSession = dbClient.openSession(false)) {
127       List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfile(dbSession, profileDto);
128
129       List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).toList();
130       List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
131
132       for (RuleDto ruleDto : ruleDtos) {
133         ruleKeys.add(ruleDto.getKey().toString());
134       }
135     }
136     return ruleKeys;
137   }
138
139   @NotNull
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());
145
146     List<ParamChange> paramChanges = new ArrayList<>();
147     for (ActiveRuleParamDto activeRuleParam : activeRuleParamDtos) {
148       paramChanges.add(new ParamChange(activeRuleParam.getKey(), activeRuleParam.getValue()));
149     }
150     ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
151
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());
158       }
159     }
160
161     return ruleChange;
162   }
163
164   public void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language) {
165     if (activeRuleChanges.isEmpty()) {
166       return;
167     }
168
169     Set<RuleChange> activatedRules = new HashSet<>();
170
171     for (ActiveRuleChange arc : activeRuleChanges) {
172       ActiveRuleDto activeRule = arc.getActiveRule();
173       if (activeRule == null) {
174         continue;
175       }
176
177       RuleChange ruleChange = new RuleChange();
178       ruleChange.setKey(activeRule.getRuleKey().toString());
179       ruleChange.setSeverity(arc.getSeverity());
180       ruleChange.setLanguage(language);
181
182       Optional<String> templateKey = templateKey(arc);
183       templateKey.ifPresent(ruleChange::setTemplateKey);
184
185       // params
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()));
189       }
190       ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
191
192       if (ACTIVATED.equals(arc.getType()) || UPDATED.equals(arc.getType())) {
193         activatedRules.add(ruleChange);
194       }
195     }
196
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());
204
205     if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
206       return;
207     }
208
209     Map<String, String> projectsUuidByKey = getProjectsUuidByKey(profiles, language);
210
211     for (Map.Entry<String, String> entry : projectsUuidByKey.entrySet()) {
212       persistPushEvent(entry.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, entry.getValue());
213     }
214   }
215
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]));
218
219     try (DbSession dbSession = dbClient.openSession(false)) {
220       PushEventDto eventDto = new PushEventDto()
221         .setName(EVENT_NAME)
222         .setProjectUuid(projectUuid)
223         .setLanguage(language)
224         .setPayload(serializeIssueToPushEvent(event));
225       dbClient.pushEventDao().insert(dbSession, eventDto);
226       dbSession.commit();
227     }
228   }
229
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();
235
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());
240       }
241     }
242     return Optional.empty();
243   }
244
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);
248
249       List<ProjectDto> defaultAssociatedProjects = getDefaultAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(true), language);
250       List<ProjectDto> manuallyAssociatedProjects = getManuallyAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(false));
251
252       return Stream
253         .concat(manuallyAssociatedProjects.stream(), defaultAssociatedProjects.stream())
254         .collect(Collectors.toMap(ProjectDto::getKey, ProjectDto::getUuid));
255     }
256   }
257
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);
261
262     return profiles
263       .stream()
264       .collect(Collectors.partitioningBy(isDefaultQualityProfile));
265   }
266
267   private List<ProjectDto> getDefaultAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> defaultProfiles, String language) {
268     if (defaultProfiles.isEmpty()) {
269       return emptyList();
270     }
271
272     return getDefaultQualityProfileAssociatedProjects(dbSession, language);
273   }
274
275   private List<ProjectDto> getDefaultQualityProfileAssociatedProjects(DbSession dbSession, String language) {
276     Set<String> associatedProjectUuids = new HashSet<>();
277
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());
284       }
285     }
286
287     return dbClient.projectDao().selectByUuids(dbSession, associatedProjectUuids);
288   }
289
290   private static boolean distributionContainsLanguage(String distribution, String language) {
291     return distribution.startsWith(language + "=") || distribution.contains(";" + language + "=");
292   }
293
294   private List<ProjectDto> getManuallyAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> profiles) {
295     return profiles
296       .stream()
297       .map(profile -> getQualityProfileAssociatedProjects(dbSession, profile))
298       .flatMap(Collection::stream)
299       .toList();
300   }
301
302   private List<ProjectDto> getQualityProfileAssociatedProjects(DbSession dbSession, QProfileDto profile) {
303     Set<String> projectUuids = getQualityProfileAssociatedProjectUuids(dbSession, profile);
304     return dbClient.projectDao().selectByUuids(dbSession, projectUuids);
305   }
306
307   private Set<String> getQualityProfileAssociatedProjectUuids(DbSession dbSession, QProfileDto profile) {
308     List<ProjectQprofileAssociationDto> associations = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profile, null);
309
310     return associations
311       .stream()
312       .map(ProjectQprofileAssociationDto::getProjectUuid)
313       .collect(Collectors.toSet());
314   }
315
316
317
318
319   private static byte[] serializeIssueToPushEvent(RuleSetChangedEvent event) {
320     return GSON.toJson(event).getBytes(UTF_8);
321   }
322 }