You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BuiltInQProfileRepositoryImpl.java 11KB

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