瀏覽代碼

SONAR-5236 Create new API to register built-in quality profiles

tags/6.6-RC1
Julien HENRY 6 年之前
父節點
當前提交
4c5d6a2722
共有 33 個文件被更改,包括 1219 次插入212 次删除
  1. 4
    0
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
  2. 37
    0
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinition.java
  3. 8
    0
      plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
  4. 54
    0
      plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinitionTest.java
  5. 2
    0
      server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
  6. 1
    1
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  7. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  8. 6
    6
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java
  9. 91
    0
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java
  10. 8
    7
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java
  11. 28
    59
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java
  12. 7
    8
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java
  13. 106
    0
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java
  14. 25
    17
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImplTest.java
  15. 6
    65
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java
  16. 8
    8
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java
  17. 31
    18
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java
  18. 17
    9
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java
  19. 4
    1
      sonar-plugin-api/src/main/java/org/sonar/api/profiles/AnnotationProfileParser.java
  20. 3
    0
      sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfileDefinition.java
  21. 1
    4
      sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java
  22. 1
    1
      sonar-plugin-api/src/main/java/org/sonar/api/profiles/XMLProfileParser.java
  23. 12
    0
      sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRule.java
  24. 55
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoader.java
  25. 424
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinition.java
  26. 23
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/profile/package-info.java
  27. 69
    0
      sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoaderTest.java
  28. 176
    0
      sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinitionTest.java
  29. 4
    4
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionAnnotationLoaderTest.java
  30. 2
    2
      sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java
  31. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/LocalIssueTracking.java
  32. 1
    1
      tests/src/test/java/org/sonarqube/tests/issue/CustomRulesTest.java
  33. 2
    0
      tests/src/test/java/org/sonarqube/tests/qualityProfile/BuiltInQualityProfilesTest.java

+ 4
- 0
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java 查看文件

@@ -60,6 +60,7 @@ import org.sonar.xoo.rule.SaveDataTwiceSensor;
import org.sonar.xoo.rule.Xoo2BasicProfile;
import org.sonar.xoo.rule.Xoo2SonarWayProfile;
import org.sonar.xoo.rule.XooBasicProfile;
import org.sonar.xoo.rule.XooBuiltInQualityProfilesDefinition;
import org.sonar.xoo.rule.XooEmptyProfile;
import org.sonar.xoo.rule.XooFakeExporter;
import org.sonar.xoo.rule.XooFakeImporter;
@@ -161,6 +162,9 @@ public class XooPlugin implements Plugin {
if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(5, 5))) {
context.addExtension(CpdTokenizerSensor.class);
}
if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(6,6))) {
context.addExtension(XooBuiltInQualityProfilesDefinition.class);
}
}

}

+ 37
- 0
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinition.java 查看文件

@@ -0,0 +1,37 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.xoo.rule;

import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.xoo.Xoo;

import static org.sonar.xoo.rule.XooRulesDefinition.XOO_REPOSITORY;

public class XooBuiltInQualityProfilesDefinition implements BuiltInQualityProfilesDefinition {
@Override
public void define(Context context) {
NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile("test BuiltInQualityProfilesDefinition", Xoo.KEY);
profile.setDefault(false);
NewBuiltInActiveRule rule = profile.activateRule(XOO_REPOSITORY, HasTagSensor.RULE_KEY);
rule.overrideSeverity("BLOCKER");
rule.overrideParam("tag", "TODO");
profile.done();
}
}

+ 8
- 0
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java 查看文件

@@ -46,4 +46,12 @@ public class XooPluginTest {
new XooPlugin().define(context);
assertThat(context.getExtensions()).hasSize(49).contains(CpdTokenizerSensor.class);
}

@Test
public void provide_extensions_for_6_6() {
SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.6"), SonarQubeSide.SCANNER);
Plugin.Context context = new Plugin.Context(runtime);
new XooPlugin().define(context);
assertThat(context.getExtensions()).hasSize(50).contains(CpdTokenizerSensor.class);
}
}

+ 54
- 0
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooBuiltInQualityProfilesDefinitionTest.java 查看文件

@@ -0,0 +1,54 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.xoo.rule;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;

import static org.assertj.core.api.Assertions.assertThat;

public class XooBuiltInQualityProfilesDefinitionTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

private XooBuiltInQualityProfilesDefinition underTest = new XooBuiltInQualityProfilesDefinition();

@Test
public void test_built_in_quality_profile() {
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();

underTest.define(context);

BuiltInQualityProfile profile = context.profile("xoo", "test BuiltInQualityProfilesDefinition");
assertThat(profile.isDefault()).isFalse();
assertThat(profile.name()).isEqualTo("test BuiltInQualityProfilesDefinition");
assertThat(profile.language()).isEqualTo("xoo");
assertThat(profile.rules()).hasSize(1);
BuiltInQualityProfilesDefinition.BuiltInActiveRule activeRule = profile.rule(RuleKey.of("xoo", "HasTag"));
assertThat(activeRule.overriddenSeverity()).isEqualTo("BLOCKER");
assertThat(activeRule.overriddenParams()).hasSize(1);
assertThat(activeRule.overriddenParam("tag").overriddenValue()).isEqualTo("TODO");
}
}

+ 2
- 0
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java 查看文件

@@ -34,6 +34,7 @@ import org.sonar.api.resources.Languages;
import org.sonar.api.resources.ResourceTypes;
import org.sonar.api.rules.AnnotationRuleParser;
import org.sonar.api.rules.XMLRuleParser;
import org.sonar.api.server.profile.BuiltInQualityProfileAnnotationLoader;
import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
import org.sonar.api.utils.Durations;
import org.sonar.api.utils.System2;
@@ -318,6 +319,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
XMLProfileParser.class,
XMLProfileSerializer.class,
AnnotationProfileParser.class,
BuiltInQualityProfileAnnotationLoader.class,
Rules.QProfiles.class,

// rule

+ 1
- 1
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java 查看文件

@@ -123,7 +123,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getComponentAdapters())
.hasSize(
CONTAINER_ITSELF
+ 72 // level 4
+ 73 // level 4
+ 4 // content of CeConfigurationModule
+ 4 // content of CeQueueModule
+ 4 // content of CeHttpModule

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java 查看文件

@@ -152,6 +152,7 @@ import org.sonar.server.projecttag.ws.ProjectTagsWsModule;
import org.sonar.server.property.InternalPropertiesImpl;
import org.sonar.server.property.ws.PropertiesWs;
import org.sonar.server.qualitygate.QualityGateModule;
import org.sonar.server.qualityprofile.BuiltInQProfileDefinitionsBridge;
import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryImpl;
import org.sonar.server.qualityprofile.QProfileBackuperImpl;
import org.sonar.server.qualityprofile.QProfileComparison;
@@ -276,6 +277,7 @@ public class PlatformLevel4 extends PlatformLevel {
BillingValidationsProxyImpl.class,

// quality profile
BuiltInQProfileDefinitionsBridge.class,
BuiltInQProfileRepositoryImpl.class,
ActiveRuleIndexer.class,
XMLProfileParser.class,

+ 6
- 6
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java 查看文件

@@ -23,16 +23,16 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import org.sonar.api.profiles.ProfileDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;

/**
* Represent a Quality Profile as computed from {@link ProfileDefinition} provided by installed plugins.
* Represent a Quality Profile as computed from {@link BuiltInQualityProfilesDefinition} provided by installed plugins.
*/
@Immutable
public final class BuiltInQProfile {
private final QProfileName qProfileName;
private final boolean isDefault;
private final List<org.sonar.api.rules.ActiveRule> activeRules;
private final List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> activeRules;

private BuiltInQProfile(Builder builder) {
this.qProfileName = new QProfileName(builder.language, builder.name);
@@ -56,7 +56,7 @@ public final class BuiltInQProfile {
return isDefault;
}

public List<org.sonar.api.rules.ActiveRule> getActiveRules() {
public List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> getActiveRules() {
return activeRules;
}

@@ -65,7 +65,7 @@ public final class BuiltInQProfile {
private String name;
private boolean declaredDefault;
private boolean computedDefault;
private final List<org.sonar.api.rules.ActiveRule> activeRules = new ArrayList<>();
private final List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> activeRules = new ArrayList<>();

public Builder setLanguage(String language) {
this.language = language;
@@ -95,7 +95,7 @@ public final class BuiltInQProfile {
return this;
}

Builder addRules(List<org.sonar.api.rules.ActiveRule> rules) {
Builder addRules(List<BuiltInQualityProfilesDefinition.BuiltInActiveRule> rules) {
this.activeRules.addAll(rules);
return this;
}

+ 91
- 0
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java 查看文件

@@ -0,0 +1,91 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.qualityprofile;

import com.google.common.collect.ImmutableList;
import java.util.List;
import org.sonar.api.profiles.ProfileDefinition;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rules.ActiveRuleParam;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;

import static java.lang.String.format;

/**
* Bridge between deprecated {@link ProfileDefinition} API and new {@link BuiltInQualityProfilesDefinition}
*/
public class BuiltInQProfileDefinitionsBridge implements BuiltInQualityProfilesDefinition {
private static final Logger LOGGER = Loggers.get(BuiltInQProfileDefinitionsBridge.class);

private final List<ProfileDefinition> definitions;

/**
* Requires for pico container when no {@link ProfileDefinition} is defined at all
*/
public BuiltInQProfileDefinitionsBridge() {
this(new ProfileDefinition[0]);
}

public BuiltInQProfileDefinitionsBridge(ProfileDefinition... definitions) {
this.definitions = ImmutableList.copyOf(definitions);
}

@Override
public void define(Context context) {
Profiler profiler = Profiler.create(Loggers.get(getClass()));
for (ProfileDefinition definition : definitions) {
profiler.start();
ValidationMessages validation = ValidationMessages.create();
RulesProfile profile = definition.createProfile(validation);
validation.log(LOGGER);
if (profile == null) {
profiler.stopDebug(format("Loaded definition %s that return no profile", definition));
} else {
if (!validation.hasErrors()) {
define(context, profile);
}
profiler.stopDebug(format("Loaded deprecated profile definition %s for language %s", profile.getName(), profile.getLanguage()));
}
}
}

private static void define(Context context, RulesProfile profile) {
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage())
.setDefault(profile.getDefaultProfile().booleanValue());

for (org.sonar.api.rules.ActiveRule ar : profile.getActiveRules()) {
NewBuiltInActiveRule newActiveRule = newQp.activateRule(ar.getRepositoryKey(), ar.getRuleKey());
RulePriority overriddenSeverity = ar.getOverriddenSeverity();
if (overriddenSeverity != null) {
newActiveRule.overrideSeverity(overriddenSeverity.name());
}
for (ActiveRuleParam param : ar.getActiveRuleParams()) {
newActiveRule.overrideParam(param.getKey(), param.getValue());
}
}
newQp.done();
}

}

+ 8
- 7
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java 查看文件

@@ -36,7 +36,7 @@ import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.ActiveRuleParam;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.rule.RuleParamType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactory;
@@ -139,8 +139,8 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
return dto;
}

private ActiveRuleChange insertActiveRule(DbSession dbSession, RulesProfileDto rulesProfileDto, org.sonar.api.rules.ActiveRule activeRule, long now) {
RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey());
private ActiveRuleChange insertActiveRule(DbSession dbSession, RulesProfileDto rulesProfileDto, BuiltInQualityProfilesDefinition.BuiltInActiveRule activeRule, long now) {
RuleKey ruleKey = RuleKey.of(activeRule.repoKey(), activeRule.ruleKey());
RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey)
.orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey));

@@ -148,7 +148,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
dto.setProfileId(rulesProfileDto.getId());
dto.setRuleId(ruleDefinitionDto.getId());
dto.setKey(ActiveRuleKey.of(rulesProfileDto, ruleDefinitionDto.getKey()));
dto.setSeverity(firstNonNull(activeRule.getSeverity().name(), ruleDefinitionDto.getSeverityString()));
dto.setSeverity(firstNonNull(activeRule.overriddenSeverity(), ruleDefinitionDto.getSeverityString()));
dto.setUpdatedAt(now);
dto.setCreatedAt(now);
dbClient.activeRuleDao().insert(dbSession, dto);
@@ -161,10 +161,11 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
return change;
}

private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, org.sonar.api.rules.ActiveRule activeRule, RuleKey ruleKey, ActiveRuleDto activeRuleDto) {
Map<String, String> valuesByParamKey = activeRule.getActiveRuleParams()
private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, BuiltInQualityProfilesDefinition.BuiltInActiveRule activeRule, RuleKey ruleKey,
ActiveRuleDto activeRuleDto) {
Map<String, String> valuesByParamKey = activeRule.overriddenParams()
.stream()
.collect(MoreCollectors.uniqueIndex(ActiveRuleParam::getParamKey, ActiveRuleParam::getValue));
.collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
return ruleRepository.getRuleParams(ruleKey)
.stream()
.map(param -> {

+ 28
- 59
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java 查看文件

@@ -19,49 +19,41 @@
*/
package org.sonar.server.qualityprofile;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.api.profiles.ProfileDefinition;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Languages;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;
import org.sonar.core.util.stream.MoreCollectors;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.isNotEmpty;
import static org.apache.commons.lang.StringUtils.lowerCase;

public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository {
private static final Logger LOGGER = Loggers.get(BuiltInQProfileRepositoryImpl.class);
private static final String DEFAULT_PROFILE_NAME = "Sonar way";

private final Languages languages;
private final List<ProfileDefinition> definitions;
private final List<BuiltInQualityProfilesDefinition> definitions;
private List<BuiltInQProfile> qProfiles;

/**
* Requires for pico container when no {@link ProfileDefinition} is defined at all
* Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all
*/
public BuiltInQProfileRepositoryImpl(Languages languages) {
this(languages, new ProfileDefinition[0]);
this(languages, new BuiltInQualityProfilesDefinition[0]);
}

public BuiltInQProfileRepositoryImpl(Languages languages, ProfileDefinition... definitions) {
public BuiltInQProfileRepositoryImpl(Languages languages, BuiltInQualityProfilesDefinition... definitions) {
this.languages = languages;
this.definitions = ImmutableList.copyOf(definitions);
}
@@ -70,9 +62,12 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
public void initialize() {
checkState(qProfiles == null, "initialize must be called only once");

Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Load quality profiles");
ListMultimap<String, RulesProfile> rulesProfilesByLanguage = buildRulesProfilesByLanguage();
validateAndClean(rulesProfilesByLanguage);
Profiler profiler = Profiler.create(LOGGER).startInfo("Load quality profiles");
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
for (BuiltInQualityProfilesDefinition definition : definitions) {
definition.define(context);
}
Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage = validateAndClean(context);
this.qProfiles = toFlatList(rulesProfilesByLanguage);
profiler.stopDebug();
}
@@ -84,49 +79,27 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
return qProfiles;
}

/**
* @return profiles by language
*/
private ListMultimap<String, RulesProfile> buildRulesProfilesByLanguage() {
ListMultimap<String, RulesProfile> byLang = ArrayListMultimap.create();
Profiler profiler = Profiler.create(Loggers.get(getClass()));
for (ProfileDefinition definition : definitions) {
profiler.start();
ValidationMessages validation = ValidationMessages.create();
RulesProfile profile = definition.createProfile(validation);
validation.log(LOGGER);
if (profile == null) {
profiler.stopDebug(format("Loaded definition %s that return no profile", definition));
} else {
if (!validation.hasErrors()) {
checkArgument(isNotEmpty(profile.getName()), "Profile created by Definition %s can't have a blank name", definition);
byLang.put(lowerCase(profile.getLanguage(), Locale.ENGLISH), profile);
}
profiler.stopDebug(format("Loaded definition %s for language %s", profile.getName(), profile.getLanguage()));
}
}
return byLang;
}

private void validateAndClean(ListMultimap<String, RulesProfile> byLang) {
byLang.asMap().entrySet()
private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) {
Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = context.profilesByLanguageAndName();
profilesByLanguageAndName.entrySet()
.removeIf(entry -> {
String language = entry.getKey();
if (languages.get(language) == null) {
LOGGER.info("Language {} is not installed, related Quality profiles are ignored", language);
return true;
}
Collection<RulesProfile> profiles = entry.getValue();
Collection<BuiltInQualityProfile> profiles = entry.getValue().values();
if (profiles.isEmpty()) {
LOGGER.warn("No Quality profiles defined for language: {}", language);
return true;
}
return false;
});
return profilesByLanguageAndName;
}

private static List<BuiltInQProfile> toFlatList(ListMultimap<String, RulesProfile> rulesProfilesByLanguage) {
Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = Multimaps.asMap(rulesProfilesByLanguage)
private static List<BuiltInQProfile> toFlatList(Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage) {
Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = rulesProfilesByLanguage
.entrySet()
.stream()
.collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, BuiltInQProfileRepositoryImpl::toQualityProfileBuilders));
@@ -151,14 +124,14 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
* RulesProfile with a given name</li>
* </ul>
*/
private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, List<RulesProfile>> rulesProfilesByLanguage) {
String language = rulesProfilesByLanguage.getKey();
private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguageAndName) {
String language = rulesProfilesByLanguageAndName.getKey();
// use a LinkedHashMap to keep order of insertion of RulesProfiles
Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) {
for (BuiltInQualityProfile builtInProfile : rulesProfilesByLanguageAndName.getValue().values()) {
qualityProfileBuildersByName.compute(
rulesProfile.getName(),
(name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile));
builtInProfile.name(),
(name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, builtInProfile));
}
return ImmutableList.copyOf(qualityProfileBuildersByName.values());
}
@@ -175,20 +148,16 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
return true;
}

private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, RulesProfile rulesProfile) {
private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, BuiltInQualityProfile builtInProfile) {
BuiltInQProfile.Builder builder = existingBuilder;
if (builder == null) {
builder = new BuiltInQProfile.Builder()
.setLanguage(language)
.setName(rulesProfile.getName());
.setName(builtInProfile.name());
}
Boolean defaultProfile = rulesProfile.getDefaultProfile();
boolean declaredDefault = defaultProfile != null && defaultProfile;
return builder
// if there is multiple RulesProfiles with the same name, if at least one is declared default,
// then QualityProfile is flagged as declared default
.setDeclaredDefault(builder.isDeclaredDefault() || declaredDefault)
.addRules(rulesProfile.getActiveRules());
.setDeclaredDefault(builtInProfile.isDefault())
.addRules(builtInProfile.rules());
}

private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) {

+ 7
- 8
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java 查看文件

@@ -24,7 +24,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.ActiveRuleParam;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -59,18 +60,16 @@ public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate {
});

// these rules are not part of the built-in profile anymore
toBeDeactivated.forEach(ruleKey ->
changes.addAll(ruleActivator.deactivateOnBuiltInRulesProfile(dbSession, ruleProfile, ruleKey, false)));
toBeDeactivated.forEach(ruleKey -> changes.addAll(ruleActivator.deactivateOnBuiltInRulesProfile(dbSession, ruleProfile, ruleKey, false)));

activeRuleIndexer.commitAndIndex(dbSession, changes);
return changes;
}

private static RuleActivation convert(org.sonar.api.rules.ActiveRule ar) {
String severity = ar.getSeverity() != null ? ar.getSeverity().name() : null;
Map<String, String> params = ar.getActiveRuleParams().stream()
.collect(MoreCollectors.uniqueIndex(ActiveRuleParam::getKey, ActiveRuleParam::getValue));
return RuleActivation.create(ar.getRule().ruleKey(), severity, params);
private static RuleActivation convert(BuiltInActiveRule ar) {
Map<String, String> params = ar.overriddenParams().stream()
.collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
return RuleActivation.create(RuleKey.of(ar.repoKey(), ar.ruleKey()), ar.overriddenSeverity(), params);
}

}

+ 106
- 0
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java 查看文件

@@ -0,0 +1,106 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.qualityprofile;

import java.util.Arrays;
import org.junit.Test;
import org.sonar.api.profiles.ProfileDefinition;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleParam;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
import org.sonar.api.utils.ValidationMessages;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;

public class BuiltInQProfileDefinitionsBridgeTest {

@Test
public void noProfileDefinitions() {
BuiltInQProfileDefinitionsBridge bridge = new BuiltInQProfileDefinitionsBridge();

BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
bridge.define(context);

assertThat(context.profilesByLanguageAndName()).isEmpty();
}

@Test
public void bridgeProfileDefinitions() {
BuiltInQProfileDefinitionsBridge bridge = new BuiltInQProfileDefinitionsBridge(new Profile1(), new NullProfile(), new ProfileWithError());

BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
bridge.define(context);

assertThat(context.profilesByLanguageAndName()).hasSize(1);
assertThat(context.profilesByLanguageAndName().get("xoo")).hasSize(1);

BuiltInQualityProfile profile1 = context.profile("xoo", "Profile 1");
assertThat(profile1).isNotNull();
assertThat(profile1.rules()).hasSize(3);
BuiltInActiveRule defaultSeverity = profile1.rule(RuleKey.of("repo1", "defaultSeverity"));
assertThat(defaultSeverity).isNotNull();
assertThat(defaultSeverity.overriddenSeverity()).isNull();
assertThat(defaultSeverity.overriddenParams()).isEmpty();

assertThat(profile1.rule(RuleKey.of("repo1", "overrideSeverity")).overriddenSeverity()).isEqualTo(Severity.CRITICAL);

assertThat(profile1.rule(RuleKey.of("repo1", "overrideParam")).overriddenParams())
.extracting(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue).containsOnly(tuple("param", "value"));
}

private class Profile1 extends ProfileDefinition {
@Override
public RulesProfile createProfile(ValidationMessages validation) {
RulesProfile profile1 = RulesProfile.create("Profile 1", "xoo");

profile1.activateRule(Rule.create("repo1", "defaultSeverity"), null);
profile1.activateRule(Rule.create("repo1", "overrideSeverity"), RulePriority.CRITICAL);
Rule ruleWithParam = Rule.create("repo1", "overrideParam");
ruleWithParam.setParams(Arrays.asList(new RuleParam(ruleWithParam, "param", "", "")));
ActiveRule arWithParam = profile1.activateRule(ruleWithParam, null);
arWithParam.setParameter("param", "value");

return profile1;
}
}

private class NullProfile extends ProfileDefinition {
@Override
public RulesProfile createProfile(ValidationMessages validation) {
return null;
}
}

private class ProfileWithError extends ProfileDefinition {
@Override
public RulesProfile createProfile(ValidationMessages validation) {
validation.addErrorText("Foo");
return RulesProfile.create("Profile with errors", "xoo");
}
}
}

+ 25
- 17
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImplTest.java 查看文件

@@ -27,6 +27,8 @@ import org.junit.rules.ExpectedException;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
import org.sonar.core.util.SequenceUuidFactory;
@@ -48,8 +50,6 @@ import org.sonar.server.util.TypeValidations;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.sonar.api.rules.RulePriority.CRITICAL;
import static org.sonar.api.rules.RulePriority.MAJOR;

public class BuiltInQProfileInsertImplTest {

@@ -102,11 +102,15 @@ public class BuiltInQProfileInsertImplTest {
OrganizationDto org = db.organizations().insert();
RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
activeRule(apiProfile, rule1, CRITICAL);
activeRule(apiProfile, rule2, MAJOR);

BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo");

newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
newQp.done();

BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
call(builtIn);

verifyTableSize("rules_profiles", 1);
@@ -122,9 +126,11 @@ public class BuiltInQProfileInsertImplTest {
@Test
public void flag_profile_as_default_on_organization_if_declared_as_default_by_api() {
OrganizationDto org = db.organizations().insert();
RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
apiProfile.setDefaultProfile(true);
BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
newQp.done();

BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));

call(builtIn);

@@ -135,26 +141,28 @@ public class BuiltInQProfileInsertImplTest {

@Test
public void existing_default_profile_in_organization_must_not_be_changed() {
RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
apiProfile.setDefaultProfile(true);
BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
newQp.done();
BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));

OrganizationDto org = db.organizations().insert();
QProfileDto currentDefault = db.qualityProfiles().insert(org, p -> p.setLanguage(apiProfile.getLanguage()));
QProfileDto currentDefault = db.qualityProfiles().insert(org, p -> p.setLanguage("xoo"));
db.qualityProfiles().setAsDefault(currentDefault);

call(builtIn);

QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, org, apiProfile.getLanguage());
QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, org, "xoo");
assertThat(defaultProfile.getKee()).isEqualTo(currentDefault.getKee());
}

@Test
public void dont_flag_profile_as_default_on_organization_if_not_declared_as_default_by_api() {
OrganizationDto org = db.organizations().insert();
RulesProfile apiProfile = RulesProfile.create("the name", "xoo");
apiProfile.setDefaultProfile(false);
BuiltInQProfile builtIn = builtInQProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(false);
newQp.done();
BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));

call(builtIn);


+ 6
- 65
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java 查看文件

@@ -22,15 +22,12 @@ package org.sonar.server.qualityprofile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.profiles.ProfileDefinition;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.server.language.LanguageTesting;

import static java.util.Arrays.asList;
@@ -83,28 +80,6 @@ public class BuiltInQProfileRepositoryImplTest {
assertThat(underTest.get()).isEmpty();
}

@Test
public void initialize_throws_IAE_if_profileDefinition_creates_RulesProfile_with_null_name() {
DummyProfileDefinition definition = new DummyProfileDefinition("foo", null, false);
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(), definition);

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Profile created by Definition " + definition + " can't have a blank name");

underTest.initialize();
}

@Test
public void initialize_throws_IAE_if_profileDefinition_creates_RulesProfile_with_empty_name() {
DummyProfileDefinition definition = new DummyProfileDefinition("foo", "", false);
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(), definition);

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Profile created by Definition " + definition + " can't have a blank name");

underTest.initialize();
}

@Test
public void initialize_makes_single_profile_of_a_language_default_even_if_not_flagged_as_so() {
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", false));
@@ -134,7 +109,7 @@ public class BuiltInQProfileRepositoryImplTest {
Collections.shuffle(definitions);
String firstName = definitions.get(0).getName();
String secondName = definitions.get(1).getName();
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), definitions.toArray(new ProfileDefinition[0]));
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), definitions.toArray(new BuiltInQualityProfilesDefinition[0]));

underTest.initialize();

@@ -154,32 +129,6 @@ public class BuiltInQProfileRepositoryImplTest {
underTest.initialize();
}

@Test
public void initialize_creates_profile_as_default_even_if_only_one_profile_with_given_name_has_default_flag_true() {
String name = "doh";
boolean flag = new Random().nextBoolean();
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE),
new DummyProfileDefinition("foo", name, flag), new DummyProfileDefinition("foo", name, !flag));

underTest.initialize();

assertThat(underTest.get())
.extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault)
.containsExactly(tuple("foo", true));
}

@Test
public void initialize_creates_single_profile_if_several_profile_have_the_same_name_for_a_given_language() {
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE),
new DummyProfileDefinition("foo", "aName", true), new DummyProfileDefinition("foo", "aName", true));

underTest.initialize();

assertThat(underTest.get())
.extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::getName)
.containsExactlyInAnyOrder(tuple(FOO_LANGUAGE.getKey(), "aName"));
}

@Test
public void initialize_creates_profile_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() {
BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
@@ -227,7 +176,7 @@ public class BuiltInQProfileRepositoryImplTest {
.containsExactly("goo");
}

private static final class DummyProfileDefinition extends ProfileDefinition {
private static final class DummyProfileDefinition implements BuiltInQualityProfilesDefinition {
private final String language;
private final String name;
private final boolean defaultProfile;
@@ -239,23 +188,15 @@ public class BuiltInQProfileRepositoryImplTest {
}

@Override
public RulesProfile createProfile(ValidationMessages validation) {
RulesProfile res = RulesProfile.create(name, language);
res.setDefaultProfile(defaultProfile);
return res;
}

String getLanguage() {
return language;
public void define(Context context) {
context.createBuiltInQualityProfile(name, language)
.setDefault(defaultProfile).done();
}

String getName() {
return name;
}

boolean isDefaultProfile() {
return defaultProfile;
}
}

}

+ 8
- 8
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java 查看文件

@@ -24,8 +24,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.rules.ExternalResource;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Language;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;

import static com.google.common.base.Preconditions.checkState;

@@ -60,13 +60,13 @@ public class BuiltInQProfileRepositoryRule extends ExternalResource implements B
return add(language, profileName, false);
}

public BuiltInQProfile add(Language language, String profileName, boolean isDefault, org.sonar.api.rules.ActiveRule... rules) {
public BuiltInQProfile add(Language language, String profileName, boolean isDefault, BuiltInQualityProfilesDefinition.BuiltInActiveRule... rules) {
BuiltInQProfile builtIn = create(language, profileName, isDefault, rules);
profiles.add(builtIn);
return builtIn;
}

public BuiltInQProfile create(Language language, String profileName, boolean isDefault, org.sonar.api.rules.ActiveRule... rules) {
public BuiltInQProfile create(Language language, String profileName, boolean isDefault, BuiltInQualityProfilesDefinition.BuiltInActiveRule... rules) {
return new BuiltInQProfile.Builder()
.setLanguage(language.getKey())
.setName(profileName)
@@ -75,12 +75,12 @@ public class BuiltInQProfileRepositoryRule extends ExternalResource implements B
.build();
}

public BuiltInQProfile create(RulesProfile api) {
public BuiltInQProfile create(BuiltInQualityProfilesDefinition.BuiltInQualityProfile api) {
return new BuiltInQProfile.Builder()
.setLanguage(api.getLanguage())
.setName(api.getName())
.setDeclaredDefault(api.getDefaultProfile())
.addRules(new ArrayList<>(api.getActiveRules()))
.setLanguage(api.language())
.setName(api.name())
.setDeclaredDefault(api.isDefault())
.addRules(new ArrayList<>(api.rules()))
.build();
}
}

+ 31
- 18
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java 查看文件

@@ -25,7 +25,10 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.db.DbTester;
@@ -81,10 +84,12 @@ public class BuiltInQProfileUpdateImplTest {
public void activate_new_rules() {
RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
activateRuleInDef(apiProfile, rule1, CRITICAL);
activateRuleInDef(apiProfile, rule2, MAJOR);
BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
newQp.done();
BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));

underTest.update(db.getSession(), builtIn, persistedProfile);

@@ -98,9 +103,11 @@ public class BuiltInQProfileUpdateImplTest {
@Test
public void already_activated_rule_is_updated_in_case_of_differences() {
RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
activateRuleInDef(apiProfile, rule, CRITICAL);
BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
newQp.done();
BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));

activateRuleInDb(persistedProfile, rule, BLOCKER);

@@ -115,9 +122,11 @@ public class BuiltInQProfileUpdateImplTest {
@Test
public void already_activated_rule_is_not_touched_if_no_differences() {
RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
activateRuleInDef(apiProfile, rule, CRITICAL);
BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
newQp.done();
BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));

activateRuleInDb(persistedProfile, rule, CRITICAL);

@@ -133,9 +142,11 @@ public class BuiltInQProfileUpdateImplTest {
public void deactivate_rule_that_is_not_in_built_in_definition_anymore() {
RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
activateRuleInDef(apiProfile, rule2, CRITICAL);
BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
newQp.done();
BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));

// built-in definition contains only rule2
// so rule1 must be deactivated
@@ -154,10 +165,13 @@ public class BuiltInQProfileUpdateImplTest {
RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("xoo"));
RulesProfile apiProfile = RulesProfile.create("Sonar way", "xoo");
activateRuleInDef(apiProfile, rule1, CRITICAL);
activateRuleInDef(apiProfile, rule2, MAJOR);
BuiltInQProfile builtIn = builtInProfileRepository.create(apiProfile);

BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
newQp.done();
BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"));

// rule1 must be updated (blocker to critical)
// rule2 must be activated
@@ -175,7 +189,6 @@ public class BuiltInQProfileUpdateImplTest {
assertThatProfileIsMarkedAsUpdated(persistedProfile);
}


private static void assertThatRuleIsNewlyActivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
ActiveRuleDto activeRule = findRule(activeRules, rule).get();


+ 17
- 9
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java 查看文件

@@ -27,10 +27,11 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.LogTester;
import org.sonar.core.util.UuidFactoryFast;
@@ -57,7 +58,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.api.rules.Rule.create;
import static org.sonar.api.rules.RulePriority.MAJOR;
import static org.sonar.db.qualityprofile.QualityProfileTesting.newRuleProfileDto;
import static org.sonar.server.language.LanguageTesting.newLanguage;
@@ -296,15 +296,23 @@ public class RegisterQualityProfilesNotificationTest {
}

private void addPluginProfile(RulesProfileDto dbProfile, RuleDefinitionDto... dbRules) {
RulesProfile pluginProfile = RulesProfile.create(dbProfile.getName(), dbProfile.getLanguage());
Arrays.stream(dbRules).forEach(dbRule -> pluginProfile.activateRule(create(dbRule.getRepositoryKey(), dbRule.getRuleKey()), null));
builtInQProfileRepositoryRule.add(newLanguage(dbProfile.getLanguage()), dbProfile.getName(), false, pluginProfile.getActiveRules().toArray(new ActiveRule[0]));
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(dbProfile.getName(), dbProfile.getLanguage());

Arrays.stream(dbRules).forEach(dbRule -> newQp.activateRule(dbRule.getRepositoryKey(), dbRule.getRuleKey()).overrideSeverity(Severity.MAJOR));
newQp.done();
builtInQProfileRepositoryRule.add(newLanguage(dbProfile.getLanguage()), dbProfile.getName(), false,
context.profile(dbProfile.getLanguage(), dbProfile.getName()).rules().toArray(new BuiltInActiveRule[0]));
}

private void addPluginProfile(QProfileDto profile, RuleDefinitionDto... dbRules) {
RulesProfile pluginProfile = RulesProfile.create(profile.getName(), profile.getLanguage());
Arrays.stream(dbRules).forEach(dbRule -> pluginProfile.activateRule(create(dbRule.getRepositoryKey(), dbRule.getRuleKey()), null));
builtInQProfileRepositoryRule.add(newLanguage(profile.getLanguage()), profile.getName(), false, pluginProfile.getActiveRules().toArray(new ActiveRule[0]));
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());

Arrays.stream(dbRules).forEach(dbRule -> newQp.activateRule(dbRule.getRepositoryKey(), dbRule.getRuleKey()).overrideSeverity(Severity.MAJOR));
newQp.done();
builtInQProfileRepositoryRule.add(newLanguage(profile.getLanguage()), profile.getName(), false,
context.profile(profile.getLanguage(), profile.getName()).rules().toArray(new BuiltInActiveRule[0]));
}

private RulesProfileDto insertBuiltInProfile(String language) {

+ 4
- 1
sonar-plugin-api/src/main/java/org/sonar/api/profiles/AnnotationProfileParser.java 查看文件

@@ -21,20 +21,23 @@ package org.sonar.api.profiles;

import java.util.Collection;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleAnnotationUtils;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.profile.BuiltInQualityProfileAnnotationLoader;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.check.BelongsToProfile;

/**
* @since 2.3
* @deprecated since 6.6 use {@link BuiltInQualityProfileAnnotationLoader}
*/
@ServerSide
@ComputeEngineSide
@Deprecated
public final class AnnotationProfileParser {

private final RuleFinder ruleFinder;

+ 3
- 0
sonar-plugin-api/src/main/java/org/sonar/api/profiles/ProfileDefinition.java 查看文件

@@ -21,6 +21,7 @@ package org.sonar.api.profiles;

import org.sonar.api.ExtensionPoint;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
import org.sonar.api.utils.ValidationMessages;

/**
@@ -28,9 +29,11 @@ import org.sonar.api.utils.ValidationMessages;
* The components <code>AnnotationProfileParser</code> and <code>XMLProfileParser</code> can be used to help implementing the method create().
*
* @since 2.3
* @deprecated since 6.6 use {@link BuiltInQualityProfilesDefinition}
*/
@ServerSide
@ExtensionPoint
@Deprecated
public abstract class ProfileDefinition {

public abstract RulesProfile createProfile(ValidationMessages validation);

+ 1
- 4
sonar-plugin-api/src/main/java/org/sonar/api/profiles/RulesProfile.java 查看文件

@@ -308,10 +308,7 @@ public class RulesProfile implements Cloneable {
"The definition of the profile '%s' (language '%s') contains multiple occurrences of the '%s:%s' rule. The plugin which declares this profile should fix this.",
getName(), getLanguage(), rule.getRepositoryKey(), rule.getKey()));
}
ActiveRule activeRule = new ActiveRule();
activeRule.setRule(rule);
activeRule.setRulesProfile(this);
activeRule.setSeverity(optionalSeverity == null ? rule.getSeverity() : optionalSeverity);
ActiveRule activeRule = new ActiveRule(this, rule, optionalSeverity);
activeRules.add(activeRule);
return activeRule;
}

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/profiles/XMLProfileParser.java 查看文件

@@ -31,11 +31,11 @@ import org.apache.commons.lang.StringUtils;
import org.codehaus.staxmate.SMInputFactory;
import org.codehaus.staxmate.in.SMHierarchicCursor;
import org.codehaus.staxmate.in.SMInputCursor;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.ValidationMessages;


+ 12
- 0
sonar-plugin-api/src/main/java/org/sonar/api/rules/ActiveRule.java 查看文件

@@ -36,6 +36,7 @@ public class ActiveRule implements Cloneable {
private Integer id;
private Rule rule;
private RulePriority severity;
private RulePriority overriddenSeverity;
private RulesProfile rulesProfile;
private List<ActiveRuleParam> activeRuleParams = new ArrayList<>();
private String inheritance;
@@ -53,6 +54,7 @@ public class ActiveRule implements Cloneable {
@Deprecated
public ActiveRule(RulesProfile profile, Rule rule, RulePriority severity) {
this.rule = rule;
this.overriddenSeverity = severity;
if (severity == null && rule != null) {
this.severity = rule.getSeverity();
} else {
@@ -119,6 +121,16 @@ public class ActiveRule implements Cloneable {
return severity;
}

/**
* For internal use
* @since 6.6
* @deprecated
*/
@Deprecated
public RulePriority getOverriddenSeverity() {
return overriddenSeverity;
}

/**
* @since 2.5
*/

+ 55
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoader.java 查看文件

@@ -0,0 +1,55 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.server.profile;

import javax.annotation.CheckForNull;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.rules.RuleAnnotationUtils;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInActiveRule;
import org.sonar.check.BelongsToProfile;

/**
* Read definitions of quality profiles based on the annotation {@link BelongsToProfile} provided by sonar-check-api. It is used
* to feed {@link BuiltInQualityProfilesDefinition}.
*
* @see BuiltInQualityProfilesDefinition
* @since 6.6
*/
@ServerSide
public class BuiltInQualityProfileAnnotationLoader {

public void load(BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile builtInProfile, String repositoryKey, Class... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
loadActiveRule(builtInProfile, repositoryKey, annotatedClass);
}
}

@CheckForNull
void loadActiveRule(BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile profile, String repositoryKey, Class<?> clazz) {
BelongsToProfile belongsToProfile = clazz.getAnnotation(BelongsToProfile.class);
if ((belongsToProfile != null) && StringUtils.equals(belongsToProfile.title(), profile.name())) {
String ruleKey = RuleAnnotationUtils.getRuleKey(clazz);
NewBuiltInActiveRule activeRule = profile.activateRule(repositoryKey, ruleKey);
activeRule.overrideSeverity(belongsToProfile.priority().name());
}
}

}

+ 424
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinition.java 查看文件

@@ -0,0 +1,424 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.server.profile;

import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.sonar.api.ExtensionPoint;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
import static org.apache.commons.lang.StringUtils.isNotBlank;

/**
* Define built-in quality profiles which are automatically registered during SonarQube startup.
* We no more provide any facility to load profiles from XML file or annotated classes, but it should
* be straightforward to implement (adapt code of deprecated {@link org.sonar.api.profiles.AnnotationProfileParser}
* or {@link org.sonar.api.profiles.XMLProfileParser} for example).
*
* @since 6.6
*/
@ServerSide
@ExtensionPoint
public interface BuiltInQualityProfilesDefinition {

/**
* Instantiated by core but not by plugins, except for their tests.
*/
class Context {

private final Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = new HashMap<>();

/**
* New builder for {@link BuiltInQualityProfile}.
* <br>
* A plugin can activate rules in a built in quality profile that is defined by another plugin.
*/
public NewBuiltInQualityProfile createBuiltInQualityProfile(String name, String language) {
return new NewBuiltInQualityProfileImpl(this, name, language);
}

private void registerProfile(NewBuiltInQualityProfileImpl newProfile) {
String language = newProfile.language();
String name = newProfile.name();
Preconditions.checkArgument(!profilesByLanguageAndName.computeIfAbsent(language, l -> new LinkedHashMap<>()).containsKey(name),
"There is already a quality profile with name '%s' for language '%s'", name, language);
profilesByLanguageAndName.get(language).put(name, new BuiltInQualityProfileImpl(newProfile));
}

public Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName() {
return profilesByLanguageAndName;
}

public BuiltInQualityProfile profile(String language, String name) {
return profilesByLanguageAndName.computeIfAbsent(language, l -> new LinkedHashMap<>()).get(name);
}
}

interface NewBuiltInQualityProfile {

/**
* Set whether this is the default profile for the language. The default profile is used when none is explicitly defined when analyzing a project.
*/
NewBuiltInQualityProfile setDefault(boolean value);

/**
* Activate a rule with specified key.
*
* @throws IllegalArgumentException if rule is already activated in this profile.
*/
NewBuiltInActiveRule activateRule(String repoKey, String ruleKey);

Collection<NewBuiltInActiveRule> activeRules();

String language();

String name();

boolean isDefault();

void done();
}

class NewBuiltInQualityProfileImpl implements NewBuiltInQualityProfile {
private final Context context;
private final String name;
private final String language;
private boolean isDefault;
private final Map<RuleKey, NewBuiltInActiveRule> newActiveRules = new HashMap<>();

private NewBuiltInQualityProfileImpl(Context context, String name, String language) {
this.context = context;
this.name = name;
this.language = language;
}

@Override
public NewBuiltInQualityProfile setDefault(boolean value) {
this.isDefault = value;
return this;
}

@Override
public NewBuiltInActiveRule activateRule(String repoKey, String ruleKey) {
RuleKey ruleKeyObj = RuleKey.of(repoKey, ruleKey);
checkArgument(!newActiveRules.containsKey(ruleKeyObj), "The rule '%s' is already activated", ruleKeyObj);
NewBuiltInActiveRule newActiveRule = new NewBuiltInActiveRule(repoKey, ruleKey);
newActiveRules.put(ruleKeyObj, newActiveRule);
return newActiveRule;
}

@Override
public String language() {
return language;
}

@Override
public String name() {
return name;
}

@Override
public boolean isDefault() {
return isDefault;
}

@Override
public Collection<NewBuiltInActiveRule> activeRules() {
return newActiveRules.values();
}

@Override
public void done() {
checkArgument(isNotBlank(name), "Built-In Quality Profile can't have a blank name");
checkArgument(isNotBlank(language), "Built-In Quality Profile can't have a blank language");

context.registerProfile(this);
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder("NewBuiltInQualityProfile{");
sb.append("name='").append(name).append('\'');
sb.append(", language='").append(language).append('\'');
sb.append(", default='").append(isDefault).append('\'');
sb.append('}');
return sb.toString();
}
}

interface BuiltInQualityProfile {
String name();

String language();

boolean isDefault();

@CheckForNull
BuiltInActiveRule rule(RuleKey ruleKey);

List<BuiltInActiveRule> rules();
}

@Immutable
class BuiltInQualityProfileImpl implements BuiltInQualityProfile {

private static final Logger LOG = Loggers.get(BuiltInQualityProfilesDefinition.BuiltInQualityProfileImpl.class);
private final String language;
private final String name;
private final boolean isDefault;
private final Map<RuleKey, BuiltInActiveRule> activeRulesByKey;

private BuiltInQualityProfileImpl(NewBuiltInQualityProfileImpl newProfile) {
this.name = newProfile.name();
this.language = newProfile.language();
this.isDefault = newProfile.isDefault();

Map<RuleKey, BuiltInActiveRule> ruleBuilder = new HashMap<>();
for (NewBuiltInActiveRule newActiveRule : newProfile.activeRules()) {
ruleBuilder.put(RuleKey.of(newActiveRule.repoKey, newActiveRule.ruleKey), new BuiltInActiveRule(newActiveRule));
}
this.activeRulesByKey = unmodifiableMap(ruleBuilder);
}

@Override
public String language() {
return language;
}

@Override
public String name() {
return name;
}

@Override
public boolean isDefault() {
return isDefault;
}

@Override
@CheckForNull
public BuiltInActiveRule rule(RuleKey ruleKey) {
return activeRulesByKey.get(ruleKey);
}

@Override
public List<BuiltInActiveRule> rules() {
return unmodifiableList(new ArrayList<>(activeRulesByKey.values()));
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BuiltInQualityProfileImpl that = (BuiltInQualityProfileImpl) o;
return language.equals(that.language) && name.equals(that.name);
}

@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + language.hashCode();
return result;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder("BuiltInQualityProfile{");
sb.append("name='").append(name).append('\'');
sb.append(", language='").append(language).append('\'');
sb.append(", default='").append(isDefault).append('\'');
sb.append('}');
return sb.toString();
}
}

class NewBuiltInActiveRule {
private final String repoKey;
private final String ruleKey;
private String overriddenSeverity = null;
private final Map<String, NewOverriddenParam> paramsByKey = new HashMap<>();

private NewBuiltInActiveRule(String repoKey, String ruleKey) {
this.repoKey = repoKey;
this.ruleKey = ruleKey;
}

public String repoKey() {
return this.repoKey;
}

public String ruleKey() {
return this.ruleKey;
}

/**
* Override default rule severity in this quality profile. By default the active rule will have the default rule severity.
* @param severity See {@link Severity} constants.
*/
public NewBuiltInActiveRule overrideSeverity(String severity) {
checkArgument(Severity.ALL.contains(severity), "Severity of rule %s is not correct: %s", RuleKey.of(repoKey, ruleKey), severity);
this.overriddenSeverity = severity;
return this;
}

/**
* Create a parameter with given unique key. Max length of key is 128 characters.
*/
public NewOverriddenParam overrideParam(String paramKey, @Nullable String value) {
checkArgument(!paramsByKey.containsKey(paramKey), "The parameter '%s' was already overridden on the built in active rule %s", paramKey, this);
NewOverriddenParam param = new NewOverriddenParam(paramKey).setOverriddenValue(value);
paramsByKey.put(paramKey, param);
return param;
}

@CheckForNull
public NewOverriddenParam getOverriddenParam(String paramKey) {
return paramsByKey.get(paramKey);
}

public Collection<NewOverriddenParam> getOverriddenParams() {
return paramsByKey.values();
}

@Override
public String toString() {
return format("[repository=%s, key=%s]", repoKey, ruleKey);
}
}

/**
* A rule activated on a built in quality profile.
*/
@Immutable
class BuiltInActiveRule {
private final String repoKey;
private final String ruleKey;
private final String overriddenSeverity;
private final Map<String, OverriddenParam> overriddenParams;

private BuiltInActiveRule(NewBuiltInActiveRule newBuiltInActiveRule) {
this.repoKey = newBuiltInActiveRule.repoKey();
this.ruleKey = newBuiltInActiveRule.ruleKey();
this.overriddenSeverity = newBuiltInActiveRule.overriddenSeverity;
Map<String, OverriddenParam> paramsBuilder = new HashMap<>();
for (NewOverriddenParam newParam : newBuiltInActiveRule.getOverriddenParams()) {
paramsBuilder.put(newParam.key, new OverriddenParam(newParam));
}
this.overriddenParams = Collections.unmodifiableMap(paramsBuilder);
}

public String repoKey() {
return repoKey;
}

public String ruleKey() {
return ruleKey;
}

@CheckForNull
public String overriddenSeverity() {
return overriddenSeverity;
}

@CheckForNull
public OverriddenParam overriddenParam(String key) {
return overriddenParams.get(key);
}

public List<OverriddenParam> overriddenParams() {
return unmodifiableList(new ArrayList<>(overriddenParams.values()));
}

@Override
public String toString() {
return format("[repository=%s, key=%s]", repoKey, ruleKey);
}
}

class NewOverriddenParam {
private final String key;
private String overriddenValue;

private NewOverriddenParam(String key) {
this.key = key;
}

public String key() {
return key;
}

/**
* Empty default value will be converted to null. Max length is 4000 characters.
*/
public NewOverriddenParam setOverriddenValue(@Nullable String s) {
this.overriddenValue = defaultIfEmpty(s, null);
return this;
}
}

@Immutable
class OverriddenParam {
private final String key;
private final String overriddenValue;

private OverriddenParam(NewOverriddenParam newOverriddenParam) {
this.key = newOverriddenParam.key();
this.overriddenValue = newOverriddenParam.overriddenValue;
}

public String key() {
return key;
}

@Nullable
public String overriddenValue() {
return overriddenValue;
}

}

/**
* This method is executed when server is started.
*/
void define(Context context);

}

+ 23
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/profile/package-info.java 查看文件

@@ -0,0 +1,23 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
@ParametersAreNonnullByDefault
package org.sonar.api.server.profile;

import javax.annotation.ParametersAreNonnullByDefault;

+ 69
- 0
sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfileAnnotationLoaderTest.java 查看文件

@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.server.profile;

import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
import org.sonar.check.BelongsToProfile;
import org.sonar.check.Priority;

import static org.assertj.core.api.Assertions.assertThat;

public class BuiltInQualityProfileAnnotationLoaderTest {

@Test
public void shouldParseAnnotatedClasses() {
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newProfile = context.createBuiltInQualityProfile("Foo way", "java");

new BuiltInQualityProfileAnnotationLoader().load(newProfile, "squid", FakeRule.class, RuleNoProfile.class);
newProfile.done();

assertThat(context.profile("java", "Foo way").rule(RuleKey.of("squid", "fake")).overriddenSeverity()).isEqualTo(Severity.BLOCKER);
}

@Test
public void shouldParseOnlyWantedProfile() {
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
NewBuiltInQualityProfile newProfile = context.createBuiltInQualityProfile("Foo way", "java");

new BuiltInQualityProfileAnnotationLoader().load(newProfile, "squid", FakeRule.class, RuleOnOtherProfile.class, RuleNoProfile.class);
newProfile.done();

assertThat(context.profile("java", "Foo way").rule(RuleKey.of("squid", "fake"))).isNotNull();
assertThat(context.profile("java", "Foo way").rule(RuleKey.of("squid", "other"))).isNull();
}
}

@BelongsToProfile(title = "Other profile", priority = Priority.BLOCKER)
@org.sonar.check.Rule(key = "other", priority = Priority.CRITICAL)
class RuleOnOtherProfile {
}

@org.sonar.check.Rule(key = "no", priority = Priority.CRITICAL)
class RuleNoProfile {
}

@BelongsToProfile(title = "Foo way", priority = Priority.BLOCKER)
@org.sonar.check.Rule(key = "fake", priority = Priority.CRITICAL)
class FakeRule {
}

+ 176
- 0
sonar-plugin-api/src/test/java/org/sonar/api/server/profile/BuiltInQualityProfilesDefinitionTest.java 查看文件

@@ -0,0 +1,176 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.server.profile;

import java.util.Map;
import java.util.function.Consumer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInActiveRule;
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;

public class BuiltInQualityProfilesDefinitionTest {

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void coverage() {
assertThat(new BuiltInQualityProfilesDefinition.Context().profile("Foo", "xoo")).isNull();
}

@Test
public void createEmptyProfile() {
Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
c.createBuiltInQualityProfile("Foo", "xoo").done();
});
assertThat(profiles).containsOnlyKeys("xoo");
assertThat(profiles.get("xoo")).containsOnlyKeys("Foo");
BuiltInQualityProfile profile = profiles.get("xoo").get("Foo");
assertThat(profile.name()).isEqualTo("Foo");
assertThat(profile.language()).isEqualTo("xoo");
assertThat(profile.isDefault()).isFalse();
}

@Test
public void sanityCheck() {
Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
NewBuiltInQualityProfile profile1 = c.createBuiltInQualityProfile("Foo1", "xoo");
NewBuiltInActiveRule rule = profile1.activateRule("repo", "rule");
profile1.done();
NewBuiltInQualityProfile profile2 = c.createBuiltInQualityProfile("Foo2", "xoo");
profile2.done();
NewBuiltInQualityProfile profile3 = c.createBuiltInQualityProfile("Foo1", "xoo2");
profile3.done();
assertThat(profile1).isEqualTo(profile1);
assertThat(profile1).isNotEqualTo(null);
assertThat(profile1).isNotEqualTo("Foo");
assertThat(profile1).isNotEqualTo(profile2);
assertThat(profile1).isNotEqualTo(profile3);
assertThat(profile1.hashCode()).isNotEqualTo(profile2.hashCode());
assertThat(profile1.toString()).isEqualTo("NewBuiltInQualityProfile{name='Foo1', language='xoo', default='false'}");
assertThat(rule.toString()).isEqualTo("[repository=repo, key=rule]");
});
BuiltInQualityProfile profile1 = profiles.get("xoo").get("Foo1");
BuiltInQualityProfile profile2 = profiles.get("xoo").get("Foo2");
BuiltInQualityProfile profile3 = profiles.get("xoo2").get("Foo1");
assertThat(profile1).isEqualTo(profile1);
assertThat(profile1).isNotEqualTo(null);
assertThat(profile1).isNotEqualTo("Foo");
assertThat(profile1).isNotEqualTo(profile2);
assertThat(profile1).isNotEqualTo(profile3);
assertThat(profile1.hashCode()).isNotEqualTo(profile2.hashCode());
assertThat(profile1.toString()).isEqualTo("BuiltInQualityProfile{name='Foo1', language='xoo', default='false'}");
assertThat(profile1.rule(RuleKey.of("repo", "rule")).toString()).isEqualTo("[repository=repo, key=rule]");
}

@Test
public void createDefaultProfile() {
Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
c.createBuiltInQualityProfile("Foo", "xoo")
.setDefault(true)
.done();
});
assertThat(profiles).containsOnlyKeys("xoo");
assertThat(profiles.get("xoo")).containsOnlyKeys("Foo");
BuiltInQualityProfile profile = profiles.get("xoo").get("Foo");
assertThat(profile.name()).isEqualTo("Foo");
assertThat(profile.language()).isEqualTo("xoo");
assertThat(profile.isDefault()).isTrue();
}

@Test
public void duplicateProfile() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("There is already a quality profile with name 'Foo' for language 'xoo'");
define(c -> {
c.createBuiltInQualityProfile("Foo", "xoo").done();
c.createBuiltInQualityProfile("Foo", "xoo").done();
});
}

@Test
public void createProfileWithRules() {
Map<String, Map<String, BuiltInQualityProfile>> profiles = define(c -> {
NewBuiltInQualityProfile profile = c.createBuiltInQualityProfile("Foo", "xoo");
profile.activateRule("repo", "ruleWithoutParam");
profile.activateRule("repo", "ruleWithSeverity").overrideSeverity("CRITICAL");
profile.activateRule("repo", "ruleWithParam").overrideParam("param", "value");
profile.done();
});
assertThat(profiles).containsOnlyKeys("xoo");
assertThat(profiles.get("xoo")).containsOnlyKeys("Foo");
BuiltInQualityProfile profile = profiles.get("xoo").get("Foo");
assertThat(profile.name()).isEqualTo("Foo");
assertThat(profile.language()).isEqualTo("xoo");
assertThat(profile.isDefault()).isFalse();
assertThat(profile.rules())
.extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::repoKey, BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey,
BuiltInQualityProfilesDefinition.BuiltInActiveRule::overriddenSeverity, r -> r.overriddenParams().size())
.containsOnly(
tuple("repo", "ruleWithoutParam", null, 0),
tuple("repo", "ruleWithSeverity", "CRITICAL", 0),
tuple("repo", "ruleWithParam", null, 1));
assertThat(profile.rule(RuleKey.of("repo", "ruleWithParam")).overriddenParam("param").key()).isEqualTo("param");
assertThat(profile.rule(RuleKey.of("repo", "ruleWithParam")).overriddenParam("param").overriddenValue()).isEqualTo("value");
}

@Test
public void createProfileWithDuplicateRules() {

define(c -> {
NewBuiltInQualityProfile profile = c.createBuiltInQualityProfile("Foo", "xoo");
profile.activateRule("repo", "rule");

thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("The rule 'repo:rule' is already activated");

profile.activateRule("repo", "rule");
});
}

private Map<String, Map<String, BuiltInQualityProfile>> define(Consumer<BuiltInQualityProfilesDefinition.Context> consumer) {
BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
new FakeProfile(consumer).define(context);
return context.profilesByLanguageAndName();
}

private class FakeProfile implements BuiltInQualityProfilesDefinition {

private Consumer<Context> consumer;

public FakeProfile(Consumer<Context> consumer) {
this.consumer = consumer;
}

@Override
public void define(Context context) {
consumer.accept(context);
}

}

}

+ 4
- 4
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionAnnotationLoaderTest.java 查看文件

@@ -60,9 +60,9 @@ public class RulesDefinitionAnnotationLoaderTest {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
NewRule newRule = annotationLoader.loadRule(newRepository, RuleWithProperty.class);
newRule.setName("Overriden name");
newRule.setName("Overridden name");
newRule.param("property").setDefaultValue("true");
newRule.param("property").setDescription("Overriden");
newRule.param("property").setDescription("Overridden");
newRepository.done();

RulesDefinition.Repository repository = context.repository("squid");
@@ -70,14 +70,14 @@ public class RulesDefinitionAnnotationLoaderTest {
RulesDefinition.Rule rule = repository.rules().get(0);
assertThat(rule.key()).isEqualTo("foo");
assertThat(rule.status()).isEqualTo(RuleStatus.BETA);
assertThat(rule.name()).isEqualTo("Overriden name");
assertThat(rule.name()).isEqualTo("Overridden name");
assertThat(rule.htmlDescription()).isEqualTo("Foo Bar");
assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
assertThat(rule.params()).hasSize(1);

RulesDefinition.Param prop = rule.param("property");
assertThat(prop.key()).isEqualTo("property");
assertThat(prop.description()).isEqualTo("Overriden");
assertThat(prop.description()).isEqualTo("Overridden");
assertThat(prop.defaultValue()).isEqualTo("true");
assertThat(prop.type()).isEqualTo(RuleParamType.STRING);
}

+ 2
- 2
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DeprecatedIssueWrapper.java 查看文件

@@ -66,8 +66,8 @@ public class DeprecatedIssueWrapper implements Issue {

@Override
public String severity() {
Severity overridenSeverity = newIssue.overriddenSeverity();
return overridenSeverity != null ? overridenSeverity.name() : null;
Severity overriddenSeverity = newIssue.overriddenSeverity();
return overriddenSeverity != null ? overriddenSeverity.name() : null;
}

@Override

+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/LocalIssueTracking.java 查看文件

@@ -198,7 +198,7 @@ public class LocalIssueTracking {
tracked.setCreationDate(new Date(dto.getCreationDate()));

if (dto.getManualSeverity()) {
// Severity overriden by user
// Severity overridden by user
tracked.setSeverity(dto.getSeverity().name());
}
mergeTo.add(tracked);

+ 1
- 1
tests/src/test/java/org/sonarqube/tests/issue/CustomRulesTest.java 查看文件

@@ -62,7 +62,7 @@ public class CustomRulesTest extends AbstractIssueTest {
Issue issue = issues.get(0);
assertThat(issue.ruleKey()).isEqualTo("xoo:MyCustomRule");
assertThat(issue.line()).isEqualTo(2);
// Overriden in quality profile
// Overridden in quality profile
assertThat(issue.severity()).isEqualTo("CRITICAL");
}
}

+ 2
- 0
tests/src/test/java/org/sonarqube/tests/qualityProfile/BuiltInQualityProfilesTest.java 查看文件

@@ -63,6 +63,7 @@ public class BuiltInQualityProfilesTest {
tuple("Basic", "xoo", true, true),
tuple("Sonar way", "xoo", true, false),
tuple("empty", "xoo", true, false),
tuple("test BuiltInQualityProfilesDefinition", "xoo", true, false),
tuple("Basic", "xoo2", true, true),
tuple("Sonar way", "xoo2", true, false));
}
@@ -77,6 +78,7 @@ public class BuiltInQualityProfilesTest {
tuple("default-organization", "Basic", "xoo", true, true),
tuple("default-organization", "Sonar way", "xoo", true, false),
tuple("default-organization", "empty", "xoo", true, false),
tuple("default-organization", "test BuiltInQualityProfilesDefinition", "xoo", true, false),
tuple("default-organization", "Basic", "xoo2", true, true),
tuple("default-organization", "Sonar way", "xoo2", true, false));
}

Loading…
取消
儲存