3 * Copyright (C) 2009-2021 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;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.Multimap;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.LinkedHashMap;
29 import java.util.List;
31 import java.util.Optional;
33 import java.util.stream.Collectors;
34 import javax.annotation.Nullable;
35 import org.sonar.api.profiles.RulesProfile;
36 import org.sonar.api.resources.Language;
37 import org.sonar.api.resources.Languages;
38 import org.sonar.api.rule.RuleKey;
39 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
40 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
41 import org.sonar.api.utils.log.Logger;
42 import org.sonar.api.utils.log.Loggers;
43 import org.sonar.api.utils.log.Profiler;
44 import org.sonar.core.util.stream.MoreCollectors;
45 import org.sonar.db.DbClient;
46 import org.sonar.db.DbSession;
47 import org.sonar.db.rule.DeprecatedRuleKeyDto;
48 import org.sonar.db.rule.RuleDefinitionDto;
50 import static com.google.common.base.Preconditions.checkState;
52 public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository {
53 private static final Logger LOGGER = Loggers.get(BuiltInQProfileRepositoryImpl.class);
54 private static final String DEFAULT_PROFILE_NAME = "Sonar way";
56 private final DbClient dbClient;
57 private final Languages languages;
58 private final List<BuiltInQualityProfilesDefinition> definitions;
59 private List<BuiltInQProfile> qProfiles;
62 * Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all
64 public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages) {
65 this(dbClient, languages, new BuiltInQualityProfilesDefinition[0]);
68 public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages, BuiltInQualityProfilesDefinition... definitions) {
69 this.dbClient = dbClient;
70 this.languages = languages;
71 this.definitions = ImmutableList.copyOf(definitions);
75 public void initialize() {
76 checkState(qProfiles == null, "initialize must be called only once");
78 Profiler profiler = Profiler.create(LOGGER).startInfo("Load quality profiles");
79 BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
80 for (BuiltInQualityProfilesDefinition definition : definitions) {
81 definition.define(context);
83 Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage = validateAndClean(context);
84 this.qProfiles = toFlatList(rulesProfilesByLanguage);
85 ensureAllLanguagesHaveAtLeastOneBuiltInQP();
90 public List<BuiltInQProfile> get() {
91 checkState(qProfiles != null, "initialize must be called first");
96 private void ensureAllLanguagesHaveAtLeastOneBuiltInQP() {
97 Set<String> languagesWithBuiltInQProfiles = qProfiles.stream().map(BuiltInQProfile::getLanguage).collect(Collectors.toSet());
98 Set<String> languagesWithoutBuiltInQProfiles = Arrays.stream(languages.all())
99 .map(Language::getKey)
100 .filter(key -> !languagesWithBuiltInQProfiles.contains(key))
101 .collect(Collectors.toSet());
103 checkState(languagesWithoutBuiltInQProfiles.isEmpty(), "The following languages have no built-in quality profiles: %s",
104 String.join("", languagesWithoutBuiltInQProfiles));
107 private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) {
108 Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = context.profilesByLanguageAndName();
109 profilesByLanguageAndName.entrySet()
111 String language = entry.getKey();
112 if (languages.get(language) == null) {
113 LOGGER.info("Language {} is not installed, related quality profiles are ignored", language);
119 return profilesByLanguageAndName;
122 private List<BuiltInQProfile> toFlatList(Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage) {
123 if (rulesProfilesByLanguage.isEmpty()) {
124 return Collections.emptyList();
126 Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = loadRuleDefinitionsByRuleKey();
127 Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = rulesProfilesByLanguage
130 .collect(MoreCollectors.uniqueIndex(
132 rulesProfilesByLanguageAndName -> toQualityProfileBuilders(rulesProfilesByLanguageAndName, rulesByRuleKey)));
133 return buildersByLanguage
136 .filter(BuiltInQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
137 .map(entry -> toQualityProfiles(entry.getValue()))
138 .flatMap(Collection::stream)
139 .collect(MoreCollectors.toList());
142 private Map<RuleKey, RuleDefinitionDto> loadRuleDefinitionsByRuleKey() {
143 try (DbSession dbSession = dbClient.openSession(false)) {
144 List<RuleDefinitionDto> ruleDefinitions = dbClient.ruleDao().selectAllDefinitions(dbSession);
145 Multimap<String, DeprecatedRuleKeyDto> deprecatedRuleKeysByRuleId = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
146 .collect(MoreCollectors.index(DeprecatedRuleKeyDto::getRuleUuid));
147 Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = new HashMap<>();
148 for (RuleDefinitionDto ruleDefinition : ruleDefinitions) {
149 rulesByRuleKey.put(ruleDefinition.getKey(), ruleDefinition);
150 deprecatedRuleKeysByRuleId.get(ruleDefinition.getUuid()).forEach(t -> rulesByRuleKey.put(RuleKey.of(t.getOldRepositoryKey(), t.getOldRuleKey()), ruleDefinition));
152 return rulesByRuleKey;
157 * Creates {@link BuiltInQProfile.Builder} for each unique quality profile name for a given language.
158 * Builders will have the following properties populated:
160 * <li>{@link BuiltInQProfile.Builder#language language}: key of the method's parameter</li>
161 * <li>{@link BuiltInQProfile.Builder#name name}: {@link RulesProfile#getName()}</li>
162 * <li>{@link BuiltInQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile
163 * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li>
164 * <li>{@link BuiltInQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all
165 * RulesProfile with a given name</li>
168 private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguageAndName,
169 Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
170 String language = rulesProfilesByLanguageAndName.getKey();
171 // use a LinkedHashMap to keep order of insertion of RulesProfiles
172 Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
173 for (BuiltInQualityProfile builtInProfile : rulesProfilesByLanguageAndName.getValue().values()) {
174 qualityProfileBuildersByName.compute(
175 builtInProfile.name(),
176 (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, builtInProfile, rulesByRuleKey));
178 return ImmutableList.copyOf(qualityProfileBuildersByName.values());
182 * Fails if more than one {@link BuiltInQProfile.Builder#declaredDefault} is {@code true}, otherwise returns {@code true}.
184 private static boolean ensureAtMostOneDeclaredDefault(Map.Entry<String, List<BuiltInQProfile.Builder>> entry) {
185 Set<String> declaredDefaultProfileNames = entry.getValue().stream()
186 .filter(BuiltInQProfile.Builder::isDeclaredDefault)
187 .map(BuiltInQProfile.Builder::getName)
188 .collect(MoreCollectors.toSet());
189 checkState(declaredDefaultProfileNames.size() <= 1, "Several Quality profiles are flagged as default for the language %s: %s", entry.getKey(), declaredDefaultProfileNames);
193 private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, BuiltInQualityProfile builtInProfile,
194 Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
195 BuiltInQProfile.Builder builder = createOrReuseBuilder(existingBuilder, language, builtInProfile);
196 builder.setDeclaredDefault(builtInProfile.isDefault());
197 builtInProfile.rules().forEach(builtInActiveRule -> {
198 RuleKey ruleKey = RuleKey.of(builtInActiveRule.repoKey(), builtInActiveRule.ruleKey());
199 RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey);
200 checkState(ruleDefinition != null, "Rule with key '%s' not found", ruleKey);
201 builder.addRule(new BuiltInQProfile.ActiveRule(ruleDefinition.getUuid(), ruleDefinition.getKey(),
202 builtInActiveRule.overriddenSeverity(), builtInActiveRule.overriddenParams()));
207 private static BuiltInQProfile.Builder createOrReuseBuilder(@Nullable BuiltInQProfile.Builder existingBuilder, String language, BuiltInQualityProfile builtInProfile) {
208 if (existingBuilder == null) {
209 return new BuiltInQProfile.Builder()
210 .setLanguage(language)
211 .setName(builtInProfile.name());
213 return existingBuilder;
216 private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) {
217 if (builders.stream().noneMatch(BuiltInQProfile.Builder::isDeclaredDefault)) {
218 Optional<BuiltInQProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst();
219 if (sonarWayProfile.isPresent()) {
220 sonarWayProfile.get().setComputedDefault(true);
222 builders.iterator().next().setComputedDefault(true);
225 return builders.stream()
226 .map(BuiltInQProfile.Builder::build)
227 .collect(MoreCollectors.toList(builders.size()));