]> source.dussan.org Git - sonarqube.git/blob
321c69fb9a8129494a758fa39f8cd863320d8fec
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2017 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
22 import com.google.common.collect.ArrayListMultimap;
23 import com.google.common.collect.ImmutableList;
24 import com.google.common.collect.ListMultimap;
25 import com.google.common.collect.Multimaps;
26 import java.util.Collection;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.Set;
33 import javax.annotation.Nullable;
34 import org.sonar.api.profiles.ProfileDefinition;
35 import org.sonar.api.profiles.RulesProfile;
36 import org.sonar.api.resources.Languages;
37 import org.sonar.api.utils.ValidationMessages;
38 import org.sonar.api.utils.log.Logger;
39 import org.sonar.api.utils.log.Loggers;
40 import org.sonar.api.utils.log.Profiler;
41 import org.sonar.core.util.stream.MoreCollectors;
42
43 import static com.google.common.base.Preconditions.checkArgument;
44 import static com.google.common.base.Preconditions.checkState;
45 import static java.lang.String.format;
46 import static org.apache.commons.lang.StringUtils.isNotEmpty;
47 import static org.apache.commons.lang.StringUtils.lowerCase;
48
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
53   private final Languages languages;
54   private final List<ProfileDefinition> definitions;
55   private List<BuiltInQProfile> qProfiles;
56
57   /**
58    * Requires for pico container when no {@link ProfileDefinition} is defined at all
59    */
60   public BuiltInQProfileRepositoryImpl(Languages languages) {
61     this(languages, new ProfileDefinition[0]);
62   }
63
64   public BuiltInQProfileRepositoryImpl(Languages languages, ProfileDefinition... definitions) {
65     this.languages = languages;
66     this.definitions = ImmutableList.copyOf(definitions);
67   }
68
69   @Override
70   public void initialize() {
71     checkState(qProfiles == null, "initialize must be called only once");
72
73     Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Load quality profiles");
74     ListMultimap<String, RulesProfile> rulesProfilesByLanguage = buildRulesProfilesByLanguage();
75     validateAndClean(rulesProfilesByLanguage);
76     this.qProfiles = toFlatList(rulesProfilesByLanguage);
77     profiler.stopDebug();
78   }
79
80   @Override
81   public List<BuiltInQProfile> get() {
82     checkState(qProfiles != null, "initialize must be called first");
83
84     return qProfiles;
85   }
86
87   /**
88    * @return profiles by language
89    */
90   private ListMultimap<String, RulesProfile> buildRulesProfilesByLanguage() {
91     ListMultimap<String, RulesProfile> byLang = ArrayListMultimap.create();
92     Profiler profiler = Profiler.create(Loggers.get(getClass()));
93     for (ProfileDefinition definition : definitions) {
94       profiler.start();
95       ValidationMessages validation = ValidationMessages.create();
96       RulesProfile profile = definition.createProfile(validation);
97       validation.log(LOGGER);
98       if (profile == null) {
99         profiler.stopDebug(format("Loaded definition %s that return no profile", definition));
100       } else {
101         if (!validation.hasErrors()) {
102           checkArgument(isNotEmpty(profile.getName()), "Profile created by Definition %s can't have a blank name", definition);
103           byLang.put(lowerCase(profile.getLanguage(), Locale.ENGLISH), profile);
104         }
105         profiler.stopDebug(format("Loaded definition %s for language %s", profile.getName(), profile.getLanguage()));
106       }
107     }
108     return byLang;
109   }
110
111   private void validateAndClean(ListMultimap<String, RulesProfile> byLang) {
112     byLang.asMap().entrySet()
113       .removeIf(entry -> {
114         String language = entry.getKey();
115         if (languages.get(language) == null) {
116           LOGGER.info("Language {} is not installed, related Quality profiles are ignored", language);
117           return true;
118         }
119         Collection<RulesProfile> profiles = entry.getValue();
120         if (profiles.isEmpty()) {
121           LOGGER.warn("No Quality profiles defined for language: {}", language);
122           return true;
123         }
124         return false;
125       });
126   }
127
128   private static List<BuiltInQProfile> toFlatList(ListMultimap<String, RulesProfile> rulesProfilesByLanguage) {
129     Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = Multimaps.asMap(rulesProfilesByLanguage)
130       .entrySet()
131       .stream()
132       .collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, BuiltInQProfileRepositoryImpl::toQualityProfileBuilders));
133     return buildersByLanguage
134       .entrySet()
135       .stream()
136       .filter(BuiltInQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
137       .map(entry -> toQualityProfiles(entry.getValue()))
138       .flatMap(Collection::stream)
139       .collect(MoreCollectors.toList());
140   }
141
142   /**
143    * Creates {@link BuiltInQProfile.Builder} for each unique quality profile name for a given language.
144    * Builders will have the following properties populated:
145    * <ul>
146    *   <li>{@link BuiltInQProfile.Builder#language language}: key of the method's parameter</li>
147    *   <li>{@link BuiltInQProfile.Builder#name name}: {@link RulesProfile#getName()}</li>
148    *   <li>{@link BuiltInQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile
149    *       with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li>
150    *   <li>{@link BuiltInQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all
151    *       RulesProfile with a given name</li>
152    * </ul>
153    */
154   private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, List<RulesProfile>> rulesProfilesByLanguage) {
155     String language = rulesProfilesByLanguage.getKey();
156     // use a LinkedHashMap to keep order of insertion of RulesProfiles
157     Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
158     for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) {
159       qualityProfileBuildersByName.compute(
160         rulesProfile.getName(),
161         (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile));
162     }
163     return ImmutableList.copyOf(qualityProfileBuildersByName.values());
164   }
165
166   /**
167    * Fails if more than one {@link BuiltInQProfile.Builder#declaredDefault} is {@code true}, otherwise returns {@code true}.
168    */
169   private static boolean ensureAtMostOneDeclaredDefault(Map.Entry<String, List<BuiltInQProfile.Builder>> entry) {
170     Set<String> declaredDefaultProfileNames = entry.getValue().stream()
171       .filter(BuiltInQProfile.Builder::isDeclaredDefault)
172       .map(BuiltInQProfile.Builder::getName)
173       .collect(MoreCollectors.toSet());
174     checkState(declaredDefaultProfileNames.size() <= 1, "Several Quality profiles are flagged as default for the language %s: %s", entry.getKey(), declaredDefaultProfileNames);
175     return true;
176   }
177
178   private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, RulesProfile rulesProfile) {
179     BuiltInQProfile.Builder builder = existingBuilder;
180     if (builder == null) {
181       builder = new BuiltInQProfile.Builder()
182         .setLanguage(language)
183         .setName(rulesProfile.getName());
184     }
185     Boolean defaultProfile = rulesProfile.getDefaultProfile();
186     boolean declaredDefault = defaultProfile != null && defaultProfile;
187     return builder
188       // if there is multiple RulesProfiles with the same name, if at least one is declared default,
189       // then QualityProfile is flagged as declared default
190       .setDeclaredDefault(builder.isDeclaredDefault() || declaredDefault)
191       .addRules(rulesProfile.getActiveRules());
192   }
193
194   private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) {
195     if (builders.stream().noneMatch(BuiltInQProfile.Builder::isDeclaredDefault)) {
196       Optional<BuiltInQProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst();
197       if (sonarWayProfile.isPresent()) {
198         sonarWayProfile.get().setComputedDefault(true);
199       } else {
200         builders.iterator().next().setComputedDefault(true);
201       }
202     }
203     return builders.stream()
204       .map(BuiltInQProfile.Builder::build)
205       .collect(MoreCollectors.toList(builders.size()));
206   }
207 }