]> source.dussan.org Git - sonarqube.git/blob
37ff4e4546346d8d0cdb4732cae61e9c61a6b899
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.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;
54
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;
61
62 @ServerSide
63 public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService {
64   private static final Gson GSON = new GsonBuilder().create();
65   private static final String EVENT_NAME = "RuleSetChanged";
66
67   private final DbClient dbClient;
68
69   public QualityProfileChangeEventServiceImpl(DbClient dbClient) {
70     this.dbClient = dbClient;
71   }
72
73   @Override
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<>();
78
79     if (activatedProfile != null) {
80       activatedRules.addAll(createRuleChanges(activatedProfile));
81     }
82
83     if (deactivatedProfile != null) {
84       deactivatedRules.addAll(getRuleKeys(deactivatedProfile));
85     }
86
87     if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
88       return;
89     }
90
91     String language = activatedProfile != null ? activatedProfile.getLanguage() : deactivatedProfile.getLanguage();
92
93     persistPushEvent(project.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, project.getUuid());
94   }
95
96   private List<RuleChange> createRuleChanges(@NotNull QProfileDto profileDto) {
97     List<RuleChange> ruleChanges = new ArrayList<>();
98
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());
102
103       Map<String, List<ActiveRuleParamDto>> paramsByActiveRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids)
104         .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
105
106       Map<String, String> activeRuleUuidByRuleUuid = activeRuleDtos.stream().collect(Collectors.toMap(ActiveRuleDto::getRuleUuid, ActiveRuleDto::getUuid));
107
108       List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).collect(Collectors.toList());
109       List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
110
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);
116       }
117     }
118     return ruleChanges;
119   }
120
121   private Set<String> getRuleKeys(@NotNull QProfileDto profileDto) {
122     Set<String> ruleKeys = new HashSet<>();
123
124     try (DbSession dbSession = dbClient.openSession(false)) {
125       List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfile(dbSession, profileDto);
126
127       List<String> ruleUuids = activeRuleDtos.stream().map(ActiveRuleDto::getRuleUuid).collect(Collectors.toList());
128       List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, ruleUuids);
129
130       for (RuleDto ruleDto : ruleDtos) {
131         ruleKeys.add(ruleDto.getKey().toString());
132       }
133     }
134     return ruleKeys;
135   }
136
137   @NotNull
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());
143
144     List<ParamChange> paramChanges = new ArrayList<>();
145     for (ActiveRuleParamDto activeRuleParam : activeRuleParamDtos) {
146       paramChanges.add(new ParamChange(activeRuleParam.getKey(), activeRuleParam.getValue()));
147     }
148     ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
149
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());
156       }
157     }
158
159     return ruleChange;
160   }
161
162   public void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language) {
163     if (activeRuleChanges.isEmpty()) {
164       return;
165     }
166
167     Set<RuleChange> activatedRules = new HashSet<>();
168
169     for (ActiveRuleChange arc : activeRuleChanges) {
170       ActiveRuleDto activeRule = arc.getActiveRule();
171       if (activeRule == null) {
172         continue;
173       }
174
175       RuleChange ruleChange = new RuleChange();
176       ruleChange.setKey(activeRule.getRuleKey().toString());
177       ruleChange.setSeverity(arc.getSeverity());
178       ruleChange.setLanguage(language);
179
180       Optional<String> templateKey = templateKey(arc);
181       templateKey.ifPresent(ruleChange::setTemplateKey);
182
183       // params
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()));
187       }
188       ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
189
190       if (ACTIVATED.equals(arc.getType()) || UPDATED.equals(arc.getType())) {
191         activatedRules.add(ruleChange);
192       }
193     }
194
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());
202
203     if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
204       return;
205     }
206
207     Map<String, String> projectsUuidByKey = getProjectsUuidByKey(profiles, language);
208
209     for (Map.Entry<String, String> entry : projectsUuidByKey.entrySet()) {
210       persistPushEvent(entry.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, entry.getValue());
211     }
212   }
213
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]));
216
217     try (DbSession dbSession = dbClient.openSession(false)) {
218       PushEventDto eventDto = new PushEventDto()
219         .setName(EVENT_NAME)
220         .setProjectUuid(projectUuid)
221         .setLanguage(language)
222         .setPayload(serializeIssueToPushEvent(event));
223       dbClient.pushEventDao().insert(dbSession, eventDto);
224       dbSession.commit();
225     }
226   }
227
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();
233
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());
238       }
239     }
240     return Optional.empty();
241   }
242
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);
246
247       List<ProjectDto> defaultAssociatedProjects = getDefaultAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(true), language);
248       List<ProjectDto> manuallyAssociatedProjects = getManuallyAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(false));
249
250       return Stream
251         .concat(manuallyAssociatedProjects.stream(), defaultAssociatedProjects.stream())
252         .collect(Collectors.toMap(ProjectDto::getKey, ProjectDto::getUuid));
253     }
254   }
255
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);
259
260     return profiles
261       .stream()
262       .collect(Collectors.partitioningBy(isDefaultQualityProfile));
263   }
264
265   private List<ProjectDto> getDefaultAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> defaultProfiles, String language) {
266     if (defaultProfiles.isEmpty()) {
267       return emptyList();
268     }
269
270     return getDefaultQualityProfileAssociatedProjects(dbSession, language);
271   }
272
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);
276   }
277
278   private List<ProjectDto> getManuallyAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> profiles) {
279     return profiles
280       .stream()
281       .map(profile -> getQualityProfileAssociatedProjects(dbSession, profile))
282       .flatMap(Collection::stream)
283       .collect(Collectors.toList());
284   }
285
286   private List<ProjectDto> getQualityProfileAssociatedProjects(DbSession dbSession, QProfileDto profile) {
287     Set<String> projectUuids = getQualityProfileAssociatedProjectUuids(dbSession, profile);
288     return dbClient.projectDao().selectByUuids(dbSession, projectUuids);
289   }
290
291   private Set<String> getQualityProfileAssociatedProjectUuids(DbSession dbSession, QProfileDto profile) {
292     List<ProjectQprofileAssociationDto> associations = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profile, null);
293
294     return associations
295       .stream()
296       .map(ProjectQprofileAssociationDto::getProjectUuid)
297       .collect(Collectors.toSet());
298   }
299
300
301
302
303   private static byte[] serializeIssueToPushEvent(RuleSetChangedEvent event) {
304     return GSON.toJson(event).getBytes(UTF_8);
305   }
306 }