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