import org.sonar.server.property.InternalPropertiesImpl;
import org.sonar.server.property.ws.PropertiesWs;
import org.sonar.server.qualitygate.QualityGateModule;
+import org.sonar.server.qualityprofile.DefinedQProfileRepositoryImpl;
import org.sonar.server.qualityprofile.QProfileBackuperImpl;
import org.sonar.server.qualityprofile.QProfileComparison;
import org.sonar.server.qualityprofile.QProfileCopier;
OrganizationsWsModule.class,
// quality profile
+ DefinedQProfileRepositoryImpl.class,
ActiveRuleIndexer.class,
XMLProfileParser.class,
XMLProfileSerializer.class,
--- /dev/null
+/*
+ * 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.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.profiles.ProfileDefinition;
+
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.codec.binary.Hex.encodeHexString;
+import static org.apache.commons.lang.StringUtils.lowerCase;
+import static org.sonar.db.loadedtemplate.LoadedTemplateDto.QUALITY_PROFILE_TYPE;
+
+/**
+ * Represent a Quality Profile as computed from {@link ProfileDefinition} provided by installed plugins.
+ */
+@Immutable
+public final class DefinedQProfile {
+ private final QProfileName qProfileName;
+ private final boolean isDefault;
+ private final String loadedTemplateType;
+ private final List<org.sonar.api.rules.ActiveRule> activeRules;
+
+ private DefinedQProfile(Builder builder, MessageDigest messageDigest) {
+ this.qProfileName = new QProfileName(builder.language, builder.getName());
+ this.isDefault = builder.declaredDefault || builder.computedDefault;
+ this.loadedTemplateType = computeLoadedTemplateType(this.qProfileName, messageDigest);
+ this.activeRules = ImmutableList.copyOf(builder.activeRules);
+ }
+
+ private static String computeLoadedTemplateType(QProfileName qProfileName, MessageDigest messageDigest) {
+ String qpIdentifier = lowerCase(qProfileName.getLanguage()) + ":" + qProfileName.getName();
+ return format("%s.%s", QUALITY_PROFILE_TYPE, encodeHexString(messageDigest.digest(qpIdentifier.getBytes(UTF_8))));
+ }
+
+ public String getName() {
+ return qProfileName.getName();
+ }
+
+ public String getLanguage() {
+ return qProfileName.getLanguage();
+ }
+
+ public QProfileName getQProfileName() {
+ return qProfileName;
+ }
+
+ public boolean isDefault() {
+ return isDefault;
+ }
+
+ public String getLoadedTemplateType() {
+ return loadedTemplateType;
+ }
+
+ public List<org.sonar.api.rules.ActiveRule> getActiveRules() {
+ return activeRules;
+ }
+
+ static final class Builder {
+ private String language;
+ private String name;
+ private boolean declaredDefault;
+ private boolean computedDefault;
+ private final List<org.sonar.api.rules.ActiveRule> activeRules = new ArrayList<>();
+
+ public Builder setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
+ Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ Builder setDeclaredDefault(boolean declaredDefault) {
+ this.declaredDefault = declaredDefault;
+ return this;
+ }
+
+ boolean isDeclaredDefault() {
+ return declaredDefault;
+ }
+
+ Builder setComputedDefault(boolean flag) {
+ computedDefault = flag;
+ return this;
+ }
+
+ Builder addRules(List<org.sonar.api.rules.ActiveRule> rules) {
+ this.activeRules.addAll(rules);
+ return this;
+ }
+
+ DefinedQProfile build(MessageDigest messageDigest) {
+ return new DefinedQProfile(this, messageDigest);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.List;
+import java.util.Map;
+
+public interface DefinedQProfileRepository {
+ /**
+ * Initializes the Repository.
+ *
+ * This method is intended to be called from a startup task
+ * (see {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup}).
+ *
+ * @throws IllegalStateException if called more then once
+ */
+ void initialize();
+
+ /**
+ * @return an immutable map containing immutable lists.
+ *
+ * @throws IllegalStateException if {@link #initialize()} has not been called
+ */
+ Map<String, List<DefinedQProfile>> getQProfilesByLanguage();
+}
--- /dev/null
+/*
+ * 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.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimaps;
+import java.security.MessageDigest;
+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.apache.commons.codec.digest.DigestUtils;
+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.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+import org.sonar.core.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+import static org.apache.commons.lang.StringUtils.lowerCase;
+
+public class DefinedQProfileRepositoryImpl implements DefinedQProfileRepository {
+ private static final Logger LOGGER = Loggers.get(DefinedQProfileRepositoryImpl.class);
+ private static final String DEFAULT_PROFILE_NAME = "Sonar way";
+
+ private final Languages languages;
+ private final List<ProfileDefinition> definitions;
+ private Map<String, List<DefinedQProfile>> qProfilesByLanguage;
+
+ /**
+ * Requires for pico container when no {@link ProfileDefinition} is defined at all
+ */
+ public DefinedQProfileRepositoryImpl(Languages languages) {
+ this(languages, new ProfileDefinition[0]);
+ }
+
+ public DefinedQProfileRepositoryImpl(Languages languages, ProfileDefinition... definitions) {
+ this.languages = languages;
+ this.definitions = ImmutableList.copyOf(definitions);
+ }
+
+ @Override
+ public void initialize() {
+ checkState(qProfilesByLanguage == 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);
+ this.qProfilesByLanguage = toQualityProfilesByLanguage(rulesProfilesByLanguage);
+ profiler.stopDebug();
+ }
+
+ @Override
+ public Map<String, List<DefinedQProfile>> getQProfilesByLanguage() {
+ checkState(qProfilesByLanguage != null, "initialize must be called first");
+
+ return qProfilesByLanguage;
+ }
+
+ /**
+ * @return profiles by language
+ */
+ private ListMultimap<String, RulesProfile> buildRulesProfilesByLanguage() {
+ ListMultimap<String, RulesProfile> byLang = ArrayListMultimap.create();
+ for (ProfileDefinition definition : definitions) {
+ ValidationMessages validation = ValidationMessages.create();
+ RulesProfile profile = definition.createProfile(validation);
+ validation.log(LOGGER);
+ if (profile != null && !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);
+ }
+ }
+ return byLang;
+ }
+
+ private void validateAndClean(ListMultimap<String, RulesProfile> byLang) {
+ byLang.asMap().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();
+ if (profiles.isEmpty()) {
+ LOGGER.warn("No Quality profiles defined for language: {}", language);
+ return true;
+ }
+ return false;
+ });
+ }
+
+ private Map<String, List<DefinedQProfile>> toQualityProfilesByLanguage(ListMultimap<String, RulesProfile> rulesProfilesByLanguage) {
+ Map<String, List<DefinedQProfile.Builder>> buildersByLanguage = Multimaps.asMap(rulesProfilesByLanguage)
+ .entrySet()
+ .stream()
+ .collect(Collectors.uniqueIndex(Map.Entry::getKey, DefinedQProfileRepositoryImpl::toQualityProfileBuilders));
+ return buildersByLanguage
+ .entrySet()
+ .stream()
+ .filter(DefinedQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
+ .collect(Collectors.uniqueIndex(Map.Entry::getKey, entry -> toQualityProfiles(entry.getValue()), buildersByLanguage.size()));
+ }
+
+ /**
+ * Creates {@link DefinedQProfile.Builder} for each unique quality profile name for a given language.
+ * Builders will have the following properties populated:
+ * <ul>
+ * <li>{@link DefinedQProfile.Builder#language language}: key of the method's parameter</li>
+ * <li>{@link DefinedQProfile.Builder#name name}: {@link RulesProfile#getName()}</li>
+ * <li>{@link DefinedQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile
+ * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li>
+ * <li>{@link DefinedQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all
+ * RulesProfile with a given name</li>
+ * </ul>
+ */
+ private static List<DefinedQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, List<RulesProfile>> rulesProfilesByLanguage) {
+ String language = rulesProfilesByLanguage.getKey();
+ // use a LinkedHashMap to keep order of insertion of RulesProfiles
+ Map<String, DefinedQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
+ for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) {
+ qualityProfileBuildersByName.compute(
+ rulesProfile.getName(),
+ (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile, name));
+ }
+ return ImmutableList.copyOf(qualityProfileBuildersByName.values());
+ }
+
+ /**
+ * Fails if more than one {@link DefinedQProfile.Builder#declaredDefault} is {@code true}, otherwise returns {@code true}.
+ */
+ private static boolean ensureAtMostOneDeclaredDefault(Map.Entry<String, List<DefinedQProfile.Builder>> entry) {
+ Set<String> declaredDefaultProfileNames = entry.getValue().stream()
+ .filter(DefinedQProfile.Builder::isDeclaredDefault)
+ .map(DefinedQProfile.Builder::getName)
+ .collect(Collectors.toSet());
+ checkState(declaredDefaultProfileNames.size() <= 1, "Several Quality profiles are flagged as default for the language %s: %s", entry.getKey(), declaredDefaultProfileNames);
+ return true;
+ }
+
+ private static DefinedQProfile.Builder updateOrCreateBuilder(String language, @Nullable DefinedQProfile.Builder existingBuilder, RulesProfile rulesProfile, String name) {
+ DefinedQProfile.Builder builder = existingBuilder;
+ if (builder == null) {
+ builder = new DefinedQProfile.Builder()
+ .setLanguage(language)
+ .setName(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());
+ }
+
+ private static List<DefinedQProfile> toQualityProfiles(List<DefinedQProfile.Builder> builders) {
+ if (builders.stream().noneMatch(DefinedQProfile.Builder::isDeclaredDefault)) {
+ Optional<DefinedQProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst();
+ if (sonarWayProfile.isPresent()) {
+ sonarWayProfile.get().setComputedDefault(true);
+ } else {
+ builders.iterator().next().setComputedDefault(true);
+ }
+ }
+ MessageDigest md5Digest = DigestUtils.getMd5Digest();
+ return builders.stream()
+ .map(builder -> builder.build(md5Digest))
+ .collect(Collectors.toList(builders.size()));
+ }
+}
*/
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.security.MessageDigest;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-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.apache.commons.codec.digest.DigestUtils;
-import org.sonar.api.profiles.ProfileDefinition;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.ActiveRuleParam;
import org.sonar.api.server.ServerSide;
-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 org.sonar.core.util.stream.Collectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.Pagination;
import org.sonar.db.qualityprofile.QualityProfileDto;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.codec.binary.Hex.encodeHexString;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.apache.commons.lang.StringUtils.lowerCase;
import static org.sonar.db.Pagination.forPage;
-import static org.sonar.db.loadedtemplate.LoadedTemplateDto.QUALITY_PROFILE_TYPE;
/**
* Synchronize Quality profiles during server startup
public class RegisterQualityProfiles {
private static final Logger LOGGER = Loggers.get(RegisterQualityProfiles.class);
- private static final String DEFAULT_PROFILE_NAME = "Sonar way";
private static final Pagination PROCESSED_ORGANIZATIONS_BATCH_SIZE = forPage(1).andSize(2000);
- private final List<ProfileDefinition> definitions;
+ private final DefinedQProfileRepository definedQProfileRepository;
private final DbClient dbClient;
private final QProfileFactory profileFactory;
private final RuleActivator ruleActivator;
- private final Languages languages;
private final ActiveRuleIndexer activeRuleIndexer;
- /**
- * To be kept when no ProfileDefinition are injected
- */
- public RegisterQualityProfiles(DbClient dbClient,
- QProfileFactory profileFactory, CachingRuleActivator ruleActivator, Languages languages, ActiveRuleIndexer activeRuleIndexer) {
- this(dbClient, profileFactory, ruleActivator, Collections.emptyList(), languages, activeRuleIndexer);
- }
-
- public RegisterQualityProfiles(DbClient dbClient,
- QProfileFactory profileFactory, CachingRuleActivator ruleActivator,
- List<ProfileDefinition> definitions, Languages languages, ActiveRuleIndexer activeRuleIndexer) {
+ public RegisterQualityProfiles(DefinedQProfileRepository definedQProfileRepository,
+ DbClient dbClient,
+ QProfileFactory profileFactory, CachingRuleActivator ruleActivator, ActiveRuleIndexer activeRuleIndexer) {
+ this.definedQProfileRepository = definedQProfileRepository;
this.dbClient = dbClient;
this.profileFactory = profileFactory;
this.ruleActivator = ruleActivator;
- this.definitions = definitions;
- this.languages = languages;
this.activeRuleIndexer = activeRuleIndexer;
}
public void start() {
Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Register quality profiles");
- ListMultimap<String, RulesProfile> rulesProfilesByLanguage = buildRulesProfilesByLanguage();
- validateAndClean(rulesProfilesByLanguage);
- Map<String, List<QualityProfile>> qualityProfilesByLanguage = toQualityProfilesByLanguage(rulesProfilesByLanguage);
- if (qualityProfilesByLanguage.isEmpty()) {
- // do not open DB session if there is no quality profile to register
- profiler.stopDebug("No quality profile to register");
- return;
- }
-
+ definedQProfileRepository.initialize();
try (DbSession session = dbClient.openSession(false)) {
List<ActiveRuleChange> changes = new ArrayList<>();
- qualityProfilesByLanguage.entrySet()
+ definedQProfileRepository.getQProfilesByLanguage().entrySet()
.forEach(entry -> registerPerLanguage(session, entry.getValue(), changes));
activeRuleIndexer.index(changes);
profiler.stopDebug();
}
}
- /**
- * @return profiles by language
- */
- private ListMultimap<String, RulesProfile> buildRulesProfilesByLanguage() {
- ListMultimap<String, RulesProfile> byLang = ArrayListMultimap.create();
- for (ProfileDefinition definition : definitions) {
- ValidationMessages validation = ValidationMessages.create();
- RulesProfile profile = definition.createProfile(validation);
- validation.log(LOGGER);
- if (profile != null && !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);
- }
- }
- return byLang;
- }
-
- private void validateAndClean(ListMultimap<String, RulesProfile> byLang) {
- byLang.asMap().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();
- if (profiles.isEmpty()) {
- LOGGER.warn("No Quality profiles defined for language: {}", language);
- return true;
- }
- return false;
- });
- }
-
- private Map<String, List<QualityProfile>> toQualityProfilesByLanguage(ListMultimap<String, RulesProfile> rulesProfilesByLanguage) {
- Map<String, List<QualityProfile.Builder>> buildersByLanguage = Multimaps.asMap(rulesProfilesByLanguage)
- .entrySet()
- .stream()
- .collect(Collectors.uniqueIndex(Map.Entry::getKey, RegisterQualityProfiles::toQualityProfileBuilders));
- return buildersByLanguage
- .entrySet()
- .stream()
- .filter(RegisterQualityProfiles::ensureAtMostOneDeclaredDefault)
- .collect(Collectors.uniqueIndex(Map.Entry::getKey, entry -> toQualityProfiles(entry.getValue()), buildersByLanguage.size()));
- }
-
- /**
- * Creates {@link QualityProfile.Builder} for each unique quality profile name for a given language.
- * Builders will have the following properties populated:
- * <ul>
- * <li>{@link QualityProfile.Builder#language language}: key of the method's parameter</li>
- * <li>{@link QualityProfile.Builder#name name}: {@link RulesProfile#getName()}</li>
- * <li>{@link QualityProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile
- * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li>
- * <li>{@link QualityProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all
- * RulesProfile with a given name</li>
- * </ul>
- */
- private static List<QualityProfile.Builder> toQualityProfileBuilders(Map.Entry<String, List<RulesProfile>> rulesProfilesByLanguage) {
- String language = rulesProfilesByLanguage.getKey();
- // use a LinkedHashMap to keep order of insertion of RulesProfiles
- Map<String, QualityProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
- for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) {
- qualityProfileBuildersByName.compute(
- rulesProfile.getName(),
- (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile, name));
- }
- return ImmutableList.copyOf(qualityProfileBuildersByName.values());
- }
-
- /**
- * Fails if more than one {@link QualityProfile.Builder#declaredDefault} is {@code true}, otherwise returns {@code true}.
- */
- private static boolean ensureAtMostOneDeclaredDefault(Map.Entry<String, List<QualityProfile.Builder>> entry) {
- Set<String> declaredDefaultProfileNames = entry.getValue().stream()
- .filter(QualityProfile.Builder::isDeclaredDefault)
- .map(QualityProfile.Builder::getName)
- .collect(Collectors.toSet());
- checkState(declaredDefaultProfileNames.size() <= 1, "Several Quality profiles are flagged as default for the language %s: %s", entry.getKey(), declaredDefaultProfileNames);
- return true;
- }
-
- private static QualityProfile.Builder updateOrCreateBuilder(String language, @Nullable QualityProfile.Builder existingBuilder, RulesProfile rulesProfile, String name) {
- QualityProfile.Builder builder = existingBuilder;
- if (builder == null) {
- builder = new QualityProfile.Builder()
- .setLanguage(language)
- .setName(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.declaredDefault || declaredDefault)
- .addRules(rulesProfile.getActiveRules());
- }
-
- private static List<QualityProfile> toQualityProfiles(List<QualityProfile.Builder> builders) {
- if (builders.stream().noneMatch(QualityProfile.Builder::isDeclaredDefault)) {
- Optional<QualityProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst();
- if (sonarWayProfile.isPresent()) {
- sonarWayProfile.get().setComputedDefault(true);
- } else {
- builders.iterator().next().setComputedDefault(true);
- }
- }
- MessageDigest md5Digest = DigestUtils.getMd5Digest();
- return builders.stream()
- .map(builder -> builder.build(md5Digest))
- .collect(Collectors.toList(builders.size()));
- }
-
- private void registerPerLanguage(DbSession session, List<QualityProfile> qualityProfiles, List<ActiveRuleChange> changes) {
+ private void registerPerLanguage(DbSession session, List<DefinedQProfile> qualityProfiles, List<ActiveRuleChange> changes) {
qualityProfiles.forEach(qp -> registerPerQualityProfile(session, qp, changes));
session.commit();
}
- private void registerPerQualityProfile(DbSession session, QualityProfile qualityProfile, List<ActiveRuleChange> changes) {
+ private void registerPerQualityProfile(DbSession session, DefinedQProfile qualityProfile, List<ActiveRuleChange> changes) {
LOGGER.info("Register profile {}", qualityProfile.getQProfileName());
List<OrganizationDto> organizationDtos;
}
}
- private List<OrganizationDto> getOrganizationsWithoutQP(DbSession session, QualityProfile qualityProfile) {
+ private List<OrganizationDto> getOrganizationsWithoutQP(DbSession session, DefinedQProfile qualityProfile) {
return dbClient.organizationDao().selectOrganizationsWithoutLoadedTemplate(session,
qualityProfile.getLoadedTemplateType(), PROCESSED_ORGANIZATIONS_BATCH_SIZE);
}
- private void registerPerQualityProfileAndOrganization(DbSession session, QualityProfile qualityProfile, OrganizationDto organization, List<ActiveRuleChange> changes) {
+ private void registerPerQualityProfileAndOrganization(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List<ActiveRuleChange> changes) {
LOGGER.debug("Register profile {} for organization {}", qualityProfile.getQProfileName(), organization.getKey());
QualityProfileDto profileDto = dbClient.qualityProfileDao().selectByNameAndLanguage(organization, qualityProfile.getName(), qualityProfile.getLanguage(), session);
session.commit();
}
- private static final class QualityProfile {
- private final QProfileName qProfileName;
- private final boolean isDefault;
- private final String loadedTemplateType;
- private final List<org.sonar.api.rules.ActiveRule> activeRules;
-
- public QualityProfile(Builder builder, MessageDigest messageDigest) {
- this.qProfileName = new QProfileName(builder.getLanguage(), builder.getName());
- this.isDefault = builder.declaredDefault || builder.computedDefault;
- this.loadedTemplateType = computeLoadedTemplateType(this.qProfileName, messageDigest);
- this.activeRules = ImmutableList.copyOf(builder.activeRules);
- }
-
- private static String computeLoadedTemplateType(QProfileName qProfileName, MessageDigest messageDigest) {
- String qpIdentifier = lowerCase(qProfileName.getLanguage(), Locale.ENGLISH) + ":" + qProfileName.getName();
- return format("%s.%s", QUALITY_PROFILE_TYPE, encodeHexString(messageDigest.digest(qpIdentifier.getBytes(UTF_8))));
- }
-
- public String getName() {
- return qProfileName.getName();
- }
-
- public String getLanguage() {
- return qProfileName.getLanguage();
- }
-
- public QProfileName getQProfileName() {
- return qProfileName;
- }
-
- public boolean isDefault() {
- return isDefault;
- }
-
- public String getLoadedTemplateType() {
- return loadedTemplateType;
- }
-
- public List<org.sonar.api.rules.ActiveRule> getActiveRules() {
- return activeRules;
- }
-
- private static final class Builder {
- private String language;
- private String name;
- private boolean declaredDefault;
- private boolean computedDefault;
- private List<org.sonar.api.rules.ActiveRule> activeRules = new ArrayList<>();
-
- public String getLanguage() {
- return language;
- }
-
- public Builder setLanguage(String language) {
- this.language = language;
- return this;
- }
-
- Builder setName(String name) {
- this.name = name;
- return this;
- }
-
- String getName() {
- return name;
- }
-
- Builder setDeclaredDefault(boolean declaredDefault) {
- this.declaredDefault = declaredDefault;
- return this;
- }
-
- boolean isDeclaredDefault() {
- return declaredDefault;
- }
-
- Builder setComputedDefault(boolean flag) {
- computedDefault = flag;
- return this;
- }
-
- Builder addRules(List<org.sonar.api.rules.ActiveRule> rules) {
- this.activeRules.addAll(rules);
- return this;
- }
-
- QualityProfile build(MessageDigest messageDigest) {
- return new QualityProfile(this, messageDigest);
- }
- }
- }
}
@Test
public void create_add_current_user_as_member_of_organization() throws OrganizationCreation.KeyConflictException {
mockForSuccessfulInsert(SOME_UUID, SOME_DATE);
+ definedQProfileRepositoryRule.initialize();
underTest.create(dbSession, SOME_USER_ID, FULL_POPULATED_NEW_ORGANIZATION);
when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
mockForSuccessfulInsert(SOME_UUID, SOME_DATE);
enableCreatePersonalOrg(true);
+ definedQProfileRepositoryRule.initialize();
underTest.createForUser(dbSession, user);
--- /dev/null
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+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.server.language.LanguageTesting;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefinedQProfileRepositoryImplTest {
+ private static final Language FOO_LANGUAGE = LanguageTesting.newLanguage("foo", "foo", "foo");
+ private static final String SONAR_WAY_QP_NAME = "Sonar way";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void getQProfilesByLanguage_throws_ISE_if_called_before_initialize() {
+ DefinedQProfileRepositoryImpl underTest = new DefinedQProfileRepositoryImpl(new Languages());
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("initialize must be called first");
+
+ underTest.getQProfilesByLanguage();
+ }
+
+ @Test
+ public void initialize_throws_ISE_if_called_twice() {
+ DefinedQProfileRepositoryImpl underTest = new DefinedQProfileRepositoryImpl(new Languages());
+ underTest.initialize();
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("initialize must be called only once");
+
+ underTest.initialize();
+ }
+
+ @Test
+ public void initialize_creates_no_DefinedQProfile_when_there_is_no_definition() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(FOO_LANGUAGE));
+
+ underTest.initialize();
+
+ assertThat(underTest.getQProfilesByLanguage()).isEmpty();
+ }
+
+ @Test
+ public void initialize_creates_no_DefinedQProfile_when_all_definitions_apply_to_non_defined_languages() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(), new DummyProfileDefinition("foo", "P1", false));
+
+ underTest.initialize();
+
+ assertThat(underTest.getQProfilesByLanguage()).isEmpty();
+ }
+
+ @Test
+ public void initialize_throws_IAE_if_profileDefinition_creates_RulesProfile_with_null_name() {
+ DummyProfileDefinition definition = new DummyProfileDefinition("foo", null, false);
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(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);
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(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_qp_of_a_language_default_even_if_not_flagged_as_so() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", false));
+
+ underTest.initialize();
+
+ Map<String, List<DefinedQProfile>> qProfilesByLanguage = underTest.getQProfilesByLanguage();
+ assertThat(qProfilesByLanguage)
+ .hasSize(1)
+ .containsOnlyKeys(FOO_LANGUAGE.getKey());
+ assertThat(qProfilesByLanguage.get(FOO_LANGUAGE.getKey()))
+ .extracting(DefinedQProfile::isDefault)
+ .containsExactly(true);
+ }
+
+ @Test
+ public void initialize_makes_single_qp_of_a_language_default_even_if_flagged_as_so() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", true));
+
+ underTest.initialize();
+
+ Map<String, List<DefinedQProfile>> qProfilesByLanguage = underTest.getQProfilesByLanguage();
+ assertThat(qProfilesByLanguage)
+ .hasSize(1)
+ .containsOnlyKeys(FOO_LANGUAGE.getKey());
+ assertThat(qProfilesByLanguage.get(FOO_LANGUAGE.getKey()))
+ .extracting(DefinedQProfile::isDefault)
+ .containsExactly(true);
+ }
+
+ @Test
+ public void initialize_makes_first_qp_of_a_language_default_when_none_flagged_as_so() {
+ List<DummyProfileDefinition> definitions = new ArrayList<>(
+ asList(new DummyProfileDefinition("foo", "foo1", false), new DummyProfileDefinition("foo", "foo2", false)));
+ Collections.shuffle(definitions);
+ String firstQPName = definitions.get(0).getName();
+ String secondQPName = definitions.get(1).getName();
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), definitions.stream().toArray(ProfileDefinition[]::new));
+
+ underTest.initialize();
+
+ Map<String, List<DefinedQProfile>> qProfilesByLanguage = underTest.getQProfilesByLanguage();
+ assertThat(qProfilesByLanguage)
+ .hasSize(1)
+ .containsOnlyKeys(FOO_LANGUAGE.getKey());
+ List<DefinedQProfile> fooDefinedQProfiles = qProfilesByLanguage.get(FOO_LANGUAGE.getKey());
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::getName)
+ .containsExactly(firstQPName, secondQPName);
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::isDefault)
+ .containsExactly(true, false);
+ }
+
+ @Test
+ public void initialize_fails_with_ISE_when_two_sq_with_different_name_are_default_for_the_same_language() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo2", true));
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Several Quality profiles are flagged as default for the language foo: [foo1, foo2]");
+
+ underTest.initialize();
+ }
+
+ @Test
+ public void initialize_create_qp_as_default_even_if_only_one_profile_with_given_name_has_default_flag_true() {
+ String name = "doh";
+ boolean flag = new Random().nextBoolean();
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", name, flag), new DummyProfileDefinition("foo", name, !flag));
+
+ underTest.initialize();
+
+ assertThat(underTest.getQProfilesByLanguage().get(FOO_LANGUAGE.getKey()))
+ .extracting(DefinedQProfile::isDefault)
+ .containsExactly(true);
+ }
+
+ @Test
+ public void initialize_creates_single_qp_if_several_profile_have_the_same_name_for_a_given_language() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo1", true));
+
+ underTest.initialize();
+
+ Map<String, List<DefinedQProfile>> qProfilesByLanguage = underTest.getQProfilesByLanguage();
+ assertThat(qProfilesByLanguage)
+ .hasSize(1)
+ .containsOnlyKeys(FOO_LANGUAGE.getKey());
+ assertThat(qProfilesByLanguage.get(FOO_LANGUAGE.getKey()))
+ .extracting(DefinedQProfile::getName)
+ .containsExactly("foo1");
+ }
+
+ @Test
+ public void initialize_creates_qp_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(
+ new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "doh", false), new DummyProfileDefinition("foo", "boo", false),
+ new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", false));
+
+ underTest.initialize();
+
+ Map<String, List<DefinedQProfile>> qProfilesByLanguage = underTest.getQProfilesByLanguage();
+ assertThat(qProfilesByLanguage)
+ .hasSize(1)
+ .containsOnlyKeys(FOO_LANGUAGE.getKey());
+ List<DefinedQProfile> fooDefinedQProfiles = qProfilesByLanguage.get(FOO_LANGUAGE.getKey());
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::getName)
+ .containsExactly("doh", "boo", SONAR_WAY_QP_NAME, "goo");
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::isDefault)
+ .containsExactly(false, false, true, false);
+ }
+
+ @Test
+ public void initialize_does_not_create_Sonar_Way_as_default_if_other_profile_is_defined_as_default() {
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(
+ new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", true));
+
+ underTest.initialize();
+
+ List<DefinedQProfile> fooDefinedQProfiles = underTest.getQProfilesByLanguage().get(FOO_LANGUAGE.getKey());
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::getName)
+ .containsExactly(SONAR_WAY_QP_NAME, "goo");
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::isDefault)
+ .containsExactly(false, true);
+ }
+
+ @Test
+ public void initialize_matches_Sonar_Way_default_with_case_sensitivity() {
+ String sonarWayInOtherCase = SONAR_WAY_QP_NAME.toUpperCase();
+ DefinedQProfileRepository underTest = new DefinedQProfileRepositoryImpl(
+ new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "goo", false), new DummyProfileDefinition("foo", sonarWayInOtherCase, false));
+
+ underTest.initialize();
+
+ List<DefinedQProfile> fooDefinedQProfiles = underTest.getQProfilesByLanguage().get(FOO_LANGUAGE.getKey());
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::getName)
+ .containsExactly("goo", sonarWayInOtherCase);
+ assertThat(fooDefinedQProfiles)
+ .extracting(DefinedQProfile::isDefault)
+ .containsExactly(true, false);
+ }
+
+ private static final class DummyProfileDefinition extends ProfileDefinition {
+ private final String language;
+ private final String name;
+ private final boolean defaultProfile;
+
+ private DummyProfileDefinition(String language, String name, boolean defaultProfile) {
+ this.language = language;
+ this.name = name;
+ this.defaultProfile = defaultProfile;
+ }
+
+ @Override
+ public RulesProfile createProfile(ValidationMessages validation) {
+ RulesProfile res = RulesProfile.create(name, language);
+ res.setDefaultProfile(defaultProfile);
+ return res;
+ }
+
+ String getLanguage() {
+ return language;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ boolean isDefaultProfile() {
+ return defaultProfile;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.rules.ExternalResource;
+import org.sonar.api.resources.Language;
+import org.sonar.core.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.sonar.core.util.stream.Collectors.toList;
+
+public class DefinedQProfileRepositoryRule extends ExternalResource implements DefinedQProfileRepository {
+ private boolean initializeCalled = false;
+ private Map<String, List<DefinedQProfile>> qProfilesbyLanguage = new HashMap<>();
+
+ @Override
+ protected void before() throws Throwable {
+ this.initializeCalled = false;
+ this.qProfilesbyLanguage.clear();
+ }
+
+ @Override
+ public void initialize() {
+ checkState(!initializeCalled, "initialize must be called only once");
+ this.initializeCalled = true;
+ }
+
+ @Override
+ public Map<String, List<DefinedQProfile>> getQProfilesByLanguage() {
+ checkState(initializeCalled, "initialize must be called first");
+
+ return ImmutableMap.copyOf(qProfilesbyLanguage);
+ }
+
+ public boolean isInitialized() {
+ return initializeCalled;
+ }
+
+ public DefinedQProfileRepositoryRule set(String languageKey, DefinedQProfile first, DefinedQProfile... others) {
+ qProfilesbyLanguage.put(
+ languageKey,
+ Stream.concat(Stream.of(first), Arrays.stream(others)).collect(toList(1 + others.length)));
+ return this;
+ }
+
+ public DefinedQProfile add(Language language, String profileName) {
+ return add(language, profileName, false);
+ }
+
+ public DefinedQProfile add(Language language, String profileName, boolean isDefault) {
+ DefinedQProfile definedQProfile = create(language, profileName, isDefault);
+ qProfilesbyLanguage.compute(language.getKey(),
+ (key, existing) -> {
+ if (existing == null) {
+ return ImmutableList.of(definedQProfile);
+ }
+ return Stream.concat(existing.stream(), Stream.of(definedQProfile)).collect(Collectors.toList(existing.size() + 1));
+ });
+ return definedQProfile;
+ }
+
+ private DefinedQProfile create(Language language, String profileName, boolean isDefault) {
+ return new DefinedQProfile.Builder()
+ .setLanguage(language.getKey())
+ .setName(profileName)
+ .setDeclaredDefault(isDefault)
+ .build(DigestUtils.getMd5Digest());
+ }
+}
}
RULE_DEFS.set(rules);
PROFILE_DEFS.set(profile);
- tester.get(Platform.class).executeStartupTasks();
+ tester.get(Platform.class).restart();
db = tester.get(DbClient.class);
dbSession = tester.get(DbClient.class).openSession(false);
@Test
public void verify_toString() {
assertThat(QUALITY_PROFILE.toString()).isEqualTo("QualityProfile{key=qpKey, name=qpName, language=languageKey, rulesUpdatedAt=1274194245000}");
-
}
}
*/
package org.sonar.server.qualityprofile;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
-import java.util.List;
-import org.apache.commons.codec.digest.DigestUtils;
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.System2;
-import org.sonar.api.utils.ValidationMessages;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.tester.UserSessionRule;
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Arrays.asList;
-import static org.apache.commons.lang.StringUtils.lowerCase;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.core.util.UtcDateUtils.formatDateTime;
private static final Language FOO_LANGUAGE = LanguageTesting.newLanguage("foo", "foo", "foo");
private static final Language BAR_LANGUAGE = LanguageTesting.newLanguage("bar", "bar", "bar");
private static final String TABLE_RULES_PROFILES = "RULES_PROFILES";
- private static final String TYPE_QUALITY_PROFILE = "QUALITY_PROFILE";
- private static final String SONAR_WAY_QP_NAME = "Sonar way";
private static final String TABLE_LOADED_TEMPLATES = "loaded_templates";
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();
@Rule
public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public DefinedQProfileRepositoryRule definedQProfileRepositoryRule = new DefinedQProfileRepositoryRule();
private DbClient dbClient = dbTester.getDbClient();
private DbClient mockedDbClient = mock(DbClient.class);
private UuidFactory mockedUuidFactory = mock(UuidFactory.class);
private System2 mockedSystem2 = mock(System2.class);
private ActiveRuleIndexer mockedActiveRuleIndexer = mock(ActiveRuleIndexer.class);
+ private RegisterQualityProfiles underTest = new RegisterQualityProfiles(
+ definedQProfileRepositoryRule,
+ dbClient,
+ new QProfileFactory(dbClient, mockedUuidFactory, mockedSystem2),
+ new CachingRuleActivator(mockedSystem2, dbClient, null, new CachingRuleActivatorContextFactory(dbClient), null, null, userSessionRule),
+ mockedActiveRuleIndexer);
@Test
- public void no_action_in_DB_nor_index_when_there_is_no_definition() {
- RegisterQualityProfiles underTest = mockedDBAndEs(Collections.emptyList(), new Languages(FOO_LANGUAGE));
-
- underTest.start();
-
- verifyZeroInteractions(mockedDbClient, mockedActiveRuleIndexer);
- }
-
- @Test
- public void no_action_in_DB_nor_index_when_all_definitions_apply_to_non_defined_languages() {
- RegisterQualityProfiles underTest = mockedDBAndEs(Collections.singletonList(new DummyProfileDefinition("foo", "P1", false)), new Languages());
+ public void no_action_in_DB_nothing_to_index_when_there_is_no_DefinedQProfile() {
+ RegisterQualityProfiles underTest = new RegisterQualityProfiles(definedQProfileRepositoryRule, mockedDbClient, null, null, mockedActiveRuleIndexer);
underTest.start();
- verifyZeroInteractions(mockedDbClient, mockedActiveRuleIndexer);
+ assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue();
+ verify(mockedDbClient).openSession(false);
+ verify(mockedActiveRuleIndexer).index(Collections.emptyList());
+ verifyNoMoreInteractions(mockedDbClient, mockedActiveRuleIndexer);
}
@Test
- public void start_throws_IAE_if_profileDefinition_creates_RulesProfile_with_null_name() {
- DummyProfileDefinition definition = new DummyProfileDefinition("foo", null, false);
- RegisterQualityProfiles underTest = mockedDBAndEs(Collections.singletonList(definition), new Languages());
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Profile created by Definition " + definition + " can't have a blank name");
-
- underTest.start();
- }
-
- @Test
- public void start_throws_IAE_if_profileDefinition_creates_RulesProfile_with_empty_name() {
- DummyProfileDefinition definition = new DummyProfileDefinition("foo", "", false);
- RegisterQualityProfiles underTest = mockedDBAndEs(Collections.singletonList(definition), new Languages());
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Profile created by Definition " + definition + " can't have a blank name");
-
- underTest.start();
- }
-
- @Test
- public void start_creates_qp_if_language_exists_and_store_flag_in_loaded_templates_for_each_organization_id_BD() {
+ public void start_creates_qp_and_store_flag_in_loaded_templates_for_each_organization_id_BD() {
OrganizationDto otherOrganization = dbTester.organizations().insert();
String[] uuids = {"uuid 1", "uuid 2"};
Long[] dates = {2_456_789L, 6_123_789L};
String[] formattedDates = {formatDateTime(new Date(dates[0])), formatDateTime(new Date(dates[1]))};
- DummyProfileDefinition qpDefinition = new DummyProfileDefinition("foo", "foo1", false);
- RegisterQualityProfiles underTest = mockedEs(Collections.singletonList(qpDefinition), new Languages(FOO_LANGUAGE));
+ DefinedQProfile definedQProfile = definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1");
mockForQPInserts(uuids, dates);
underTest.start();
+ assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue();
Arrays.asList(dbTester.getDefaultOrganization(), otherOrganization)
.forEach(organization -> {
QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1");
assertThat(dto.getRulesUpdatedAt()).isIn(formattedDates);
assertThat(dto.getLastUsed()).isNull();
assertThat(dto.getUserUpdatedAt()).isNull();
- assertThat(dto.isDefault()).isTrue();
+ assertThat(dto.isDefault()).isFalse();
- assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(computeLoadedTemplateType(qpDefinition), organization.getUuid(), dbTester.getSession()))
+ assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession()))
.isEqualTo(1);
});
assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(2);
}
@Test
- public void start_makes_single_qp_of_a_language_default_even_if_not_flagged_as_so() {
- RegisterQualityProfiles underTest = mockedEs(Collections.singletonList(new DummyProfileDefinition("foo", "foo1", false)), new Languages(FOO_LANGUAGE));
- mockForSingleQPInsert();
+ public void start_persists_default_flag_of_DefinedQualityProfile() {
+ definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1", false);
+ definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo2", true);
+ definedQProfileRepositoryRule.add(BAR_LANGUAGE, "bar1", true);
+ definedQProfileRepositoryRule.add(BAR_LANGUAGE, "bar2", false);
+ mockForQPInserts(new String[]{"uuid1", "uuid2", "uuid3", "uuid4"}, new Long[]{ 1L, 2L, 3L, 4L});
underTest.start();
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo1").isDefault()).isTrue();
- }
-
- @Test
- public void start_makes_single_qp_of_a_language_default_even_if_flagged_as_so() {
- RegisterQualityProfiles underTest = mockedEs(Collections.singletonList(new DummyProfileDefinition("foo", "foo1", true)), new Languages(FOO_LANGUAGE));
- mockForSingleQPInsert();
-
- underTest.start();
-
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo1").isDefault()).isTrue();
- }
-
- @Test
- public void start_makes_first_qp_of_a_language_default_when_none_flagged_as_so() {
- List<ProfileDefinition> definitions = new ArrayList<>(asList(new DummyProfileDefinition("foo", "foo1", false), new DummyProfileDefinition("foo", "foo2", false)));
- Collections.shuffle(definitions);
- String firstQPName = ((DummyProfileDefinition) definitions.iterator().next()).getName();
- RegisterQualityProfiles underTest = mockedEs(definitions, new Languages(FOO_LANGUAGE));
- mockForTwoQPInserts();
-
- underTest.start();
-
- QualityProfileDto dto = getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo1");
- assertThat(dto.isDefault()).isEqualTo(dto.getName().equals(firstQPName));
- dto = getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo2");
- assertThat(dto.isDefault()).isEqualTo(dto.getName().equals(firstQPName));
- assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(2);
+ assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue();
+ assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo1").isDefault()).isFalse();
+ assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo2").isDefault()).isTrue();
+ assertThat(getPersistedQP(dbTester.getDefaultOrganization(), BAR_LANGUAGE, "bar1").isDefault()).isTrue();
+ assertThat(getPersistedQP(dbTester.getDefaultOrganization(), BAR_LANGUAGE, "bar2").isDefault()).isFalse();
}
@Test
public void start_does_not_create_sq_if_loaded_profile_of_organization_already_exists() {
OrganizationDto org1 = dbTester.organizations().insert();
OrganizationDto org2 = dbTester.organizations().insert();
- DummyProfileDefinition qpDefinition = new DummyProfileDefinition("foo", "foo1", false);
- dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(dbTester.getDefaultOrganization().getUuid(), computeLoadedTemplateType(qpDefinition)), dbTester.getSession());
- dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(org1.getUuid(), computeLoadedTemplateType(qpDefinition)), dbTester.getSession());
+ DefinedQProfile definedQProfile = definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1");
+ dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(dbTester.getDefaultOrganization().getUuid(), definedQProfile.getLoadedTemplateType()), dbTester.getSession());
+ dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(org1.getUuid(), definedQProfile.getLoadedTemplateType()), dbTester.getSession());
dbTester.commit();
- RegisterQualityProfiles underTest = mockedEs(Collections.singletonList(qpDefinition), new Languages(FOO_LANGUAGE));
mockForSingleQPInsert();
underTest.start();
- assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(computeLoadedTemplateType(qpDefinition), org2.getUuid(), dbTester.getSession()))
+ assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue();
+ assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile.getLoadedTemplateType(), org2.getUuid(), dbTester.getSession()))
.isEqualTo(1);
assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_LOADED_TEMPLATES)).isEqualTo(3);
assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1);
}
@Test
- public void start_fails_with_ISE_when_two_sq_with_different_name_are_default_for_the_same_language() {
- RegisterQualityProfiles underTest = mockedEs(
- asList(new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo2", true)),
- new Languages(FOO_LANGUAGE));
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Several Quality profiles are flagged as default for the language foo: [foo1, foo2]");
-
- underTest.start();
- }
-
- @Test
- public void starts_creates_single_qp_if_several_profile_have_the_same_name_for_a_given_language() {
- RegisterQualityProfiles underTest = mockedEs(
- asList(new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo1", true)),
- new Languages(FOO_LANGUAGE));
- mockForSingleQPInsert();
-
- underTest.start();
-
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo1").isDefault()).isTrue();
- assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1);
- }
-
- @Test
- public void start_creates_different_qp_and_their_loaded_templates_if_several_profile_has_same_name_for_different_languages() {
+ public void start_creates_different_qps_and_their_loaded_templates_if_several_profile_has_same_name_for_different_languages() {
String uuid1 = "uuid1";
String uuid2 = "uuid2";
long date1 = 2_456_789L;
long date2 = 4_231_654L;
String name = "doh";
- DummyProfileDefinition qpDefinition1 = new DummyProfileDefinition("foo", name, true);
- DummyProfileDefinition qpDefinition2 = new DummyProfileDefinition("bar", name, true);
- RegisterQualityProfiles underTest = mockedEs(asList(qpDefinition1, qpDefinition2), new Languages(FOO_LANGUAGE, BAR_LANGUAGE));
- when(mockedUuidFactory.create()).thenReturn(uuid1).thenReturn(uuid2).thenThrow(new UnsupportedOperationException("uuidFactory should be called only twice"));
- when(mockedSystem2.now()).thenReturn(date1).thenReturn(date2).thenThrow(new UnsupportedOperationException("now should be called only twice"));
+ DefinedQProfile definedQProfile1 = definedQProfileRepositoryRule.add(FOO_LANGUAGE, name, true);
+ DefinedQProfile definedQProfile2 = definedQProfileRepositoryRule.add(BAR_LANGUAGE, name, true);
+ mockForQPInserts(new String[]{uuid1, uuid2}, new Long[]{date1, date2});
underTest.start();
+ assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue();
OrganizationDto organization = dbTester.getDefaultOrganization();
QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, name);
String uuidQP1 = dto.getKee();
assertThat(dto.isDefault()).isTrue();
assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(2);
- assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(computeLoadedTemplateType(qpDefinition1), organization.getUuid(), dbTester.getSession()))
+ assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile1.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession()))
.isEqualTo(1);
- assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(computeLoadedTemplateType(qpDefinition2), organization.getUuid(), dbTester.getSession()))
+ assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile2.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession()))
.isEqualTo(1);
}
- @Test
- public void start_create_qp_as_default_even_if_only_one_profile_with_given_name_has_default_flag_true() {
- String name = "doh";
- RegisterQualityProfiles underTest = mockedEs(
- asList(new DummyProfileDefinition("foo", name, false), new DummyProfileDefinition("foo", name, true)),
- new Languages(FOO_LANGUAGE));
- mockForSingleQPInsert();
-
- underTest.start();
-
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, name).isDefault()).isTrue();
- }
-
- @Test
- public void start_creates_qp_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() {
-
- RegisterQualityProfiles underTest = mockedEs(
- asList(
- new DummyProfileDefinition("foo", "doh", false), new DummyProfileDefinition("foo", "boo", false),
- new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", false)),
- new Languages(FOO_LANGUAGE));
- mockForQPInserts(new String[] {"uuid1", "uuid2", "uuid3", "uuid4"}, new Long[] {2_456_789L, 2_456_789L, 2_456_789L, 2_456_789L});
-
- underTest.start();
-
- Arrays.asList("doh", "boo", "goo")
- .forEach(name -> assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, name).isDefault())
- .describedAs("QP with name " + name + " should not be default")
- .isFalse());
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, SONAR_WAY_QP_NAME).isDefault()).isTrue();
- }
-
- @Test
- public void start_does_not_create_Sonar_Way_as_default_if_other_profile_is_defined_as_default() {
- RegisterQualityProfiles underTest = mockedEs(
- asList(
- new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", true)),
- new Languages(FOO_LANGUAGE));
- mockForTwoQPInserts();
-
- underTest.start();
-
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, SONAR_WAY_QP_NAME).isDefault()).isFalse();
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "goo").isDefault()).isTrue();
- }
-
- @Test
- public void start_matches_Sonar_Way_default_with_case_sensitivity() {
- String sonarWayInOtherCase = SONAR_WAY_QP_NAME.toUpperCase();
- RegisterQualityProfiles underTest = mockedEs(
- asList(
- new DummyProfileDefinition("foo", "goo", false), new DummyProfileDefinition("foo", sonarWayInOtherCase, false)),
- new Languages(FOO_LANGUAGE));
- mockForTwoQPInserts();
-
- underTest.start();
-
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, sonarWayInOtherCase).isDefault()).isFalse();
- assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "goo").isDefault()).isTrue();
- }
-
private void mockForSingleQPInsert() {
mockForSingleQPInsert("generated uuid", 2_456_789);
}
- private void mockForTwoQPInserts() {
- mockForQPInserts(new String[] {"uuid1", "uuid2"}, new Long[] {2_456_789L, 3_789_159L});
- }
-
private void mockForSingleQPInsert(String uuid, long now) {
when(mockedUuidFactory.create()).thenReturn(uuid).thenThrow(new UnsupportedOperationException("uuidFactory should be called only once"));
when(mockedSystem2.now()).thenReturn(now).thenThrow(new UnsupportedOperationException("now should be called only once"));
return dbClient.qualityProfileDao().selectByNameAndLanguage(organization, name, language.getKey(), dbTester.getSession());
}
- private RegisterQualityProfiles mockedDBAndEs(List<ProfileDefinition> definitions, Languages languages) {
- return new RegisterQualityProfiles(mockedDbClient, null, null, definitions, languages, mockedActiveRuleIndexer);
- }
-
- private RegisterQualityProfiles mockedEs(List<ProfileDefinition> definitions, Languages languages) {
- return new RegisterQualityProfiles(
- dbClient,
- new QProfileFactory(dbClient, mockedUuidFactory, mockedSystem2),
- new CachingRuleActivator(mockedSystem2, dbClient, null, new CachingRuleActivatorContextFactory(dbClient), null, null, userSessionRule),
- definitions,
- languages,
- mockedActiveRuleIndexer);
- }
-
- private static final class DummyProfileDefinition extends ProfileDefinition {
- private final String language;
- private final String name;
- private final boolean defaultProfile;
-
- private DummyProfileDefinition(String language, String name, boolean defaultProfile) {
- this.language = language;
- this.name = name;
- this.defaultProfile = defaultProfile;
- }
-
- @Override
- public RulesProfile createProfile(ValidationMessages validation) {
- RulesProfile res = RulesProfile.create(name, language);
- res.setDefaultProfile(defaultProfile);
- return res;
- }
-
- String getLanguage() {
- return language;
- }
-
- String getName() {
- return name;
- }
-
- boolean isDefaultProfile() {
- return defaultProfile;
- }
- }
-
- private String computeLoadedTemplateType(DummyProfileDefinition qpDefinition) {
- String qpIdentifier = lowerCase(qpDefinition.getLanguage()) + ":" + qpDefinition.getName();
- return format("%s.%s", TYPE_QUALITY_PROFILE, DigestUtils.md5Hex(qpIdentifier.getBytes(UTF_8)));
- }
}
dbSession.close();
}
RULE_DEFS.set(rules);
- TESTER.get(Platform.class).executeStartupTasks();
+ TESTER.get(Platform.class).restart();
db = TESTER.get(DbClient.class);
dbSession = TESTER.get(DbClient.class).openSession(false);
dbSession.clearCache();