3 * Copyright (C) 2009-2022 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.qualityprofile.builtin;
22 import com.google.common.collect.ArrayListMultimap;
23 import com.google.common.collect.ListMultimap;
24 import java.util.Collection;
25 import java.util.HashMap;
27 import java.util.Objects;
28 import java.util.stream.Collectors;
29 import javax.annotation.CheckForNull;
30 import org.sonar.api.rule.RuleKey;
31 import org.sonar.db.qualityprofile.ActiveRuleDto;
32 import org.sonar.db.qualityprofile.ActiveRuleKey;
33 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
34 import org.sonar.db.qualityprofile.QProfileDto;
35 import org.sonar.db.qualityprofile.RulesProfileDto;
36 import org.sonar.db.rule.RuleDefinitionDto;
37 import org.sonar.db.rule.RuleParamDto;
38 import org.sonar.server.qualityprofile.RuleActivation;
40 import static com.google.common.base.Preconditions.checkArgument;
41 import static com.google.common.base.Preconditions.checkState;
42 import static java.util.Objects.requireNonNull;
43 import static org.sonar.core.util.stream.MoreCollectors.index;
44 import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
45 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
46 import static org.sonar.server.exceptions.BadRequestException.checkRequest;
49 * Cache of the data required to activate/deactivate
50 * multiple rules on a Quality profile, including
51 * the rule definitions, the rule parameters, the tree
52 * of profiles hierarchy and its related active rules.
54 public class RuleActivationContext {
56 private final long date;
58 // The profile that is initially targeted by the operation
59 private final RulesProfileDto baseRulesProfile;
61 private final Map<String, QProfileDto> profilesByUuid = new HashMap<>();
62 private final ListMultimap<String, QProfileDto> profilesByParentUuid = ArrayListMultimap.create();
64 // The rules/active rules involved in the group of activations/de-activations
65 private final Map<String, RuleWrapper> rulesByUuid = new HashMap<>();
66 private final Map<ActiveRuleKey, ActiveRuleWrapper> activeRulesByKey = new HashMap<>();
68 // Cursors used to move in the rules and in the tree of profiles.
70 private RulesProfileDto currentRulesProfile;
71 // Cardinality is zero-to-many when cursor is on a built-in rules profile,
72 // otherwise it's always one, and only one (cursor on descendants or on non-built-in base profile).
73 private Collection<QProfileDto> currentProfiles;
74 private RuleWrapper currentRule;
75 private ActiveRuleWrapper currentActiveRule;
76 private ActiveRuleWrapper currentParentActiveRule;
78 private boolean descendantsLoaded = false;
79 private final DescendantProfilesSupplier descendantProfilesSupplier;
81 private RuleActivationContext(Builder builder) {
82 this.date = builder.date;
83 this.descendantProfilesSupplier = builder.descendantProfilesSupplier;
85 ListMultimap<String, RuleParamDto> paramsByRuleId = builder.ruleParams.stream().collect(index(RuleParamDto::getRuleUuid));
86 for (RuleDefinitionDto rule : builder.rules) {
87 RuleWrapper wrapper = new RuleWrapper(rule, paramsByRuleId.get(rule.getUuid()));
88 rulesByUuid.put(rule.getUuid(), wrapper);
91 this.baseRulesProfile = builder.baseRulesProfile;
92 register(builder.profiles);
93 register(builder.activeRules, builder.activeRuleParams);
96 private void register(Collection<QProfileDto> profiles) {
97 for (QProfileDto profile : profiles) {
98 profilesByUuid.put(profile.getKee(), profile);
99 if (profile.getParentKee() != null) {
100 profilesByParentUuid.put(profile.getParentKee(), profile);
105 private void register(Collection<ActiveRuleDto> activeRules, Collection<ActiveRuleParamDto> activeRuleParams) {
106 ListMultimap<String, ActiveRuleParamDto> paramsByActiveRuleUuid = activeRuleParams.stream().collect(index(ActiveRuleParamDto::getActiveRuleUuid));
107 for (ActiveRuleDto activeRule : activeRules) {
108 ActiveRuleWrapper wrapper = new ActiveRuleWrapper(activeRule, paramsByActiveRuleUuid.get(activeRule.getUuid()));
109 this.activeRulesByKey.put(activeRule.getKey(), wrapper);
118 * The rule currently selected.
120 public RuleWrapper getRule() {
121 checkState(currentRule != null, "Rule has not been set yet");
126 String getRequestedParamValue(RuleActivation request, String key) {
127 if (currentRule.rule.isCustomRule()) {
130 return request.getParameter(key);
133 boolean hasRequestedParamValue(RuleActivation request, String key) {
134 return request.hasParameter(key);
138 * The rules profile being selected.
140 RulesProfileDto getRulesProfile() {
141 checkState(currentRulesProfile != null, "Rule profile has not been set yet");
142 return currentRulesProfile;
146 * The active rule related to the selected profile and rule.
147 * @return null if the selected rule is not activated on the selected profile.
148 * @see #getRulesProfile()
152 ActiveRuleWrapper getActiveRule() {
153 return currentActiveRule;
157 * The active rule related to the rule and the parent of the selected profile.
158 * @return null if the selected rule is not activated on the parent profile.
162 ActiveRuleWrapper getParentActiveRule() {
163 return currentParentActiveRule;
167 * Whether the profile cursor is on the base profile or not.
169 boolean isCascading() {
170 return currentRulesProfile != null && !currentRulesProfile.getUuid().equals(baseRulesProfile.getUuid());
174 * The profiles being selected. Can be zero or many if {@link #getRulesProfile()} is built-in.
175 * Else the collection always contains a single profile.
177 Collection<QProfileDto> getProfiles() {
178 checkState(currentProfiles != null, "Profiles have not been set yet");
179 return currentProfiles;
183 * The children of {@link #getProfiles()}
185 Collection<QProfileDto> getChildProfiles() {
187 return getProfiles().stream()
188 .flatMap(p -> profilesByParentUuid.get(p.getKee()).stream())
189 .collect(Collectors.toList());
192 private void loadDescendants() {
193 if (descendantsLoaded) {
196 Collection<QProfileDto> baseProfiles = profilesByUuid.values().stream()
197 .filter(p -> p.getRulesProfileUuid().equals(baseRulesProfile.getUuid()))
198 .collect(toArrayList(profilesByUuid.size()));
199 DescendantProfilesSupplier.Result result = descendantProfilesSupplier.get(baseProfiles, rulesByUuid.keySet());
200 register(result.getProfiles());
201 register(result.getActiveRules(), result.getActiveRuleParams());
202 descendantsLoaded = true;
206 * Move the cursor to the given rule and back to the base profile.
208 public void reset(String ruleUuid) {
209 doSwitch(this.baseRulesProfile, ruleUuid);
213 * Moves cursor to a child profile
215 void selectChild(QProfileDto to) {
216 checkState(!to.isBuiltIn());
217 QProfileDto qp = requireNonNull(this.profilesByUuid.get(to.getKee()), () -> "No profile with uuid " + to.getKee());
219 RulesProfileDto ruleProfile = RulesProfileDto.from(qp);
220 doSwitch(ruleProfile, getRule().get().getUuid());
223 private void doSwitch(RulesProfileDto ruleProfile, String ruleUuid) {
224 this.currentRule = rulesByUuid.get(ruleUuid);
225 checkRequest(this.currentRule != null, "Rule with UUID %s not found", ruleUuid);
226 RuleKey ruleKey = currentRule.get().getKey();
228 this.currentRulesProfile = ruleProfile;
229 this.currentProfiles = profilesByUuid.values().stream()
230 .filter(p -> p.getRulesProfileUuid().equals(ruleProfile.getUuid()))
231 .collect(Collectors.toList());
232 this.currentActiveRule = this.activeRulesByKey.get(ActiveRuleKey.of(ruleProfile, ruleKey));
233 this.currentParentActiveRule = this.currentProfiles.stream()
234 .map(QProfileDto::getParentKee)
235 .filter(Objects::nonNull)
236 .map(profilesByUuid::get)
237 .filter(Objects::nonNull)
239 .map(profile -> activeRulesByKey.get(ActiveRuleKey.of(profile, ruleKey)))
243 static final class Builder {
244 private long date = System.currentTimeMillis();
245 private RulesProfileDto baseRulesProfile;
246 private Collection<RuleDefinitionDto> rules;
247 private Collection<RuleParamDto> ruleParams;
248 private Collection<QProfileDto> profiles;
249 private Collection<ActiveRuleDto> activeRules;
250 private Collection<ActiveRuleParamDto> activeRuleParams;
251 private DescendantProfilesSupplier descendantProfilesSupplier;
253 Builder setDate(long l) {
258 Builder setBaseProfile(RulesProfileDto p) {
259 this.baseRulesProfile = p;
263 Builder setRules(Collection<RuleDefinitionDto> rules) {
268 Builder setRuleParams(Collection<RuleParamDto> ruleParams) {
269 this.ruleParams = ruleParams;
274 * All the profiles involved in the activation workflow, including the
275 * parent profile, even if it's not updated.
277 Builder setProfiles(Collection<QProfileDto> profiles) {
278 this.profiles = profiles;
282 Builder setActiveRules(Collection<ActiveRuleDto> activeRules) {
283 this.activeRules = activeRules;
287 Builder setActiveRuleParams(Collection<ActiveRuleParamDto> activeRuleParams) {
288 this.activeRuleParams = activeRuleParams;
292 Builder setDescendantProfilesSupplier(DescendantProfilesSupplier d) {
293 this.descendantProfilesSupplier = d;
297 RuleActivationContext build() {
298 checkArgument(date > 0, "date is not set");
299 requireNonNull(baseRulesProfile, "baseRulesProfile is null");
300 requireNonNull(rules, "rules is null");
301 requireNonNull(ruleParams, "ruleParams is null");
302 requireNonNull(profiles, "profiles is null");
303 requireNonNull(activeRules, "activeRules is null");
304 requireNonNull(activeRuleParams, "activeRuleParams is null");
305 requireNonNull(descendantProfilesSupplier, "descendantProfilesSupplier is null");
306 return new RuleActivationContext(this);
310 public static final class RuleWrapper {
311 private final RuleDefinitionDto rule;
312 private final Map<String, RuleParamDto> paramsByKey;
314 private RuleWrapper(RuleDefinitionDto rule, Collection<RuleParamDto> params) {
316 this.paramsByKey = params.stream().collect(uniqueIndex(RuleParamDto::getName));
319 public RuleDefinitionDto get() {
323 Collection<RuleParamDto> getParams() {
324 return paramsByKey.values();
328 RuleParamDto getParam(String key) {
329 return paramsByKey.get(key);
333 String getParamDefaultValue(String key) {
334 RuleParamDto param = getParam(key);
335 return param != null ? param.getDefaultValue() : null;
339 static final class ActiveRuleWrapper {
340 private final ActiveRuleDto activeRule;
341 private final Map<String, ActiveRuleParamDto> paramsByKey;
343 private ActiveRuleWrapper(ActiveRuleDto activeRule, Collection<ActiveRuleParamDto> params) {
344 this.activeRule = activeRule;
345 this.paramsByKey = params.stream().collect(uniqueIndex(ActiveRuleParamDto::getKey));
348 ActiveRuleDto get() {
352 Collection<ActiveRuleParamDto> getParams() {
353 return paramsByKey.values();
357 ActiveRuleParamDto getParam(String key) {
358 return paramsByKey.get(key);
362 String getParamValue(String key) {
363 ActiveRuleParamDto param = paramsByKey.get(key);
364 return param != null ? param.getValue() : null;