001/*
002 * SonarQube
003 * Copyright (C) 2009-2017 SonarSource SA
004 * mailto:info AT sonarsource DOT com
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.server.profile;
021
022import com.google.common.base.Preconditions;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030import javax.annotation.CheckForNull;
031import javax.annotation.Nullable;
032import javax.annotation.concurrent.Immutable;
033import org.sonar.api.ExtensionPoint;
034import org.sonar.api.rule.RuleKey;
035import org.sonar.api.rule.Severity;
036import org.sonar.api.server.ServerSide;
037import org.sonar.api.utils.log.Logger;
038import org.sonar.api.utils.log.Loggers;
039
040import static com.google.common.base.Preconditions.checkArgument;
041import static java.lang.String.format;
042import static java.util.Collections.unmodifiableList;
043import static java.util.Collections.unmodifiableMap;
044import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
045import static org.apache.commons.lang.StringUtils.isNotBlank;
046
047/**
048 * Define built-in quality profiles which are automatically registered during SonarQube startup.
049 * We no more provide any facility to load profiles from XML file or annotated classes, but it should
050 * be straightforward to implement (adapt code of deprecated {@link org.sonar.api.profiles.AnnotationProfileParser} 
051 * or {@link org.sonar.api.profiles.XMLProfileParser} for example).
052 *
053 * @since 6.6
054 */
055@ServerSide
056@ExtensionPoint
057public interface BuiltInQualityProfilesDefinition {
058
059  /**
060   * Instantiated by core but not by plugins, except for their tests.
061   */
062  class Context {
063
064    private final Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = new HashMap<>();
065
066    /**
067     * New builder for {@link BuiltInQualityProfile}.
068     * <br>
069     * A plugin can activate rules in a built in quality profile that is defined by another plugin.
070     */
071    public NewBuiltInQualityProfile createBuiltInQualityProfile(String name, String language) {
072      return new NewBuiltInQualityProfileImpl(this, name, language);
073    }
074
075    private void registerProfile(NewBuiltInQualityProfileImpl newProfile) {
076      String language = newProfile.language();
077      String name = newProfile.name();
078      Preconditions.checkArgument(!profilesByLanguageAndName.computeIfAbsent(language, l -> new LinkedHashMap<>()).containsKey(name),
079        "There is already a quality profile with name '%s' for language '%s'", name, language);
080      profilesByLanguageAndName.get(language).put(name, new BuiltInQualityProfileImpl(newProfile));
081    }
082
083    public Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName() {
084      return profilesByLanguageAndName;
085    }
086
087    public BuiltInQualityProfile profile(String language, String name) {
088      return profilesByLanguageAndName.computeIfAbsent(language, l -> new LinkedHashMap<>()).get(name);
089    }
090  }
091
092  interface NewBuiltInQualityProfile {
093
094    /**
095     * Set whether this is the default profile for the language. The default profile is used when none is explicitly defined when analyzing a project.
096     */
097    NewBuiltInQualityProfile setDefault(boolean value);
098
099    /**
100     * Activate a rule with specified key.
101     *
102     * @throws IllegalArgumentException if rule is already activated in this profile.
103     */
104    NewBuiltInActiveRule activateRule(String repoKey, String ruleKey);
105
106    Collection<NewBuiltInActiveRule> activeRules();
107
108    String language();
109
110    String name();
111
112    boolean isDefault();
113
114    void done();
115  }
116
117  class NewBuiltInQualityProfileImpl implements NewBuiltInQualityProfile {
118    private final Context context;
119    private final String name;
120    private final String language;
121    private boolean isDefault;
122    private final Map<RuleKey, NewBuiltInActiveRule> newActiveRules = new HashMap<>();
123
124    private NewBuiltInQualityProfileImpl(Context context, String name, String language) {
125      this.context = context;
126      this.name = name;
127      this.language = language;
128    }
129
130    @Override
131    public NewBuiltInQualityProfile setDefault(boolean value) {
132      this.isDefault = value;
133      return this;
134    }
135
136    @Override
137    public NewBuiltInActiveRule activateRule(String repoKey, String ruleKey) {
138      RuleKey ruleKeyObj = RuleKey.of(repoKey, ruleKey);
139      checkArgument(!newActiveRules.containsKey(ruleKeyObj), "The rule '%s' is already activated", ruleKeyObj);
140      NewBuiltInActiveRule newActiveRule = new NewBuiltInActiveRule(repoKey, ruleKey);
141      newActiveRules.put(ruleKeyObj, newActiveRule);
142      return newActiveRule;
143    }
144
145    @Override
146    public String language() {
147      return language;
148    }
149
150    @Override
151    public String name() {
152      return name;
153    }
154
155    @Override
156    public boolean isDefault() {
157      return isDefault;
158    }
159
160    @Override
161    public Collection<NewBuiltInActiveRule> activeRules() {
162      return newActiveRules.values();
163    }
164
165    @Override
166    public void done() {
167      checkArgument(isNotBlank(name), "Built-In Quality Profile can't have a blank name");
168      checkArgument(isNotBlank(language), "Built-In Quality Profile can't have a blank language");
169
170      context.registerProfile(this);
171    }
172
173    @Override
174    public String toString() {
175      StringBuilder sb = new StringBuilder("NewBuiltInQualityProfile{");
176      sb.append("name='").append(name).append('\'');
177      sb.append(", language='").append(language).append('\'');
178      sb.append(", default='").append(isDefault).append('\'');
179      sb.append('}');
180      return sb.toString();
181    }
182  }
183
184  interface BuiltInQualityProfile {
185    String name();
186
187    String language();
188
189    boolean isDefault();
190
191    @CheckForNull
192    BuiltInActiveRule rule(RuleKey ruleKey);
193
194    List<BuiltInActiveRule> rules();
195  }
196
197  @Immutable
198  class BuiltInQualityProfileImpl implements BuiltInQualityProfile {
199
200    private static final Logger LOG = Loggers.get(BuiltInQualityProfilesDefinition.BuiltInQualityProfileImpl.class);
201    private final String language;
202    private final String name;
203    private final boolean isDefault;
204    private final Map<RuleKey, BuiltInActiveRule> activeRulesByKey;
205
206    private BuiltInQualityProfileImpl(NewBuiltInQualityProfileImpl newProfile) {
207      this.name = newProfile.name();
208      this.language = newProfile.language();
209      this.isDefault = newProfile.isDefault();
210
211      Map<RuleKey, BuiltInActiveRule> ruleBuilder = new HashMap<>();
212      for (NewBuiltInActiveRule newActiveRule : newProfile.activeRules()) {
213        ruleBuilder.put(RuleKey.of(newActiveRule.repoKey, newActiveRule.ruleKey), new BuiltInActiveRule(newActiveRule));
214      }
215      this.activeRulesByKey = unmodifiableMap(ruleBuilder);
216    }
217
218    @Override
219    public String language() {
220      return language;
221    }
222
223    @Override
224    public String name() {
225      return name;
226    }
227
228    @Override
229    public boolean isDefault() {
230      return isDefault;
231    }
232
233    @Override
234    @CheckForNull
235    public BuiltInActiveRule rule(RuleKey ruleKey) {
236      return activeRulesByKey.get(ruleKey);
237    }
238
239    @Override
240    public List<BuiltInActiveRule> rules() {
241      return unmodifiableList(new ArrayList<>(activeRulesByKey.values()));
242    }
243
244    @Override
245    public boolean equals(Object o) {
246      if (this == o) {
247        return true;
248      }
249      if (o == null || getClass() != o.getClass()) {
250        return false;
251      }
252      BuiltInQualityProfileImpl that = (BuiltInQualityProfileImpl) o;
253      return language.equals(that.language) && name.equals(that.name);
254    }
255
256    @Override
257    public int hashCode() {
258      int result = name.hashCode();
259      result = 31 * result + language.hashCode();
260      return result;
261    }
262
263    @Override
264    public String toString() {
265      StringBuilder sb = new StringBuilder("BuiltInQualityProfile{");
266      sb.append("name='").append(name).append('\'');
267      sb.append(", language='").append(language).append('\'');
268      sb.append(", default='").append(isDefault).append('\'');
269      sb.append('}');
270      return sb.toString();
271    }
272  }
273
274  class NewBuiltInActiveRule {
275    private final String repoKey;
276    private final String ruleKey;
277    private String overriddenSeverity = null;
278    private final Map<String, NewOverriddenParam> paramsByKey = new HashMap<>();
279
280    private NewBuiltInActiveRule(String repoKey, String ruleKey) {
281      this.repoKey = repoKey;
282      this.ruleKey = ruleKey;
283    }
284
285    public String repoKey() {
286      return this.repoKey;
287    }
288
289    public String ruleKey() {
290      return this.ruleKey;
291    }
292
293    /**
294     * Override default rule severity in this quality profile. By default the active rule will have the default rule severity.
295     * @param severity See {@link Severity} constants.
296     */
297    public NewBuiltInActiveRule overrideSeverity(String severity) {
298      checkArgument(Severity.ALL.contains(severity), "Severity of rule %s is not correct: %s", RuleKey.of(repoKey, ruleKey), severity);
299      this.overriddenSeverity = severity;
300      return this;
301    }
302
303    /**
304     * Create a parameter with given unique key. Max length of key is 128 characters.
305     */
306    public NewOverriddenParam overrideParam(String paramKey, @Nullable String value) {
307      checkArgument(!paramsByKey.containsKey(paramKey), "The parameter '%s' was already overridden on the built in active rule %s", paramKey, this);
308      NewOverriddenParam param = new NewOverriddenParam(paramKey).setOverriddenValue(value);
309      paramsByKey.put(paramKey, param);
310      return param;
311    }
312
313    @CheckForNull
314    public NewOverriddenParam getOverriddenParam(String paramKey) {
315      return paramsByKey.get(paramKey);
316    }
317
318    public Collection<NewOverriddenParam> getOverriddenParams() {
319      return paramsByKey.values();
320    }
321
322    @Override
323    public String toString() {
324      return format("[repository=%s, key=%s]", repoKey, ruleKey);
325    }
326  }
327
328  /**
329   * A rule activated on a built in quality profile.
330   */
331  @Immutable
332  class BuiltInActiveRule {
333    private final String repoKey;
334    private final String ruleKey;
335    private final String overriddenSeverity;
336    private final Map<String, OverriddenParam> overriddenParams;
337
338    private BuiltInActiveRule(NewBuiltInActiveRule newBuiltInActiveRule) {
339      this.repoKey = newBuiltInActiveRule.repoKey();
340      this.ruleKey = newBuiltInActiveRule.ruleKey();
341      this.overriddenSeverity = newBuiltInActiveRule.overriddenSeverity;
342      Map<String, OverriddenParam> paramsBuilder = new HashMap<>();
343      for (NewOverriddenParam newParam : newBuiltInActiveRule.getOverriddenParams()) {
344        paramsBuilder.put(newParam.key, new OverriddenParam(newParam));
345      }
346      this.overriddenParams = Collections.unmodifiableMap(paramsBuilder);
347    }
348
349    public String repoKey() {
350      return repoKey;
351    }
352
353    public String ruleKey() {
354      return ruleKey;
355    }
356
357    @CheckForNull
358    public String overriddenSeverity() {
359      return overriddenSeverity;
360    }
361
362    @CheckForNull
363    public OverriddenParam overriddenParam(String key) {
364      return overriddenParams.get(key);
365    }
366
367    public List<OverriddenParam> overriddenParams() {
368      return unmodifiableList(new ArrayList<>(overriddenParams.values()));
369    }
370
371    @Override
372    public String toString() {
373      return format("[repository=%s, key=%s]", repoKey, ruleKey);
374    }
375  }
376
377  class NewOverriddenParam {
378    private final String key;
379    private String overriddenValue;
380
381    private NewOverriddenParam(String key) {
382      this.key = key;
383    }
384
385    public String key() {
386      return key;
387    }
388
389    /**
390     * Empty default value will be converted to null. Max length is 4000 characters.
391     */
392    public NewOverriddenParam setOverriddenValue(@Nullable String s) {
393      this.overriddenValue = defaultIfEmpty(s, null);
394      return this;
395    }
396  }
397
398  @Immutable
399  class OverriddenParam {
400    private final String key;
401    private final String overriddenValue;
402
403    private OverriddenParam(NewOverriddenParam newOverriddenParam) {
404      this.key = newOverriddenParam.key();
405      this.overriddenValue = newOverriddenParam.overriddenValue;
406    }
407
408    public String key() {
409      return key;
410    }
411
412    @Nullable
413    public String overriddenValue() {
414      return overriddenValue;
415    }
416
417  }
418
419  /**
420   * This method is executed when server is started.
421   */
422  void define(Context context);
423
424}