3 * Copyright (C) 2009-2019 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 java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.LinkedHashMap;
27 import java.util.List;
29 import java.util.Optional;
31 import java.util.stream.Collectors;
32 import javax.annotation.Nullable;
33 import org.sonar.api.profiles.RulesProfile;
34 import org.sonar.api.resources.Language;
35 import org.sonar.api.resources.Languages;
36 import org.sonar.api.rule.RuleKey;
37 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
38 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
39 import org.sonar.api.utils.log.Logger;
40 import org.sonar.api.utils.log.Loggers;
41 import org.sonar.api.utils.log.Profiler;
42 import org.sonar.core.util.stream.MoreCollectors;
43 import org.sonar.db.DbClient;
44 import org.sonar.db.DbSession;
45 import org.sonar.db.rule.RuleDefinitionDto;
47 import static com.google.common.base.Preconditions.checkState;
49 public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository {
50 private static final Logger LOGGER = Loggers.get(BuiltInQProfileRepositoryImpl.class);
51 private static final String DEFAULT_PROFILE_NAME = "Sonar way";
53 private final DbClient dbClient;
54 private final Languages languages;
55 private final List<BuiltInQualityProfilesDefinition> definitions;
56 private List<BuiltInQProfile> qProfiles;
59 * Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all
61 public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages) {
62 this(dbClient, languages, new BuiltInQualityProfilesDefinition[0]);
65 public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages, BuiltInQualityProfilesDefinition... definitions) {
66 this.dbClient = dbClient;
67 this.languages = languages;
68 this.definitions = ImmutableList.copyOf(definitions);
72 public void initialize() {
73 checkState(qProfiles == null, "initialize must be called only once");
75 Profiler profiler = Profiler.create(LOGGER).startInfo("Load quality profiles");
76 BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
77 for (BuiltInQualityProfilesDefinition definition : definitions) {
78 definition.define(context);
80 Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage = validateAndClean(context);
81 this.qProfiles = toFlatList(rulesProfilesByLanguage);
82 ensureAllLanguagesHaveAtLeastOneBuiltInQP();
87 public List<BuiltInQProfile> get() {
88 checkState(qProfiles != null, "initialize must be called first");
93 private void ensureAllLanguagesHaveAtLeastOneBuiltInQP() {
94 Set<String> languagesWithBuiltInQProfiles = qProfiles.stream().map(BuiltInQProfile::getLanguage).collect(Collectors.toSet());
95 Set<String> languagesWithoutBuiltInQProfiles = Arrays.stream(languages.all())
96 .map(Language::getKey)
97 .filter(key -> !languagesWithBuiltInQProfiles.contains(key))
98 .collect(Collectors.toSet());
100 checkState(languagesWithoutBuiltInQProfiles.isEmpty(), "The following languages have no built-in quality profiles: %s",
101 languagesWithoutBuiltInQProfiles.stream().collect(Collectors.joining()));
104 private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) {
105 Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = context.profilesByLanguageAndName();
106 profilesByLanguageAndName.entrySet()
108 String language = entry.getKey();
109 if (languages.get(language) == null) {
110 LOGGER.info("Language {} is not installed, related quality profiles are ignored", language);
116 return profilesByLanguageAndName;
119 private List<BuiltInQProfile> toFlatList(Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage) {
120 if (rulesProfilesByLanguage.isEmpty()) {
121 return Collections.emptyList();
124 try (DbSession dbSession = dbClient.openSession(false)) {
125 Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = dbClient.ruleDao().selectAllDefinitions(dbSession)
127 .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getKey));
128 Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = rulesProfilesByLanguage
131 .collect(MoreCollectors.uniqueIndex(
133 rulesProfilesByLanguageAndName -> toQualityProfileBuilders(rulesProfilesByLanguageAndName, rulesByRuleKey)));
134 return buildersByLanguage
137 .filter(BuiltInQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
138 .map(entry -> toQualityProfiles(entry.getValue()))
139 .flatMap(Collection::stream)
140 .collect(MoreCollectors.toList());
145 * Creates {@link BuiltInQProfile.Builder} for each unique quality profile name for a given language.
146 * Builders will have the following properties populated:
148 * <li>{@link BuiltInQProfile.Builder#language language}: key of the method's parameter</li>
149 * <li>{@link BuiltInQProfile.Builder#name name}: {@link RulesProfile#getName()}</li>
150 * <li>{@link BuiltInQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile
151 * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li>
152 * <li>{@link BuiltInQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all
153 * RulesProfile with a given name</li>
156 private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguageAndName,
157 Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
158 String language = rulesProfilesByLanguageAndName.getKey();
159 // use a LinkedHashMap to keep order of insertion of RulesProfiles
160 Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
161 for (BuiltInQualityProfile builtInProfile : rulesProfilesByLanguageAndName.getValue().values()) {
162 qualityProfileBuildersByName.compute(
163 builtInProfile.name(),
164 (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, builtInProfile, rulesByRuleKey));
166 return ImmutableList.copyOf(qualityProfileBuildersByName.values());
170 * Fails if more than one {@link BuiltInQProfile.Builder#declaredDefault} is {@code true}, otherwise returns {@code true}.
172 private static boolean ensureAtMostOneDeclaredDefault(Map.Entry<String, List<BuiltInQProfile.Builder>> entry) {
173 Set<String> declaredDefaultProfileNames = entry.getValue().stream()
174 .filter(BuiltInQProfile.Builder::isDeclaredDefault)
175 .map(BuiltInQProfile.Builder::getName)
176 .collect(MoreCollectors.toSet());
177 checkState(declaredDefaultProfileNames.size() <= 1, "Several Quality profiles are flagged as default for the language %s: %s", entry.getKey(), declaredDefaultProfileNames);
181 private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, BuiltInQualityProfile builtInProfile,
182 Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
183 BuiltInQProfile.Builder builder = createOrReuseBuilder(existingBuilder, language, builtInProfile);
184 builder.setDeclaredDefault(builtInProfile.isDefault());
185 builtInProfile.rules().forEach(builtInActiveRule -> {
186 RuleKey ruleKey = RuleKey.of(builtInActiveRule.repoKey(), builtInActiveRule.ruleKey());
187 RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey);
188 checkState(ruleDefinition != null, "Rule with key '%s' not found", ruleKey);
189 builder.addRule(builtInActiveRule, ruleDefinition.getId());
194 private static BuiltInQProfile.Builder createOrReuseBuilder(@Nullable BuiltInQProfile.Builder existingBuilder, String language, BuiltInQualityProfile builtInProfile) {
195 if (existingBuilder == null) {
196 return new BuiltInQProfile.Builder()
197 .setLanguage(language)
198 .setName(builtInProfile.name());
200 return existingBuilder;
203 private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) {
204 if (builders.stream().noneMatch(BuiltInQProfile.Builder::isDeclaredDefault)) {
205 Optional<BuiltInQProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst();
206 if (sonarWayProfile.isPresent()) {
207 sonarWayProfile.get().setComputedDefault(true);
209 builders.iterator().next().setComputedDefault(true);
212 return builders.stream()
213 .map(BuiltInQProfile.Builder::build)
214 .collect(MoreCollectors.toList(builders.size()));