]> source.dussan.org Git - sonarqube.git/blob
695fdce7070d59968b50081c56f76e8b7f758933
[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.qualityprofile.builtin;
21
22 import com.google.common.base.Splitter;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31 import java.util.stream.Stream;
32 import javax.annotation.CheckForNull;
33 import javax.annotation.Nullable;
34 import org.apache.commons.lang3.StringUtils;
35 import org.sonar.api.config.Configuration;
36 import org.sonar.api.rule.RuleStatus;
37 import org.sonar.api.server.ServerSide;
38 import org.sonar.api.server.rule.RuleParamType;
39 import org.sonar.api.utils.System2;
40 import org.sonar.core.config.CorePropertyDefinitions;
41 import org.sonar.core.platform.SonarQubeVersion;
42 import org.sonar.db.DbClient;
43 import org.sonar.db.DbSession;
44 import org.sonar.db.qualityprofile.ActiveRuleDao;
45 import org.sonar.db.qualityprofile.ActiveRuleDto;
46 import org.sonar.db.qualityprofile.ActiveRuleKey;
47 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
48 import org.sonar.db.qualityprofile.OrgQProfileDto;
49 import org.sonar.db.qualityprofile.QProfileChangeDto;
50 import org.sonar.db.qualityprofile.QProfileDto;
51 import org.sonar.db.qualityprofile.RulesProfileDto;
52 import org.sonar.db.rule.RuleDto;
53 import org.sonar.db.rule.RuleParamDto;
54 import org.sonar.server.qualityprofile.ActiveRuleChange;
55 import org.sonar.server.qualityprofile.ActiveRuleInheritance;
56 import org.sonar.server.qualityprofile.RuleActivation;
57 import org.sonar.server.qualityprofile.builtin.RuleActivationContext.ActiveRuleWrapper;
58 import org.sonar.server.qualityprofile.builtin.RuleActivationContext.RuleWrapper;
59 import org.sonar.server.user.UserSession;
60 import org.sonar.server.util.TypeValidations;
61
62 import static com.google.common.base.Preconditions.checkArgument;
63 import static java.lang.Boolean.TRUE;
64 import static org.sonar.server.exceptions.BadRequestException.checkRequest;
65
66 /**
67  * Activation and deactivation of rules in Quality profiles
68  */
69 @ServerSide
70 public class RuleActivator {
71
72   private final System2 system2;
73   private final DbClient db;
74   private final TypeValidations typeValidations;
75   private final UserSession userSession;
76   private final Configuration configuration;
77   private final SonarQubeVersion sonarQubeVersion;
78
79   public RuleActivator(System2 system2, DbClient db, TypeValidations typeValidations, UserSession userSession,
80     Configuration configuration, SonarQubeVersion sonarQubeVersion) {
81     this.system2 = system2;
82     this.db = db;
83     this.typeValidations = typeValidations;
84     this.userSession = userSession;
85     this.configuration = configuration;
86     this.sonarQubeVersion = sonarQubeVersion;
87   }
88
89   public List<ActiveRuleChange> activate(DbSession dbSession, Collection<RuleActivation> activations, RuleActivationContext context) {
90     return activations.stream().map(a -> activate(dbSession, a, context))
91       .flatMap(List::stream)
92       .toList();
93   }
94
95   public List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
96     context.reset(activation.getRuleUuid());
97     return doActivateRecursively(dbSession, activation, context);
98   }
99
100   private List<ActiveRuleChange> doActivateRecursively(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
101     RuleDto rule = context.getRule().get();
102     checkRequest(RuleStatus.REMOVED != rule.getStatus(), "Rule was removed: %s", rule.getKey());
103     checkRequest(!rule.isTemplate(), "Rule template can't be activated on a Quality profile: %s", rule.getKey());
104     checkRequest(context.getRulesProfile().getLanguage().equals(rule.getLanguage()),
105       "%s rule %s cannot be activated on %s profile %s", rule.getLanguage(), rule.getKey(), context.getRulesProfile().getLanguage(),
106       context.getRulesProfile().getName());
107     List<ActiveRuleChange> changes = new ArrayList<>();
108     ActiveRuleChange change = null;
109
110     ActiveRuleWrapper activeRule = context.getActiveRule();
111     ActiveRuleKey activeRuleKey = ActiveRuleKey.of(context.getRulesProfile(), rule.getKey());
112     if (activeRule == null) {
113       if (activation.isReset()) {
114         // ignore reset when rule is not activated
115         return changes;
116       }
117       change = handleNewRuleActivation(activation, context, rule, activeRuleKey);
118     } else {
119       // already activated
120
121       // No change if propagating to descendants, but child profile already overrides rule
122       if (!context.isCascading() || !activeRule.get().doesOverride()) {
123         change = new ActiveRuleChange(ActiveRuleChange.Type.UPDATED, activeRuleKey, rule);
124         handleUpdatedRuleActivation(activation, context, change, activeRule);
125
126         if (isSame(change, activeRule) || (context.isCascading() && activeRule.get().getInheritance() != null && !isSameAsParent(change,
127           context))) {
128           // The rule config hasn't changed; or the rule is being propagated but the parent has a different config,
129           // which means the rule was overridden by a profile in the inheritance chain
130           change = null;
131         }
132       }
133     }
134
135     if (change != null) {
136       changes.add(change);
137       persist(change, context, dbSession);
138     }
139
140     if (!changes.isEmpty()) {
141       updateProfileDates(dbSession, context);
142     }
143
144     // get all inherited profiles
145     for (QProfileDto child : context.getChildProfiles()) {
146       context.selectChild(child);
147       changes.addAll(doActivateRecursively(dbSession, activation, context));
148     }
149
150     return changes;
151   }
152
153   private void handleUpdatedRuleActivation(RuleActivation activation, RuleActivationContext context, ActiveRuleChange change,
154     ActiveRuleWrapper activeRule) {
155     if (context.isCascading() && activeRule.get().getInheritance() == null) {
156       // The rule is being propagated, but it was activated directly on this profile before
157       change.setSeverity(activeRule.get().getSeverityString());
158       for (ActiveRuleParamDto activeParam : activeRule.getParams()) {
159         change.setParameter(activeParam.getKey(), activeParam.getValue());
160       }
161       change.setInheritance(isSameAsParent(change, context) ? ActiveRuleInheritance.INHERITED : ActiveRuleInheritance.OVERRIDES);
162     } else {
163       applySeverityAndPrioritizedRuleAndParamToChange(activation, context, change);
164       if (!context.isCascading() && context.getParentActiveRule() != null) {
165         // override rule which is already declared on parents
166         change.setInheritance(isSameAsParent(change, context) ? ActiveRuleInheritance.INHERITED : ActiveRuleInheritance.OVERRIDES);
167       }
168     }
169   }
170
171   private ActiveRuleChange handleNewRuleActivation(RuleActivation activation, RuleActivationContext context, RuleDto rule,
172     ActiveRuleKey activeRuleKey) {
173     ActiveRuleChange change = new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, activeRuleKey, rule);
174     applySeverityAndPrioritizedRuleAndParamToChange(activation, context, change);
175     if (context.isCascading() || context.getParentActiveRule() != null) {
176       change.setInheritance(isSameAsParent(change, context) ? ActiveRuleInheritance.INHERITED : ActiveRuleInheritance.OVERRIDES);
177     }
178     return change;
179   }
180
181   private void updateProfileDates(DbSession dbSession, RuleActivationContext context) {
182     RulesProfileDto ruleProfile = context.getRulesProfile();
183     ruleProfile.setRulesUpdatedAtAsDate(new Date(context.getDate()));
184     db.qualityProfileDao().update(dbSession, ruleProfile);
185
186     if (userSession.isLoggedIn()) {
187       context.getProfiles().forEach(p -> db.qualityProfileDao().update(dbSession,
188         OrgQProfileDto.from(p).setUserUpdatedAt(context.getDate())));
189     }
190   }
191
192   /**
193    * Update severity, prioritizedRule and params
194    */
195   private void applySeverityAndPrioritizedRuleAndParamToChange(RuleActivation request, RuleActivationContext context,
196     ActiveRuleChange change) {
197     RuleWrapper rule = context.getRule();
198     ActiveRuleWrapper activeRule = context.getActiveRule();
199     ActiveRuleWrapper parentActiveRule = context.getParentActiveRule();
200
201     if (request.isReset()) {
202       applySeverityAndPrioritizedRuleAndParamsWhenResetRequested(change, rule, parentActiveRule);
203     } else if (context.getRulesProfile().isBuiltIn()) {
204       applySeverityAndPrioritizedRuleAndParamsWhenBuiltInProfile(request, context, change, rule);
205     } else {
206       applySeverityAndPrioritizedRuleAndParamsWhenNonBuiltInProfile(request, context, change, rule, activeRule, parentActiveRule);
207     }
208   }
209
210   private void applySeverityAndPrioritizedRuleAndParamsWhenResetRequested(ActiveRuleChange change, RuleWrapper rule,
211     @Nullable ActiveRuleWrapper parentActiveRule) {
212     String severity = firstNonNull(
213       parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null,
214       rule.get().getSeverityString());
215     change.setSeverity(severity);
216     change.setPrioritizedRule(parentActiveRule != null && parentActiveRule.get().isPrioritizedRule());
217
218     for (RuleParamDto ruleParamDto : rule.getParams()) {
219       String paramKey = ruleParamDto.getName();
220       // load params from parent profile, else from default values
221       String paramValue = firstNonNull(
222         parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null,
223         rule.getParamDefaultValue(paramKey));
224
225       change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
226     }
227   }
228
229   private void applySeverityAndPrioritizedRuleAndParamsWhenBuiltInProfile(RuleActivation request, RuleActivationContext context,
230     ActiveRuleChange change, RuleWrapper rule) {
231     // for builtin quality profiles, the severity from profile, when null use the default severity of the rule
232     String severity = firstNonNull(request.getSeverity(), rule.get().getSeverityString());
233     change.setSeverity(severity);
234
235     boolean prioritizedRule = TRUE.equals(request.isPrioritizedRule());
236     change.setPrioritizedRule(prioritizedRule);
237
238     for (RuleParamDto ruleParamDto : rule.getParams()) {
239       String paramKey = ruleParamDto.getName();
240       // use the value defined in the profile definition, else the rule default value
241       String paramValue = firstNonNull(
242         context.getRequestedParamValue(request, paramKey),
243         rule.getParamDefaultValue(paramKey));
244       change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
245     }
246   }
247
248   /**
249    * 1. apply requested severity and param
250    * 2. if rule activated and overridden - apply user value
251    * 3. apply parent value
252    * 4. apply defaults
253    */
254   private void applySeverityAndPrioritizedRuleAndParamsWhenNonBuiltInProfile(RuleActivation request, RuleActivationContext context,
255     ActiveRuleChange change,
256     RuleWrapper rule, @Nullable ActiveRuleWrapper activeRule, @Nullable ActiveRuleWrapper parentActiveRule) {
257     String severity = getSeverityForNonBuiltInProfile(request, rule, activeRule, parentActiveRule);
258     boolean prioritizedRule = getPrioritizedRuleForNonBuiltInProfile(request, activeRule, parentActiveRule);
259     change.setSeverity(severity);
260     change.setPrioritizedRule(prioritizedRule);
261
262     for (RuleParamDto ruleParamDto : rule.getParams()) {
263       String paramKey = ruleParamDto.getName();
264       String parentValue = parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null;
265       String paramValue;
266       if (context.hasRequestedParamValue(request, paramKey)) {
267         // If the request contains the parameter then we're using either value from request, or parent value, or default value
268         paramValue = firstNonNull(
269           context.getRequestedParamValue(request, paramKey),
270           parentValue,
271           rule.getParamDefaultValue(paramKey));
272       } else if (activeRule != null) {
273         // If the request doesn't contain the parameter, then we're using either user value from db, or parent value if rule inherited,
274         // or default value
275         paramValue = firstNonNull(
276           activeRule.get().doesOverride() ? activeRule.getParamValue(paramKey) : null,
277           parentValue == null ? activeRule.getParamValue(paramKey) : parentValue,
278           rule.getParamDefaultValue(paramKey));
279       } else {
280         paramValue = firstNonNull(
281           parentValue,
282           rule.getParamDefaultValue(paramKey));
283       }
284       change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
285     }
286   }
287
288   private static String getSeverityForNonBuiltInProfile(RuleActivation request, RuleWrapper rule, @Nullable ActiveRuleWrapper activeRule,
289     @Nullable ActiveRuleWrapper parentActiveRule) {
290     String severity;
291     if (activeRule != null) {
292       ActiveRuleDto activeRuleDto = activeRule.get();
293       // load severity from request, else keep existing one (if overridden), else from parent if rule inherited, else from default
294       severity = firstNonNull(
295         request.getSeverity(),
296         activeRuleDto.doesOverride() ? activeRuleDto.getSeverityString() : null,
297         parentActiveRule != null ? parentActiveRule.get().getSeverityString() : activeRuleDto.getSeverityString(),
298         rule.get().getSeverityString());
299     } else {
300       // load severity from request, else from parent, else from default
301       severity = firstNonNull(
302         request.getSeverity(),
303         parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null,
304         rule.get().getSeverityString());
305     }
306     return severity;
307   }
308
309   private static boolean getPrioritizedRuleForNonBuiltInProfile(RuleActivation request, @Nullable ActiveRuleWrapper activeRule,
310     @Nullable ActiveRuleWrapper parentActiveRule) {
311     boolean prioritizedRule;
312     if (activeRule != null) {
313       ActiveRuleDto activeRuleDto = activeRule.get();
314       // load prioritizedRule from request, else keep existing one (if overridden), else from parent if rule inherited, else 'false'
315       prioritizedRule = firstNonNull(
316         request.isPrioritizedRule(),
317         activeRuleDto.doesOverride() ? activeRuleDto.isPrioritizedRule() : null,
318         parentActiveRule != null && parentActiveRule.get().isPrioritizedRule());
319     } else {
320       // load prioritizedRule from request, else from parent, else 'false'
321       prioritizedRule = firstNonNull(
322         request.isPrioritizedRule(),
323         parentActiveRule != null && parentActiveRule.get().isPrioritizedRule());
324     }
325     return prioritizedRule;
326   }
327
328   private void persist(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
329     ActiveRuleDto activeRule = null;
330     if (change.getType() == ActiveRuleChange.Type.ACTIVATED) {
331       activeRule = doInsert(change, context, dbSession);
332     } else if (change.getType() == ActiveRuleChange.Type.DEACTIVATED) {
333       ActiveRuleDao dao = db.activeRuleDao();
334       activeRule = dao.delete(dbSession, change.getKey()).orElse(null);
335
336     } else if (change.getType() == ActiveRuleChange.Type.UPDATED) {
337       activeRule = doUpdate(change, context, dbSession);
338     }
339     change.setActiveRule(activeRule);
340
341     QProfileChangeDto dto = change.toDto(userSession.getUuid());
342     dto.setSqVersion(sonarQubeVersion.toString());
343
344     db.qProfileChangeDao().insert(dbSession, dto);
345   }
346
347   private ActiveRuleDto doInsert(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
348     ActiveRuleDao dao = db.activeRuleDao();
349     RuleWrapper rule = context.getRule();
350
351     ActiveRuleDto activeRule = new ActiveRuleDto();
352     activeRule.setProfileUuid(context.getRulesProfile().getUuid());
353     activeRule.setRuleUuid(rule.get().getUuid());
354     activeRule.setKey(ActiveRuleKey.of(context.getRulesProfile(), rule.get().getKey()));
355     String severity = change.getSeverity();
356     if (severity != null) {
357       activeRule.setSeverity(severity);
358     }
359     activeRule.setPrioritizedRule(TRUE.equals(change.isPrioritizedRule()));
360     ActiveRuleInheritance inheritance = change.getInheritance();
361     if (inheritance != null) {
362       activeRule.setInheritance(inheritance.name());
363     }
364     activeRule.setUpdatedAt(system2.now());
365     activeRule.setCreatedAt(system2.now());
366     dao.insert(dbSession, activeRule);
367     List<ActiveRuleParamDto> params = new ArrayList<>();
368     for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
369       if (param.getValue() != null) {
370         ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(rule.getParam(param.getKey()));
371         paramDto.setValue(param.getValue());
372         params.add(paramDto);
373         dao.insertParam(dbSession, activeRule, paramDto);
374       }
375     }
376     context.register(activeRule, params);
377     return activeRule;
378   }
379
380   private ActiveRuleDto doUpdate(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
381     ActiveRuleWrapper activeRule = context.getActiveRule();
382     if (activeRule == null) {
383       return null;
384     }
385     ActiveRuleDao dao = db.activeRuleDao();
386     String severity = change.getSeverity();
387     ActiveRuleDto ruleDto = activeRule.get();
388     if (severity != null) {
389       ruleDto.setSeverity(severity);
390     }
391     Boolean prioritizedRule = change.isPrioritizedRule();
392     if (prioritizedRule != null) {
393       ruleDto.setPrioritizedRule(prioritizedRule);
394     }
395     ActiveRuleInheritance inheritance = change.getInheritance();
396     if (inheritance != null) {
397       ruleDto.setInheritance(inheritance.name());
398     }
399     ruleDto.setUpdatedAt(system2.now());
400     dao.update(dbSession, ruleDto);
401
402     for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
403       ActiveRuleParamDto activeRuleParamDto = activeRule.getParam(param.getKey());
404       if (activeRuleParamDto == null) {
405         // did not exist
406         if (param.getValue() != null) {
407           activeRuleParamDto = ActiveRuleParamDto.createFor(context.getRule().getParam(param.getKey()));
408           activeRuleParamDto.setValue(param.getValue());
409           dao.insertParam(dbSession, ruleDto, activeRuleParamDto);
410         }
411       } else {
412         if (param.getValue() != null) {
413           activeRuleParamDto.setValue(param.getValue());
414           dao.updateParam(dbSession, activeRuleParamDto);
415         } else {
416           dao.deleteParam(dbSession, activeRuleParamDto);
417         }
418       }
419     }
420     return ruleDto;
421   }
422
423   public List<ActiveRuleChange> deactivate(DbSession dbSession, RuleActivationContext context, String ruleUuid, boolean force) {
424     context.reset(ruleUuid);
425     return doDeactivateRecursively(dbSession, context, force);
426   }
427
428   private List<ActiveRuleChange> doDeactivateRecursively(DbSession dbSession, RuleActivationContext context, boolean force) {
429     List<ActiveRuleChange> changes = new ArrayList<>();
430     ActiveRuleWrapper activeRule = context.getActiveRule();
431     if (activeRule != null) {
432       checkRequest(force || context.isCascading() || activeRule.get().getInheritance() == null || isAllowDisableInheritedRules(),
433         "Cannot deactivate inherited rule '%s'", context.getRule().get().getKey());
434
435       ActiveRuleChange change = new ActiveRuleChange(ActiveRuleChange.Type.DEACTIVATED, activeRule.get(), context.getRule().get());
436       changes.add(change);
437       persist(change, context, dbSession);
438     }
439
440     // get all inherited profiles (they are not built-in by design)
441     context.getChildProfiles().forEach(child -> {
442       context.selectChild(child);
443       changes.addAll(doDeactivateRecursively(dbSession, context, force));
444     });
445
446     if (!changes.isEmpty()) {
447       updateProfileDates(dbSession, context);
448     }
449
450     return changes;
451   }
452
453   private boolean isAllowDisableInheritedRules() {
454     return configuration.getBoolean(CorePropertyDefinitions.ALLOW_DISABLE_INHERITED_RULES).orElse(true);
455   }
456
457   @CheckForNull
458   private String validateParam(RuleParamDto ruleParam, @Nullable String value) {
459     if (value != null) {
460       RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType());
461       if (ruleParamType.multiple()) {
462         List<String> values = Splitter.on(",").splitToList(value);
463         typeValidations.validate(values, ruleParamType.type(), ruleParamType.values());
464       } else {
465         typeValidations.validate(value, ruleParamType.type(), ruleParamType.values());
466       }
467     }
468     return value;
469   }
470
471   public RuleActivationContext createContextForBuiltInProfile(DbSession dbSession, RulesProfileDto builtInProfile,
472     Collection<String> ruleUuids) {
473     checkArgument(builtInProfile.isBuiltIn(), "Rules profile with UUID %s is not built-in", builtInProfile.getUuid());
474
475     RuleActivationContext.Builder builder = new RuleActivationContext.Builder();
476     builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession));
477
478     // load rules
479     completeWithRules(dbSession, builder, ruleUuids);
480
481     // load org profiles. Their parents are null by nature.
482     List<QProfileDto> profiles = db.qualityProfileDao().selectQProfilesByRuleProfile(dbSession, builtInProfile);
483     builder.setProfiles(profiles);
484     builder.setBaseProfile(builtInProfile);
485
486     // load active rules
487     Collection<String> ruleProfileUuids = Stream
488       .concat(Stream.of(builtInProfile.getUuid()), profiles.stream().map(QProfileDto::getRulesProfileUuid))
489       .collect(Collectors.toSet());
490     completeWithActiveRules(dbSession, builder, ruleUuids, ruleProfileUuids);
491     return builder.build();
492   }
493
494   public RuleActivationContext createContextForUserProfile(DbSession dbSession, QProfileDto profile, Collection<String> ruleUuids) {
495     checkArgument(!profile.isBuiltIn(), "Profile with UUID %s is built-in", profile.getKee());
496     RuleActivationContext.Builder builder = new RuleActivationContext.Builder();
497     builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession));
498
499     // load rules
500     completeWithRules(dbSession, builder, ruleUuids);
501
502     // load profiles
503     List<QProfileDto> profiles = new ArrayList<>();
504     profiles.add(profile);
505     if (profile.getParentKee() != null) {
506       profiles.add(db.qualityProfileDao().selectByUuid(dbSession, profile.getParentKee()));
507     }
508     builder.setProfiles(profiles);
509     builder.setBaseProfile(RulesProfileDto.from(profile));
510
511     // load active rules
512     Collection<String> ruleProfileUuids = profiles.stream()
513       .map(QProfileDto::getRulesProfileUuid)
514       .collect(Collectors.toSet());
515     completeWithActiveRules(dbSession, builder, ruleUuids, ruleProfileUuids);
516
517     return builder.build();
518   }
519
520   DescendantProfilesSupplier createDescendantProfilesSupplier(DbSession dbSession) {
521     return (parents, ruleUuids) -> {
522       Collection<QProfileDto> profiles = db.qualityProfileDao().selectDescendants(dbSession, parents);
523       Set<String> ruleProfileUuids = profiles.stream()
524         .map(QProfileDto::getRulesProfileUuid)
525         .collect(Collectors.toSet());
526       Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleUuids, ruleProfileUuids);
527       List<String> activeRuleUuids = activeRules.stream().map(ActiveRuleDto::getUuid).toList();
528       List<ActiveRuleParamDto> activeRuleParams = db.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids);
529       return new DescendantProfilesSupplier.Result(profiles, activeRules, activeRuleParams);
530     };
531   }
532
533   private void completeWithRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<String> ruleUuids) {
534     List<RuleDto> rules = db.ruleDao().selectByUuids(dbSession, ruleUuids);
535     builder.setRules(rules);
536     builder.setRuleParams(db.ruleDao().selectRuleParamsByRuleUuids(dbSession, ruleUuids));
537   }
538
539   private void completeWithActiveRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<String> ruleUuids,
540     Collection<String> ruleProfileUuids) {
541     Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleUuids, ruleProfileUuids);
542     builder.setActiveRules(activeRules);
543     List<String> activeRuleUuids = activeRules.stream().map(ActiveRuleDto::getUuid).toList();
544     builder.setActiveRuleParams(db.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids));
545   }
546
547   private static boolean isSame(ActiveRuleChange change, ActiveRuleWrapper activeRule) {
548     ActiveRuleInheritance inheritance = change.getInheritance();
549     if (inheritance != null && !inheritance.name().equals(activeRule.get().getInheritance())) {
550       return false;
551     }
552     String severity = change.getSeverity();
553     if (severity != null && !severity.equals(activeRule.get().getSeverityString())) {
554       return false;
555     }
556     Boolean prioritizedRule = change.isPrioritizedRule();
557     if (prioritizedRule != null && prioritizedRule != activeRule.get().isPrioritizedRule()) {
558       return false;
559     }
560     for (Map.Entry<String, String> changeParam : change.getParameters().entrySet()) {
561       String activeParamValue = activeRule.getParamValue(changeParam.getKey());
562       if (changeParam.getValue() == null && activeParamValue != null) {
563         return false;
564       }
565       if (changeParam.getValue() != null && (activeParamValue == null || !StringUtils.equals(changeParam.getValue(), activeParamValue))) {
566         return false;
567       }
568     }
569     return true;
570   }
571
572   private static boolean isSameAsParent(ActiveRuleChange change, RuleActivationContext context) {
573     ActiveRuleWrapper parentActiveRule = context.getParentActiveRule();
574     if (parentActiveRule == null) {
575       return false;
576     }
577     if (!StringUtils.equals(change.getSeverity(), parentActiveRule.get().getSeverityString())) {
578       return false;
579     }
580     if (change.isPrioritizedRule() != null && !Objects.equals(change.isPrioritizedRule(), parentActiveRule.get().isPrioritizedRule())) {
581       return false;
582     }
583     for (Map.Entry<String, String> entry : change.getParameters().entrySet()) {
584       if (entry.getValue() != null && !entry.getValue().equals(parentActiveRule.getParamValue(entry.getKey()))) {
585         return false;
586       }
587     }
588     return true;
589   }
590
591   @CheckForNull
592   private static String firstNonNull(String... strings) {
593     for (String s : strings) {
594       if (s != null) {
595         return s;
596       }
597     }
598     return null;
599   }
600
601   private static boolean firstNonNull(Boolean... booleans) {
602     for (Boolean b : booleans) {
603       if (b != null) {
604         return b;
605       }
606     }
607     return false;
608   }
609 }