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}