]> source.dussan.org Git - sonarqube.git/blob
3f209ca71724ceda0f976672a417b143daa18009
[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.qualityprofile.builtin;
21
22 import com.google.common.collect.ArrayListMultimap;
23 import com.google.common.collect.ListMultimap;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Map;
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.RuleDto;
37 import org.sonar.db.rule.RuleParamDto;
38 import org.sonar.server.qualityprofile.RuleActivation;
39
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;
47
48 /**
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.
53  */
54 public class RuleActivationContext {
55
56   private final long date;
57
58   // The profile that is initially targeted by the operation
59   private final RulesProfileDto baseRulesProfile;
60
61   private final Map<String, QProfileDto> profilesByUuid = new HashMap<>();
62   private final ListMultimap<String, QProfileDto> profilesByParentUuid = ArrayListMultimap.create();
63
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<>();
67
68   // Cursors used to move in the rules and in the tree of profiles.
69
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;
77
78   private boolean descendantsLoaded = false;
79   private final DescendantProfilesSupplier descendantProfilesSupplier;
80
81   private RuleActivationContext(Builder builder) {
82     this.date = builder.date;
83     this.descendantProfilesSupplier = builder.descendantProfilesSupplier;
84
85     ListMultimap<String, RuleParamDto> paramsByRuleId = builder.ruleParams.stream().collect(index(RuleParamDto::getRuleUuid));
86     for (RuleDto rule : builder.rules) {
87       RuleWrapper wrapper = new RuleWrapper(rule, paramsByRuleId.get(rule.getUuid()));
88       rulesByUuid.put(rule.getUuid(), wrapper);
89     }
90
91     this.baseRulesProfile = builder.baseRulesProfile;
92     register(builder.profiles);
93     register(builder.activeRules, builder.activeRuleParams);
94   }
95
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);
101       }
102     }
103   }
104
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);
110     }
111   }
112
113   long getDate() {
114     return date;
115   }
116
117   /**
118    * The rule currently selected.
119    */
120   public RuleWrapper getRule() {
121     checkState(currentRule != null, "Rule has not been set yet");
122     return currentRule;
123   }
124
125   @CheckForNull
126   String getRequestedParamValue(RuleActivation request, String key) {
127     if (currentRule.rule.isCustomRule()) {
128       return null;
129     }
130     return request.getParameter(key);
131   }
132
133   boolean hasRequestedParamValue(RuleActivation request, String key) {
134     return request.hasParameter(key);
135   }
136
137   /**
138    * The rules profile being selected.
139    */
140   RulesProfileDto getRulesProfile() {
141     checkState(currentRulesProfile != null, "Rule profile has not been set yet");
142     return currentRulesProfile;
143   }
144
145   /**
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()
149    * @see #getRule()
150    */
151   @CheckForNull
152   ActiveRuleWrapper getActiveRule() {
153     return currentActiveRule;
154   }
155
156   /**
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.
159    * @see #getRule()
160    */
161   @CheckForNull
162   ActiveRuleWrapper getParentActiveRule() {
163     return currentParentActiveRule;
164   }
165
166   /**
167    * Whether the profile cursor is on the base profile or not.
168    */
169   boolean isCascading() {
170     return currentRulesProfile != null && !currentRulesProfile.getUuid().equals(baseRulesProfile.getUuid());
171   }
172
173   /**
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.
176    */
177   Collection<QProfileDto> getProfiles() {
178     checkState(currentProfiles != null, "Profiles have not been set yet");
179     return currentProfiles;
180   }
181
182   /**
183    * The children of {@link #getProfiles()}
184    */
185   Collection<QProfileDto> getChildProfiles() {
186     loadDescendants();
187     return getProfiles().stream()
188       .flatMap(p -> profilesByParentUuid.get(p.getKee()).stream())
189       .collect(Collectors.toList());
190   }
191
192   private void loadDescendants() {
193     if (descendantsLoaded) {
194       return;
195     }
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;
203   }
204
205   /**
206    * Move the cursor to the given rule and back to the base profile.
207    */
208   public void reset(String ruleUuid) {
209     doSwitch(this.baseRulesProfile, ruleUuid);
210   }
211
212   /**
213    * Moves cursor to a child profile
214    */
215   void selectChild(QProfileDto to) {
216     checkState(!to.isBuiltIn());
217     QProfileDto qp = requireNonNull(this.profilesByUuid.get(to.getKee()), () -> "No profile with uuid " + to.getKee());
218
219     RulesProfileDto ruleProfile = RulesProfileDto.from(qp);
220     doSwitch(ruleProfile, getRule().get().getUuid());
221   }
222
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();
227
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)
238       .findFirst()
239       .map(profile -> activeRulesByKey.get(ActiveRuleKey.of(profile, ruleKey)))
240       .orElse(null);
241   }
242
243   static final class Builder {
244     private long date = System.currentTimeMillis();
245     private RulesProfileDto baseRulesProfile;
246     private Collection<RuleDto> 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;
252
253     Builder setDate(long l) {
254       this.date = l;
255       return this;
256     }
257
258     Builder setBaseProfile(RulesProfileDto p) {
259       this.baseRulesProfile = p;
260       return this;
261     }
262
263     Builder setRules(Collection<RuleDto> rules) {
264       this.rules = rules;
265       return this;
266     }
267
268     Builder setRuleParams(Collection<RuleParamDto> ruleParams) {
269       this.ruleParams = ruleParams;
270       return this;
271     }
272
273     /**
274      * All the profiles involved in the activation workflow, including the
275      * parent profile, even if it's not updated.
276      */
277     Builder setProfiles(Collection<QProfileDto> profiles) {
278       this.profiles = profiles;
279       return this;
280     }
281
282     Builder setActiveRules(Collection<ActiveRuleDto> activeRules) {
283       this.activeRules = activeRules;
284       return this;
285     }
286
287     Builder setActiveRuleParams(Collection<ActiveRuleParamDto> activeRuleParams) {
288       this.activeRuleParams = activeRuleParams;
289       return this;
290     }
291
292     Builder setDescendantProfilesSupplier(DescendantProfilesSupplier d) {
293       this.descendantProfilesSupplier = d;
294       return this;
295     }
296
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);
307     }
308   }
309
310   public static final class RuleWrapper {
311     private final RuleDto rule;
312     private final Map<String, RuleParamDto> paramsByKey;
313
314     private RuleWrapper(RuleDto rule, Collection<RuleParamDto> params) {
315       this.rule = rule;
316       this.paramsByKey = params.stream().collect(uniqueIndex(RuleParamDto::getName));
317     }
318
319     public RuleDto get() {
320       return rule;
321     }
322
323     Collection<RuleParamDto> getParams() {
324       return paramsByKey.values();
325     }
326
327     @CheckForNull
328     RuleParamDto getParam(String key) {
329       return paramsByKey.get(key);
330     }
331
332     @CheckForNull
333     String getParamDefaultValue(String key) {
334       RuleParamDto param = getParam(key);
335       return param != null ? param.getDefaultValue() : null;
336     }
337   }
338
339   static final class ActiveRuleWrapper {
340     private final ActiveRuleDto activeRule;
341     private final Map<String, ActiveRuleParamDto> paramsByKey;
342
343     private ActiveRuleWrapper(ActiveRuleDto activeRule, Collection<ActiveRuleParamDto> params) {
344       this.activeRule = activeRule;
345       this.paramsByKey = params.stream().collect(uniqueIndex(ActiveRuleParamDto::getKey));
346     }
347
348     ActiveRuleDto get() {
349       return activeRule;
350     }
351
352     Collection<ActiveRuleParamDto> getParams() {
353       return paramsByKey.values();
354     }
355
356     @CheckForNull
357     ActiveRuleParamDto getParam(String key) {
358       return paramsByKey.get(key);
359     }
360
361     @CheckForNull
362     String getParamValue(String key) {
363       ActiveRuleParamDto param = paramsByKey.get(key);
364       return param != null ? param.getValue() : null;
365     }
366   }
367 }