From 3618f6417ade2a02a139c99b2add0967ca647288 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 24 May 2017 14:12:20 +0200 Subject: SONAR-9302 display built-in Quality profiles --- .../organization/OrganizationCreationImpl.java | 22 +- .../platform/platformlevel/PlatformLevel4.java | 12 +- .../platformlevel/PlatformLevelStartup.java | 8 +- .../server/qualityprofile/BuiltInQProfile.java | 145 +++++++++ .../qualityprofile/BuiltInQProfileCreation.java | 34 +++ .../BuiltInQProfileCreationImpl.java | 61 ++++ .../qualityprofile/BuiltInQProfileInsert.java | 27 ++ .../qualityprofile/BuiltInQProfileInsertImpl.java | 204 +++++++++++++ .../qualityprofile/BuiltInQProfileLoader.java | 44 +++ .../qualityprofile/BuiltInQProfileRepository.java | 42 +++ .../BuiltInQProfileRepositoryImpl.java | 267 +++++++++++++++++ .../CachingBuiltInQProfileCreation.java | 26 ++ .../CachingBuiltInQProfileCreationImpl.java | 28 ++ .../CachingDefinedQProfileCreation.java | 26 -- .../CachingDefinedQProfileCreationImpl.java | 28 -- .../server/qualityprofile/DefinedQProfile.java | 145 --------- .../qualityprofile/DefinedQProfileCreation.java | 34 --- .../DefinedQProfileCreationImpl.java | 61 ---- .../qualityprofile/DefinedQProfileInsert.java | 27 -- .../qualityprofile/DefinedQProfileInsertImpl.java | 203 ------------- .../qualityprofile/DefinedQProfileLoader.java | 44 --- .../qualityprofile/DefinedQProfileRepository.java | 42 --- .../DefinedQProfileRepositoryImpl.java | 267 ----------------- .../MassRegisterQualityProfiles.java | 28 +- .../qualityprofile/QProfileBackuperImpl.java | 2 +- .../server/qualityprofile/QProfileCopier.java | 2 +- .../server/qualityprofile/QProfileFactory.java | 15 +- .../server/qualityprofile/QProfileResetImpl.java | 16 +- .../qualityprofile/RegisterQualityProfiles.java | 52 +++- .../server/qualityprofile/ws/CreateAction.java | 2 +- .../organization/OrganizationCreationImplTest.java | 88 +++--- .../server/organization/ws/CreateActionTest.java | 6 +- .../BuiltInQProfileCreationImplTest.java | 332 +++++++++++++++++++++ .../BuiltInQProfileCreationRule.java | 85 ++++++ .../qualityprofile/BuiltInQProfileInsertRule.java | 62 ++++ .../qualityprofile/BuiltInQProfileLoaderTest.java | 39 +++ .../BuiltInQProfileRepositoryImplTest.java | 291 ++++++++++++++++++ .../BuiltInQProfileRepositoryRule.java | 95 ++++++ .../DefinedQProfileCreationImplTest.java | 332 --------------------- .../DefinedQProfileCreationRule.java | 85 ------ .../qualityprofile/DefinedQProfileInsertRule.java | 62 ---- .../qualityprofile/DefinedQProfileLoaderTest.java | 39 --- .../DefinedQProfileRepositoryImplTest.java | 291 ------------------ .../DefinedQProfileRepositoryRule.java | 95 ------ .../qualityprofile/QProfileFactoryMediumTest.java | 21 +- .../RegisterQualityProfilesMediumTest.java | 3 + .../RegisterQualityProfilesTest.java | 104 ++++--- .../index/ActiveRuleIndexerTest/index.xml | 1 + .../active_rule_with_inherited_inheritance.xml | 2 + .../active_rule_with_overrides_inheritance.xml | 2 + .../one_active_rule.xml | 1 + .../ActiveRuleResultSetIteratorTest/shared.xml | 3 + 52 files changed, 2006 insertions(+), 1947 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreation.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsert.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileLoader.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepository.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreation.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreationImpl.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreation.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreationImpl.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfile.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsert.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsertImpl.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileLoader.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepository.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImpl.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationRule.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertRule.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileLoaderTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationRule.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileInsertRule.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileLoaderTest.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImplTest.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java (limited to 'server/sonar-server') diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java index e588c4314a0..c99bbe23ce1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java @@ -42,9 +42,9 @@ import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserGroupDto; -import org.sonar.server.qualityprofile.DefinedQProfile; -import org.sonar.server.qualityprofile.DefinedQProfileInsert; -import org.sonar.server.qualityprofile.DefinedQProfileRepository; +import org.sonar.server.qualityprofile.BuiltInQProfile; +import org.sonar.server.qualityprofile.BuiltInQProfileInsert; +import org.sonar.server.qualityprofile.BuiltInQProfileRepository; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.user.index.UserIndexer; import org.sonar.server.usergroups.DefaultGroupCreator; @@ -67,15 +67,15 @@ public class OrganizationCreationImpl implements OrganizationCreation { private final UuidFactory uuidFactory; private final OrganizationValidation organizationValidation; private final Settings settings; - private final DefinedQProfileRepository definedQProfileRepository; - private final DefinedQProfileInsert definedQProfileInsert; + private final BuiltInQProfileRepository builtInQProfileRepository; + private final BuiltInQProfileInsert builtInQProfileInsert; private final DefaultGroupCreator defaultGroupCreator; private final UserIndexer userIndexer; private final ActiveRuleIndexer activeRuleIndexer; public OrganizationCreationImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory, OrganizationValidation organizationValidation, Settings settings, UserIndexer userIndexer, - DefinedQProfileRepository definedQProfileRepository, DefinedQProfileInsert definedQProfileInsert, + BuiltInQProfileRepository builtInQProfileRepository, BuiltInQProfileInsert builtInQProfileInsert, DefaultGroupCreator defaultGroupCreator, ActiveRuleIndexer activeRuleIndexer) { this.dbClient = dbClient; this.system2 = system2; @@ -83,8 +83,8 @@ public class OrganizationCreationImpl implements OrganizationCreation { this.organizationValidation = organizationValidation; this.settings = settings; this.userIndexer = userIndexer; - this.definedQProfileRepository = definedQProfileRepository; - this.definedQProfileInsert = definedQProfileInsert; + this.builtInQProfileRepository = builtInQProfileRepository; + this.builtInQProfileInsert = builtInQProfileInsert; this.defaultGroupCreator = defaultGroupCreator; this.activeRuleIndexer = activeRuleIndexer; } @@ -268,16 +268,16 @@ public class OrganizationCreationImpl implements OrganizationCreation { } private void insertQualityProfiles(DbSession dbSession, DbSession batchDbSession, OrganizationDto organization) { - definedQProfileRepository.getQProfilesByLanguage().entrySet() + builtInQProfileRepository.getQProfilesByLanguage().entrySet() .stream() .flatMap(entry -> entry.getValue().stream()) .forEach(profile -> insertQualityProfile(dbSession, batchDbSession, profile, organization)); } - private void insertQualityProfile(DbSession regularSession, DbSession batchDbSession, DefinedQProfile profile, OrganizationDto organization) { + private void insertQualityProfile(DbSession regularSession, DbSession batchDbSession, BuiltInQProfile profile, OrganizationDto organization) { LOGGER.debug("Creating quality profile {} for language {} for organization {}", profile.getName(), profile.getLanguage(), organization.getKey()); - definedQProfileInsert.create(regularSession, batchDbSession, profile, organization); + builtInQProfileInsert.create(regularSession, batchDbSession, profile, organization); } /** diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index d6e90fdd7bb..3d17c9004d6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -141,9 +141,9 @@ 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.DefinedQProfileCreationImpl; -import org.sonar.server.qualityprofile.DefinedQProfileInsertImpl; -import org.sonar.server.qualityprofile.DefinedQProfileRepositoryImpl; +import org.sonar.server.qualityprofile.BuiltInQProfileCreationImpl; +import org.sonar.server.qualityprofile.BuiltInQProfileInsertImpl; +import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryImpl; import org.sonar.server.qualityprofile.QProfileBackuperImpl; import org.sonar.server.qualityprofile.QProfileComparison; import org.sonar.server.qualityprofile.QProfileCopier; @@ -260,8 +260,8 @@ public class PlatformLevel4 extends PlatformLevel { BillingValidationsProxyImpl.class, // quality profile - DefinedQProfileRepositoryImpl.class, - DefinedQProfileInsertImpl.class, + BuiltInQProfileRepositoryImpl.class, + BuiltInQProfileInsertImpl.class, ActiveRuleIndexer.class, XMLProfileParser.class, XMLProfileSerializer.class, @@ -278,7 +278,7 @@ public class PlatformLevel4 extends PlatformLevel { QProfileCopier.class, QProfileBackuperImpl.class, QProfileResetImpl.class, - DefinedQProfileCreationImpl.class, + BuiltInQProfileCreationImpl.class, QProfilesWsModule.class, // rule diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index 9bbfd549765..0fe167f5b89 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -25,10 +25,10 @@ import org.sonar.server.organization.DefaultOrganizationEnforcer; import org.sonar.server.platform.ServerLifecycleNotifier; import org.sonar.server.platform.web.RegisterServletFilters; import org.sonar.server.qualitygate.RegisterQualityGates; -import org.sonar.server.qualityprofile.CachingDefinedQProfileCreationImpl; +import org.sonar.server.qualityprofile.CachingBuiltInQProfileCreationImpl; import org.sonar.server.qualityprofile.CachingRuleActivator; import org.sonar.server.qualityprofile.CachingRuleActivatorContextFactory; -import org.sonar.server.qualityprofile.DefinedQProfileLoader; +import org.sonar.server.qualityprofile.BuiltInQProfileLoader; import org.sonar.server.qualityprofile.MassRegisterQualityProfiles; import org.sonar.server.qualityprofile.RegisterQualityProfiles; import org.sonar.server.rule.RegisterRules; @@ -58,12 +58,12 @@ public class PlatformLevelStartup extends PlatformLevel { RegisterMetrics.class, RegisterQualityGates.class, RegisterRules.class); - add(DefinedQProfileLoader.class); + add(BuiltInQProfileLoader.class); addIfStartupLeader( MassRegisterQualityProfiles.class, CachingRuleActivatorContextFactory.class, CachingRuleActivator.class, - CachingDefinedQProfileCreationImpl.class, + CachingBuiltInQProfileCreationImpl.class, RegisterQualityProfiles.class, RegisterPermissionTemplates.class, RenameDeprecatedPropertyKeys.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java new file mode 100644 index 00000000000..863d6586031 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java @@ -0,0 +1,145 @@ +/* + * 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.CheckForNull; +import javax.annotation.Nullable; +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 BuiltInQProfile { + private final QProfileName qProfileName; + private final boolean isDefault; + private final String loadedTemplateType; + private final List activeRules; + private final QProfileName parentQProfileName; + + private BuiltInQProfile(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); + this.parentQProfileName = builder.parentName == null ? null : new QProfileName(builder.language, builder.parentName); + } + + 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 getActiveRules() { + return activeRules; + } + + @CheckForNull + public QProfileName getParentQProfileName() { + return parentQProfileName; + } + + static final class Builder { + private String language; + private String name; + private boolean declaredDefault; + private boolean computedDefault; + private final List activeRules = new ArrayList<>(); + private String parentName; + + 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 rules) { + this.activeRules.addAll(rules); + return this; + } + + public Builder setParentName(@Nullable String parentName) { + this.parentName = parentName; + return this; + } + + @CheckForNull + public String getParentName() { + return parentName; + } + + BuiltInQProfile build(MessageDigest messageDigest) { + return new BuiltInQProfile(this, messageDigest); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreation.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreation.java new file mode 100644 index 00000000000..af32a40e420 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreation.java @@ -0,0 +1,34 @@ +/* + * 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 org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; + +public interface BuiltInQProfileCreation { + /** + * Persists the specified {@link BuiltInQProfile} of the specified organization and adds any {@link ActiveRuleChange} + * to the specified list. + * + * The session is not commit. + */ + void create(DbSession session, BuiltInQProfile qualityProfile, OrganizationDto organization, List changes); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImpl.java new file mode 100644 index 00000000000..c64758d096a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImpl.java @@ -0,0 +1,61 @@ +/* + * 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 org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.ActiveRuleParam; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.loadedtemplate.LoadedTemplateDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.qualityprofile.QualityProfileDto; + +public class BuiltInQProfileCreationImpl implements BuiltInQProfileCreation { + private final DbClient dbClient; + private final QProfileFactory profileFactory; + private final RuleActivator ruleActivator; + + public BuiltInQProfileCreationImpl(DbClient dbClient, QProfileFactory profileFactory, RuleActivator ruleActivator) { + this.dbClient = dbClient; + this.profileFactory = profileFactory; + this.ruleActivator = ruleActivator; + } + + @Override + public void create(DbSession session, BuiltInQProfile qualityProfile, OrganizationDto organization, List changes) { + QualityProfileDto profileDto = dbClient.qualityProfileDao().selectByNameAndLanguage(organization, qualityProfile.getName(), qualityProfile.getLanguage(), session); + if (profileDto == null) { + profileDto = profileFactory.createBuiltIn(session, organization, qualityProfile.getQProfileName(), qualityProfile.isDefault()); + for (org.sonar.api.rules.ActiveRule activeRule : qualityProfile.getActiveRules()) { + RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()); + RuleActivation activation = new RuleActivation(ruleKey); + activation.setSeverity(activeRule.getSeverity() != null ? activeRule.getSeverity().name() : null); + for (ActiveRuleParam param : activeRule.getActiveRuleParams()) { + activation.setParameter(param.getKey(), param.getValue()); + } + changes.addAll(ruleActivator.activate(session, activation, profileDto)); + } + } + + LoadedTemplateDto template = new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()); + dbClient.loadedTemplateDao().insert(template, session); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsert.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsert.java new file mode 100644 index 00000000000..2b2d8459950 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsert.java @@ -0,0 +1,27 @@ +/* + * 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 org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; + +public interface BuiltInQProfileInsert { + void create(DbSession session, DbSession batchSession, BuiltInQProfile builtInQProfile, OrganizationDto organization); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java new file mode 100644 index 00000000000..1483adc8840 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java @@ -0,0 +1,204 @@ +/* + * 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.base.Splitter; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +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.rule.RuleParamType; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.loadedtemplate.LoadedTemplateDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.qualityprofile.ActiveRuleDto; +import org.sonar.db.qualityprofile.ActiveRuleKey; +import org.sonar.db.qualityprofile.ActiveRuleParamDto; +import org.sonar.db.qualityprofile.QualityProfileDto; +import org.sonar.db.rule.RuleDefinitionDto; +import org.sonar.db.rule.RuleParamDto; +import org.sonar.server.util.TypeValidations; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Objects.requireNonNull; + +public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert { + private final DbClient dbClient; + private final System2 system2; + private final UuidFactory uuidFactory; + private final TypeValidations typeValidations; + private RuleRepository ruleRepository; + + public BuiltInQProfileInsertImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory, TypeValidations typeValidations) { + this.dbClient = dbClient; + this.system2 = system2; + this.uuidFactory = uuidFactory; + this.typeValidations = typeValidations; + } + + @Override + public void create(DbSession session, DbSession batchSession, BuiltInQProfile builtInQProfile, OrganizationDto organization) { + initRuleRepository(batchSession); + + checkArgument(builtInQProfile.getParentQProfileName() == null, "Inheritance of Quality Profiles is not supported yet"); + + Date now = new Date(system2.now()); + QualityProfileDto profileDto = insertQualityProfile(session, builtInQProfile, organization, now); + + List localChanges = builtInQProfile.getActiveRules() + .stream() + .map(activeRule -> insertActiveRule(session, profileDto, activeRule, now.getTime())) + .collect(MoreCollectors.toList()); + + localChanges.forEach(change -> dbClient.qProfileChangeDao().insert(batchSession, change.toDto(null))); + + insertTemplate(session, builtInQProfile, organization); + } + + private void initRuleRepository(DbSession session) { + if (ruleRepository == null) { + ruleRepository = new RuleRepository(dbClient, session); + } + } + + private QualityProfileDto insertQualityProfile(DbSession session, BuiltInQProfile builtInQProfile, OrganizationDto organization, Date now) { + QualityProfileDto profileDto = QualityProfileDto.createFor(uuidFactory.create()) + .setName(builtInQProfile.getName()) + .setOrganizationUuid(organization.getUuid()) + .setLanguage(builtInQProfile.getLanguage()) + .setDefault(builtInQProfile.isDefault()) + .setIsBuiltIn(true) + .setRulesUpdatedAtAsDate(now); + dbClient.qualityProfileDao().insert(session, profileDto); + return profileDto; + } + + private ActiveRuleChange insertActiveRule(DbSession session, QualityProfileDto profileDto, org.sonar.api.rules.ActiveRule activeRule, long now) { + RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()); + RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey) + .orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey)); + + ActiveRuleDto dto = ActiveRuleDto.createFor(profileDto, ruleDefinitionDto); + dto.setSeverity(firstNonNull(activeRule.getSeverity().name(), ruleDefinitionDto.getSeverityString())); + dto.setUpdatedAt(now); + dto.setCreatedAt(now); + dbClient.activeRuleDao().insert(session, dto); + + List paramDtos = insertActiveRuleParams(session, activeRule, ruleKey, dto); + + ActiveRuleChange change = ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(profileDto.getKey(), ruleKey)); + change.setSeverity(dto.getSeverityString()); + paramDtos.forEach(paramDto -> change.setParameter(paramDto.getKey(), paramDto.getValue())); + return change; + } + + private List insertActiveRuleParams(DbSession session, org.sonar.api.rules.ActiveRule activeRule, RuleKey ruleKey, ActiveRuleDto activeRuleDto) { + Map valuesByParamKey = activeRule.getActiveRuleParams() + .stream() + .collect(MoreCollectors.uniqueIndex(ActiveRuleParam::getParamKey, ActiveRuleParam::getValue)); + return ruleRepository.getRuleParams(ruleKey) + .stream() + .map(param -> { + String activeRuleValue = valuesByParamKey.get(param.getName()); + return createParamDto(param, activeRuleValue == null ? param.getDefaultValue() : activeRuleValue); + }) + .filter(Objects::nonNull) + .peek(paramDto -> dbClient.activeRuleDao().insertParam(session, activeRuleDto, paramDto)) + .collect(MoreCollectors.toList()); + } + + @CheckForNull + private ActiveRuleParamDto createParamDto(RuleParamDto param, @Nullable String value) { + if (value == null) { + return null; + } + ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(param); + paramDto.setValue(validateParam(param, value)); + return paramDto; + } + + @CheckForNull + private String validateParam(RuleParamDto ruleParam, String value) { + RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType()); + if (ruleParamType.multiple()) { + List values = newArrayList(Splitter.on(",").split(value)); + typeValidations.validate(values, ruleParamType.type(), ruleParamType.values()); + } else { + typeValidations.validate(value, ruleParamType.type(), ruleParamType.values()); + } + return value; + } + + private void insertTemplate(DbSession session, BuiltInQProfile qualityProfile, OrganizationDto organization) { + LoadedTemplateDto template = new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()); + dbClient.loadedTemplateDao().insert(template, session); + } + + public static class RuleRepository { + private final Map ruleDefinitions; + private final Map> ruleParams; + + private RuleRepository(DbClient dbClient, DbSession session) { + this.ruleDefinitions = dbClient.ruleDao().selectAllDefinitions(session) + .stream() + .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity())); + Map ruleIdsByKey = ruleDefinitions.values() + .stream() + .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getId, RuleDefinitionDto::getKey)); + this.ruleParams = new HashMap<>(ruleIdsByKey.size()); + dbClient.ruleDao().selectRuleParamsByRuleKeys(session, ruleDefinitions.keySet()) + .forEach(ruleParam -> ruleParams.compute( + ruleIdsByKey.get(ruleParam.getRuleId()), + (key, value) -> { + if (value == null) { + return ImmutableSet.of(ruleParam); + } + return ImmutableSet.copyOf(Sets.union(value, Collections.singleton(ruleParam))); + })); + } + + Optional getDefinition(RuleKey ruleKey) { + return Optional.ofNullable(ruleDefinitions.get(requireNonNull(ruleKey, "RuleKey can't be null"))); + } + + Set getRuleParams(RuleKey ruleKey) { + Set res = ruleParams.get(requireNonNull(ruleKey, "RuleKey can't be null")); + return res == null ? Collections.emptySet() : res; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileLoader.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileLoader.java new file mode 100644 index 00000000000..3c6e32dc055 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileLoader.java @@ -0,0 +1,44 @@ +/* + * 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 org.picocontainer.Startable; + +/** + * Startable added to {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup} responsible for initializing + * {@link BuiltInQProfileRepository}. + */ +public class BuiltInQProfileLoader implements Startable { + private final BuiltInQProfileRepository builtInQProfileRepository; + + public BuiltInQProfileLoader(BuiltInQProfileRepository builtInQProfileRepository) { + this.builtInQProfileRepository = builtInQProfileRepository; + } + + @Override + public void start() { + builtInQProfileRepository.initialize(); + } + + @Override + public void stop() { + // nothing to do + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepository.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepository.java new file mode 100644 index 00000000000..c836848b974 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepository.java @@ -0,0 +1,42 @@ +/* + * 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 BuiltInQProfileRepository { + /** + * 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> getQProfilesByLanguage(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java new file mode 100644 index 00000000000..48e0e61c263 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java @@ -0,0 +1,267 @@ +/* + * 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.annotations.VisibleForTesting; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; +import java.security.MessageDigest; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +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 java.util.function.Function; +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.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 definitions; + private Map> qProfilesByLanguage; + + /** + * Requires for pico container when no {@link ProfileDefinition} is defined at all + */ + public BuiltInQProfileRepositoryImpl(Languages languages) { + this(languages, new ProfileDefinition[0]); + } + + public BuiltInQProfileRepositoryImpl(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 rulesProfilesByLanguage = buildRulesProfilesByLanguage(); + validateAndClean(rulesProfilesByLanguage); + this.qProfilesByLanguage = toQualityProfilesByLanguage(rulesProfilesByLanguage); + profiler.stopDebug(); + } + + @Override + public Map> getQProfilesByLanguage() { + checkState(qProfilesByLanguage != null, "initialize must be called first"); + + return qProfilesByLanguage; + } + + /** + * @return profiles by language + */ + private ListMultimap buildRulesProfilesByLanguage() { + ListMultimap 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 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 profiles = entry.getValue(); + if (profiles.isEmpty()) { + LOGGER.warn("No Quality profiles defined for language: {}", language); + return true; + } + return false; + }); + } + + private static Map> toQualityProfilesByLanguage(ListMultimap rulesProfilesByLanguage) { + Map> buildersByLanguage = Multimaps.asMap(rulesProfilesByLanguage) + .entrySet() + .stream() + .collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, BuiltInQProfileRepositoryImpl::toQualityProfileBuilders)); + return buildersByLanguage + .entrySet() + .stream() + .filter(BuiltInQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault) + .filter(entry -> ensureParentExists(entry.getKey(), entry.getValue())) + .collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, entry -> toQualityProfiles(entry.getValue()), buildersByLanguage.size())); + } + + private static boolean ensureParentExists(String language, List builders) { + Set qProfileNames = builders.stream() + .map(BuiltInQProfile.Builder::getName) + .collect(MoreCollectors.toSet(builders.size())); + builders + .forEach(builder -> { + String parentName = builder.getParentName(); + checkState(parentName == null || qProfileNames.contains(parentName), + "Quality profile with name %s references Quality profile with name %s as its parent, but it does not exist for language %s", + builder.getName(), builder.getParentName(), language); + }); + return true; + } + + /** + * Creates {@link BuiltInQProfile.Builder} for each unique quality profile name for a given language. + * Builders will have the following properties populated: + *
    + *
  • {@link BuiltInQProfile.Builder#language language}: key of the method's parameter
  • + *
  • {@link BuiltInQProfile.Builder#name name}: {@link RulesProfile#getName()}
  • + *
  • {@link BuiltInQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile + * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}
  • + *
  • {@link BuiltInQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all + * RulesProfile with a given name
  • + *
+ */ + private static List toQualityProfileBuilders(Map.Entry> rulesProfilesByLanguage) { + String language = rulesProfilesByLanguage.getKey(); + // use a LinkedHashMap to keep order of insertion of RulesProfiles + Map qualityProfileBuildersByName = new LinkedHashMap<>(); + for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) { + qualityProfileBuildersByName.compute( + rulesProfile.getName(), + (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile)); + } + return ImmutableList.copyOf(qualityProfileBuildersByName.values()); + } + + /** + * Fails if more than one {@link BuiltInQProfile.Builder#declaredDefault} is {@code true}, otherwise returns {@code true}. + */ + private static boolean ensureAtMostOneDeclaredDefault(Map.Entry> entry) { + Set declaredDefaultProfileNames = entry.getValue().stream() + .filter(BuiltInQProfile.Builder::isDeclaredDefault) + .map(BuiltInQProfile.Builder::getName) + .collect(MoreCollectors.toSet()); + checkState(declaredDefaultProfileNames.size() <= 1, "Several Quality profiles are flagged as default for the language %s: %s", entry.getKey(), declaredDefaultProfileNames); + return true; + } + + private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, RulesProfile rulesProfile) { + BuiltInQProfile.Builder builder = existingBuilder; + if (builder == null) { + builder = new BuiltInQProfile.Builder() + .setLanguage(language) + .setName(rulesProfile.getName()) + .setParentName(rulesProfile.getParentName()); + } + 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 toQualityProfiles(List builders) { + if (builders.stream().noneMatch(BuiltInQProfile.Builder::isDeclaredDefault)) { + Optional 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() + .sorted(new SortByParentName(builders)) + .map(builder -> builder.build(md5Digest)) + .collect(MoreCollectors.toList(builders.size())); + } + + @VisibleForTesting + static class SortByParentName implements Comparator { + private final Map buildersByName; + @VisibleForTesting + final Map depthByBuilder; + + @VisibleForTesting + SortByParentName(Collection builders) { + this.buildersByName = builders.stream() + .collect(MoreCollectors.uniqueIndex(BuiltInQProfile.Builder::getName, Function.identity(), builders.size())); + this.depthByBuilder = buildDepthByBuilder(buildersByName, builders); + } + + private static Map buildDepthByBuilder(Map buildersByName, Collection builders) { + Map depthByBuilder = new HashMap<>(); + builders.forEach(builder -> depthByBuilder.put(builder.getName(), 0)); + builders + .stream() + .filter(builder -> builder.getParentName() != null) + .forEach(builder -> increaseDepth(buildersByName, depthByBuilder, builder)); + return ImmutableMap.copyOf(depthByBuilder); + } + + private static void increaseDepth(Map buildersByName, Map maps, BuiltInQProfile.Builder builder) { + BuiltInQProfile.Builder parent = buildersByName.get(builder.getParentName()); + if (parent.getParentName() != null) { + increaseDepth(buildersByName, maps, parent); + } + maps.put(builder.getName(), maps.get(parent.getName()) + 1); + } + + @Override + public int compare(BuiltInQProfile.Builder o1, BuiltInQProfile.Builder o2) { + return depthByBuilder.getOrDefault(o1.getName(), 0) - depthByBuilder.getOrDefault(o2.getName(), 0); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreation.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreation.java new file mode 100644 index 00000000000..974db3afbf4 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreation.java @@ -0,0 +1,26 @@ +/* + * 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; + +/** + * Marker interface of any implementation of {@link BuiltInQProfileCreation} which supports caching. + */ +public interface CachingBuiltInQProfileCreation extends BuiltInQProfileCreation { +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreationImpl.java new file mode 100644 index 00000000000..d6fe7e955c5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingBuiltInQProfileCreationImpl.java @@ -0,0 +1,28 @@ +/* + * 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 org.sonar.db.DbClient; + +public class CachingBuiltInQProfileCreationImpl extends BuiltInQProfileCreationImpl implements CachingBuiltInQProfileCreation { + public CachingBuiltInQProfileCreationImpl(DbClient dbClient, QProfileFactory profileFactory, CachingRuleActivator ruleActivator) { + super(dbClient, profileFactory, ruleActivator); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreation.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreation.java deleted file mode 100644 index 4f973d6411b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreation.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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; - -/** - * Marker interface of any implementation of {@link DefinedQProfileCreation} which supports caching. - */ -public interface CachingDefinedQProfileCreation extends DefinedQProfileCreation { -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreationImpl.java deleted file mode 100644 index 1d4e7550871..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreationImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 org.sonar.db.DbClient; - -public class CachingDefinedQProfileCreationImpl extends DefinedQProfileCreationImpl implements CachingDefinedQProfileCreation { - public CachingDefinedQProfileCreationImpl(DbClient dbClient, QProfileFactory profileFactory, CachingRuleActivator ruleActivator) { - super(dbClient, profileFactory, ruleActivator); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfile.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfile.java deleted file mode 100644 index af4dfff22fe..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfile.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.CheckForNull; -import javax.annotation.Nullable; -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 activeRules; - private final QProfileName parentQProfileName; - - 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); - this.parentQProfileName = builder.parentName == null ? null : new QProfileName(builder.language, builder.parentName); - } - - 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 getActiveRules() { - return activeRules; - } - - @CheckForNull - public QProfileName getParentQProfileName() { - return parentQProfileName; - } - - static final class Builder { - private String language; - private String name; - private boolean declaredDefault; - private boolean computedDefault; - private final List activeRules = new ArrayList<>(); - private String parentName; - - 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 rules) { - this.activeRules.addAll(rules); - return this; - } - - public Builder setParentName(@Nullable String parentName) { - this.parentName = parentName; - return this; - } - - @CheckForNull - public String getParentName() { - return parentName; - } - - DefinedQProfile build(MessageDigest messageDigest) { - return new DefinedQProfile(this, messageDigest); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java deleted file mode 100644 index 65a206abc02..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; - -public interface DefinedQProfileCreation { - /** - * Persists the specified {@link DefinedQProfile} of the specified organization and adds any {@link ActiveRuleChange} - * to the specified list. - * - * The session is not commit. - */ - void create(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java deleted file mode 100644 index dfec3d1690f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.ActiveRuleParam; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.loadedtemplate.LoadedTemplateDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.qualityprofile.QualityProfileDto; - -public class DefinedQProfileCreationImpl implements DefinedQProfileCreation { - private final DbClient dbClient; - private final QProfileFactory profileFactory; - private final RuleActivator ruleActivator; - - public DefinedQProfileCreationImpl(DbClient dbClient, QProfileFactory profileFactory, RuleActivator ruleActivator) { - this.dbClient = dbClient; - this.profileFactory = profileFactory; - this.ruleActivator = ruleActivator; - } - - @Override - public void create(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes) { - QualityProfileDto profileDto = dbClient.qualityProfileDao().selectByNameAndLanguage(organization, qualityProfile.getName(), qualityProfile.getLanguage(), session); - if (profileDto == null) { - profileDto = profileFactory.create(session, organization, qualityProfile.getQProfileName(), qualityProfile.isDefault()); - for (org.sonar.api.rules.ActiveRule activeRule : qualityProfile.getActiveRules()) { - RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()); - RuleActivation activation = new RuleActivation(ruleKey); - activation.setSeverity(activeRule.getSeverity() != null ? activeRule.getSeverity().name() : null); - for (ActiveRuleParam param : activeRule.getActiveRuleParams()) { - activation.setParameter(param.getKey(), param.getValue()); - } - changes.addAll(ruleActivator.activate(session, activation, profileDto)); - } - } - - LoadedTemplateDto template = new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()); - dbClient.loadedTemplateDao().insert(template, session); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsert.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsert.java deleted file mode 100644 index 6ec943901d5..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsert.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; - -public interface DefinedQProfileInsert { - void create(DbSession session, DbSession batchSession, DefinedQProfile definedQProfile, OrganizationDto organization); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsertImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsertImpl.java deleted file mode 100644 index a363cfb1b39..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsertImpl.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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.base.Splitter; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -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.rule.RuleParamType; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactory; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.loadedtemplate.LoadedTemplateDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.ActiveRuleKey; -import org.sonar.db.qualityprofile.ActiveRuleParamDto; -import org.sonar.db.qualityprofile.QualityProfileDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.rule.RuleParamDto; -import org.sonar.server.util.TypeValidations; - -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Lists.newArrayList; -import static java.util.Objects.requireNonNull; - -public class DefinedQProfileInsertImpl implements DefinedQProfileInsert { - private final DbClient dbClient; - private final System2 system2; - private final UuidFactory uuidFactory; - private final TypeValidations typeValidations; - private RuleRepository ruleRepository; - - public DefinedQProfileInsertImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory, TypeValidations typeValidations) { - this.dbClient = dbClient; - this.system2 = system2; - this.uuidFactory = uuidFactory; - this.typeValidations = typeValidations; - } - - @Override - public void create(DbSession session, DbSession batchSession, DefinedQProfile definedQProfile, OrganizationDto organization) { - initRuleRepository(batchSession); - - checkArgument(definedQProfile.getParentQProfileName() == null, "Inheritance of Quality Profiles is not supported yet"); - - Date now = new Date(system2.now()); - QualityProfileDto profileDto = insertQualityProfile(session, definedQProfile, organization, now); - - List localChanges = definedQProfile.getActiveRules() - .stream() - .map(activeRule -> insertActiveRule(session, profileDto, activeRule, now.getTime())) - .collect(MoreCollectors.toList()); - - localChanges.forEach(change -> dbClient.qProfileChangeDao().insert(batchSession, change.toDto(null))); - - insertTemplate(session, definedQProfile, organization); - } - - private void initRuleRepository(DbSession session) { - if (ruleRepository == null) { - ruleRepository = new RuleRepository(dbClient, session); - } - } - - private QualityProfileDto insertQualityProfile(DbSession session, DefinedQProfile definedQProfile, OrganizationDto organization, Date now) { - QualityProfileDto profileDto = QualityProfileDto.createFor(uuidFactory.create()) - .setName(definedQProfile.getName()) - .setOrganizationUuid(organization.getUuid()) - .setLanguage(definedQProfile.getLanguage()) - .setDefault(definedQProfile.isDefault()) - .setRulesUpdatedAtAsDate(now); - dbClient.qualityProfileDao().insert(session, profileDto); - return profileDto; - } - - private ActiveRuleChange insertActiveRule(DbSession session, QualityProfileDto profileDto, org.sonar.api.rules.ActiveRule activeRule, long now) { - RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()); - RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey) - .orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey)); - - ActiveRuleDto dto = ActiveRuleDto.createFor(profileDto, ruleDefinitionDto); - dto.setSeverity(firstNonNull(activeRule.getSeverity().name(), ruleDefinitionDto.getSeverityString())); - dto.setUpdatedAt(now); - dto.setCreatedAt(now); - dbClient.activeRuleDao().insert(session, dto); - - List paramDtos = insertActiveRuleParams(session, activeRule, ruleKey, dto); - - ActiveRuleChange change = ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(profileDto.getKey(), ruleKey)); - change.setSeverity(dto.getSeverityString()); - paramDtos.forEach(paramDto -> change.setParameter(paramDto.getKey(), paramDto.getValue())); - return change; - } - - private List insertActiveRuleParams(DbSession session, org.sonar.api.rules.ActiveRule activeRule, RuleKey ruleKey, ActiveRuleDto activeRuleDto) { - Map valuesByParamKey = activeRule.getActiveRuleParams() - .stream() - .collect(MoreCollectors.uniqueIndex(ActiveRuleParam::getParamKey, ActiveRuleParam::getValue)); - return ruleRepository.getRuleParams(ruleKey) - .stream() - .map(param -> { - String activeRuleValue = valuesByParamKey.get(param.getName()); - return createParamDto(param, activeRuleValue == null ? param.getDefaultValue() : activeRuleValue); - }) - .filter(Objects::nonNull) - .peek(paramDto -> dbClient.activeRuleDao().insertParam(session, activeRuleDto, paramDto)) - .collect(MoreCollectors.toList()); - } - - @CheckForNull - private ActiveRuleParamDto createParamDto(RuleParamDto param, @Nullable String value) { - if (value == null) { - return null; - } - ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(param); - paramDto.setValue(validateParam(param, value)); - return paramDto; - } - - @CheckForNull - private String validateParam(RuleParamDto ruleParam, String value) { - RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType()); - if (ruleParamType.multiple()) { - List values = newArrayList(Splitter.on(",").split(value)); - typeValidations.validate(values, ruleParamType.type(), ruleParamType.values()); - } else { - typeValidations.validate(value, ruleParamType.type(), ruleParamType.values()); - } - return value; - } - - private void insertTemplate(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization) { - LoadedTemplateDto template = new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()); - dbClient.loadedTemplateDao().insert(template, session); - } - - public static class RuleRepository { - private final Map ruleDefinitions; - private final Map> ruleParams; - - private RuleRepository(DbClient dbClient, DbSession session) { - this.ruleDefinitions = dbClient.ruleDao().selectAllDefinitions(session) - .stream() - .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity())); - Map ruleIdsByKey = ruleDefinitions.values() - .stream() - .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getId, RuleDefinitionDto::getKey)); - this.ruleParams = new HashMap<>(ruleIdsByKey.size()); - dbClient.ruleDao().selectRuleParamsByRuleKeys(session, ruleDefinitions.keySet()) - .forEach(ruleParam -> ruleParams.compute( - ruleIdsByKey.get(ruleParam.getRuleId()), - (key, value) -> { - if (value == null) { - return ImmutableSet.of(ruleParam); - } - return ImmutableSet.copyOf(Sets.union(value, Collections.singleton(ruleParam))); - })); - } - - Optional getDefinition(RuleKey ruleKey) { - return Optional.ofNullable(ruleDefinitions.get(requireNonNull(ruleKey, "RuleKey can't be null"))); - } - - Set getRuleParams(RuleKey ruleKey) { - Set res = ruleParams.get(requireNonNull(ruleKey, "RuleKey can't be null")); - return res == null ? Collections.emptySet() : res; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileLoader.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileLoader.java deleted file mode 100644 index 1709b65b7e6..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileLoader.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 org.picocontainer.Startable; - -/** - * Startable added to {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup} responsible for initializing - * {@link DefinedQProfileRepository}. - */ -public class DefinedQProfileLoader implements Startable { - private final DefinedQProfileRepository definedQProfileRepository; - - public DefinedQProfileLoader(DefinedQProfileRepository definedQProfileRepository) { - this.definedQProfileRepository = definedQProfileRepository; - } - - @Override - public void start() { - definedQProfileRepository.initialize(); - } - - @Override - public void stop() { - // nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepository.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepository.java deleted file mode 100644 index 53452d1d857..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepository.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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> getQProfilesByLanguage(); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImpl.java deleted file mode 100644 index 05ed2e133c4..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImpl.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * 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.annotations.VisibleForTesting; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Multimaps; -import java.security.MessageDigest; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -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 java.util.function.Function; -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.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 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 definitions; - private Map> 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 rulesProfilesByLanguage = buildRulesProfilesByLanguage(); - validateAndClean(rulesProfilesByLanguage); - this.qProfilesByLanguage = toQualityProfilesByLanguage(rulesProfilesByLanguage); - profiler.stopDebug(); - } - - @Override - public Map> getQProfilesByLanguage() { - checkState(qProfilesByLanguage != null, "initialize must be called first"); - - return qProfilesByLanguage; - } - - /** - * @return profiles by language - */ - private ListMultimap buildRulesProfilesByLanguage() { - ListMultimap 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 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 profiles = entry.getValue(); - if (profiles.isEmpty()) { - LOGGER.warn("No Quality profiles defined for language: {}", language); - return true; - } - return false; - }); - } - - private static Map> toQualityProfilesByLanguage(ListMultimap rulesProfilesByLanguage) { - Map> buildersByLanguage = Multimaps.asMap(rulesProfilesByLanguage) - .entrySet() - .stream() - .collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, DefinedQProfileRepositoryImpl::toQualityProfileBuilders)); - return buildersByLanguage - .entrySet() - .stream() - .filter(DefinedQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault) - .filter(entry -> ensureParentExists(entry.getKey(), entry.getValue())) - .collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, entry -> toQualityProfiles(entry.getValue()), buildersByLanguage.size())); - } - - private static boolean ensureParentExists(String language, List builders) { - Set qProfileNames = builders.stream() - .map(DefinedQProfile.Builder::getName) - .collect(MoreCollectors.toSet(builders.size())); - builders - .forEach(builder -> { - String parentName = builder.getParentName(); - checkState(parentName == null || qProfileNames.contains(parentName), - "Quality profile with name %s references Quality profile with name %s as its parent, but it does not exist for language %s", - builder.getName(), builder.getParentName(), language); - }); - return true; - } - - /** - * Creates {@link DefinedQProfile.Builder} for each unique quality profile name for a given language. - * Builders will have the following properties populated: - *
    - *
  • {@link DefinedQProfile.Builder#language language}: key of the method's parameter
  • - *
  • {@link DefinedQProfile.Builder#name name}: {@link RulesProfile#getName()}
  • - *
  • {@link DefinedQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile - * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}
  • - *
  • {@link DefinedQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all - * RulesProfile with a given name
  • - *
- */ - private static List toQualityProfileBuilders(Map.Entry> rulesProfilesByLanguage) { - String language = rulesProfilesByLanguage.getKey(); - // use a LinkedHashMap to keep order of insertion of RulesProfiles - Map qualityProfileBuildersByName = new LinkedHashMap<>(); - for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) { - qualityProfileBuildersByName.compute( - rulesProfile.getName(), - (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile)); - } - 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> entry) { - Set declaredDefaultProfileNames = entry.getValue().stream() - .filter(DefinedQProfile.Builder::isDeclaredDefault) - .map(DefinedQProfile.Builder::getName) - .collect(MoreCollectors.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) { - DefinedQProfile.Builder builder = existingBuilder; - if (builder == null) { - builder = new DefinedQProfile.Builder() - .setLanguage(language) - .setName(rulesProfile.getName()) - .setParentName(rulesProfile.getParentName()); - } - 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 toQualityProfiles(List builders) { - if (builders.stream().noneMatch(DefinedQProfile.Builder::isDeclaredDefault)) { - Optional 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() - .sorted(new SortByParentName(builders)) - .map(builder -> builder.build(md5Digest)) - .collect(MoreCollectors.toList(builders.size())); - } - - @VisibleForTesting - static class SortByParentName implements Comparator { - private final Map buildersByName; - @VisibleForTesting - final Map depthByBuilder; - - @VisibleForTesting - SortByParentName(Collection builders) { - this.buildersByName = builders.stream() - .collect(MoreCollectors.uniqueIndex(DefinedQProfile.Builder::getName, Function.identity(), builders.size())); - this.depthByBuilder = buildDepthByBuilder(buildersByName, builders); - } - - private static Map buildDepthByBuilder(Map buildersByName, Collection builders) { - Map depthByBuilder = new HashMap<>(); - builders.forEach(builder -> depthByBuilder.put(builder.getName(), 0)); - builders - .stream() - .filter(builder -> builder.getParentName() != null) - .forEach(builder -> increaseDepth(buildersByName, depthByBuilder, builder)); - return ImmutableMap.copyOf(depthByBuilder); - } - - private static void increaseDepth(Map buildersByName, Map maps, DefinedQProfile.Builder builder) { - DefinedQProfile.Builder parent = buildersByName.get(builder.getParentName()); - if (parent.getParentName() != null) { - increaseDepth(buildersByName, maps, parent); - } - maps.put(builder.getName(), maps.get(parent.getName()) + 1); - } - - @Override - public int compare(DefinedQProfile.Builder o1, DefinedQProfile.Builder o2) { - return depthByBuilder.getOrDefault(o1.getName(), 0) - depthByBuilder.getOrDefault(o2.getName(), 0); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/MassRegisterQualityProfiles.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/MassRegisterQualityProfiles.java index 8e449fd2f16..e95b3b766d6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/MassRegisterQualityProfiles.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/MassRegisterQualityProfiles.java @@ -49,16 +49,16 @@ public class MassRegisterQualityProfiles { private static final Pagination PROCESSED_ORGANIZATIONS_BATCH_SIZE = forPage(1).andSize(2000); private final Settings settings; - private final DefinedQProfileRepository definedQProfileRepository; + private final BuiltInQProfileRepository builtInQProfileRepository; private final DbClient dbClient; - private final DefinedQProfileInsert definedQProfileInsert; + private final BuiltInQProfileInsert builtInQProfileInsert; - public MassRegisterQualityProfiles(Settings settings, DefinedQProfileRepository definedQProfileRepository, - DbClient dbClient, DefinedQProfileInsert definedQProfileInsert) { + public MassRegisterQualityProfiles(Settings settings, BuiltInQProfileRepository builtInQProfileRepository, + DbClient dbClient, BuiltInQProfileInsert builtInQProfileInsert) { this.settings = settings; - this.definedQProfileRepository = definedQProfileRepository; + this.builtInQProfileRepository = builtInQProfileRepository; this.dbClient = dbClient; - this.definedQProfileInsert = definedQProfileInsert; + this.builtInQProfileInsert = builtInQProfileInsert; } public void start() { @@ -67,23 +67,23 @@ public class MassRegisterQualityProfiles { } Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Mass Register quality profiles"); - if (definedQProfileRepository.getQProfilesByLanguage().isEmpty()) { + if (builtInQProfileRepository.getQProfilesByLanguage().isEmpty()) { return; } try (DbSession session = dbClient.openSession(false); DbSession batchSession = dbClient.openSession(true)) { - definedQProfileRepository.getQProfilesByLanguage() + builtInQProfileRepository.getQProfilesByLanguage() .forEach((key, value) -> registerPerLanguage(session, batchSession, value)); profiler.stopDebug(); } } - private void registerPerLanguage(DbSession session, DbSession batchSession, List qualityProfiles) { + private void registerPerLanguage(DbSession session, DbSession batchSession, List qualityProfiles) { qualityProfiles.forEach(qp -> registerPerQualityProfile(session, batchSession, qp)); } - private void registerPerQualityProfile(DbSession session, DbSession batchSession, DefinedQProfile qualityProfile) { + private void registerPerQualityProfile(DbSession session, DbSession batchSession, BuiltInQProfile qualityProfile) { LOGGER.info("Register profile {}", qualityProfile.getQProfileName()); Profiler profiler = Profiler.create(Loggers.get(getClass())); @@ -93,21 +93,21 @@ public class MassRegisterQualityProfiles { } } - private List getOrganizationsWithoutQP(DbSession session, DefinedQProfile qualityProfile) { + private List getOrganizationsWithoutQP(DbSession session, BuiltInQProfile qualityProfile) { return dbClient.organizationDao().selectOrganizationsWithoutLoadedTemplate(session, qualityProfile.getLoadedTemplateType(), PROCESSED_ORGANIZATIONS_BATCH_SIZE); } private void registerPerQualityProfileAndOrganization(DbSession session, DbSession batchSession, - DefinedQProfile definedQProfile, OrganizationDto organization, Profiler profiler) { + BuiltInQProfile builtInQProfile, OrganizationDto organization, Profiler profiler) { profiler.start(); - definedQProfileInsert.create(session, batchSession, definedQProfile, organization); + builtInQProfileInsert.create(session, batchSession, builtInQProfile, organization); session.commit(); batchSession.commit(); - profiler.stopDebug(format("Register profile %s for organization %s", definedQProfile.getQProfileName(), organization.getKey())); + profiler.stopDebug(format("Register profile %s for organization %s", builtInQProfile.getQProfileName(), organization.getKey())); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java index ec35da33058..48684b47079 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java @@ -121,7 +121,7 @@ public class QProfileBackuperImpl implements QProfileBackuper { if (overriddenProfileName != null) { targetName = new QProfileName(nameInBackup.getLanguage(), overriddenProfileName); } - return profileFactory.getOrCreate(dbSession, organization, targetName); + return profileFactory.getOrCreateCustom(dbSession, organization, targetName); }); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileCopier.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileCopier.java index 7f54187922e..639da9113b8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileCopier.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileCopier.java @@ -70,7 +70,7 @@ public class QProfileCopier { verify(sourceProfile, toProfileName); QualityProfileDto toProfile = db.qualityProfileDao().selectByNameAndLanguage(organization, toProfileName.getName(), toProfileName.getLanguage(), dbSession); if (toProfile == null) { - toProfile = factory.checkAndCreate(dbSession, organization, toProfileName); + toProfile = factory.checkAndCreateCustom(dbSession, organization, toProfileName); toProfile.setParentKee(sourceProfile.getParentKee()); db.qualityProfileDao().update(dbSession, toProfile); dbSession.commit(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java index f5427e1ba58..35978d7e3c4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java @@ -54,11 +54,11 @@ public class QProfileFactory { // ------------- CREATION - QualityProfileDto getOrCreate(DbSession dbSession, OrganizationDto organization, QProfileName name) { + QualityProfileDto getOrCreateCustom(DbSession dbSession, OrganizationDto organization, QProfileName name) { requireNonNull(organization); QualityProfileDto profile = db.qualityProfileDao().selectByNameAndLanguage(organization, name.getName(), name.getLanguage(), dbSession); if (profile == null) { - profile = doCreate(dbSession, organization, name, false); + profile = doCreate(dbSession, organization, name, false, false); } return profile; } @@ -68,11 +68,11 @@ public class QProfileFactory { * * @throws BadRequestException if a quality profile with the specified name already exists */ - public QualityProfileDto checkAndCreate(DbSession dbSession, OrganizationDto organization, QProfileName name) { + public QualityProfileDto checkAndCreateCustom(DbSession dbSession, OrganizationDto organization, QProfileName name) { requireNonNull(organization); QualityProfileDto dto = db.qualityProfileDao().selectByNameAndLanguage(organization, name.getName(), name.getLanguage(), dbSession); checkRequest(dto == null, "Quality profile already exists: %s", name); - return doCreate(dbSession, organization, name, false); + return doCreate(dbSession, organization, name, false, false); } /** @@ -80,8 +80,8 @@ public class QProfileFactory { * * A DB error will be thrown if the quality profile already exists. */ - public QualityProfileDto create(DbSession dbSession, OrganizationDto organization, QProfileName name, boolean isDefault) { - return doCreate(dbSession, requireNonNull(organization), name, isDefault); + public QualityProfileDto createBuiltIn(DbSession dbSession, OrganizationDto organization, QProfileName name, boolean isDefault) { + return doCreate(dbSession, requireNonNull(organization), name, isDefault, true); } private static OrganizationDto requireNonNull(@Nullable OrganizationDto organization) { @@ -89,7 +89,7 @@ public class QProfileFactory { return organization; } - private QualityProfileDto doCreate(DbSession dbSession, OrganizationDto organization, QProfileName name, boolean isDefault) { + private QualityProfileDto doCreate(DbSession dbSession, OrganizationDto organization, QProfileName name, boolean isDefault, boolean isBuiltIn) { if (StringUtils.isEmpty(name.getName())) { throw BadRequestException.create("quality_profiles.profile_name_cant_be_blank"); } @@ -99,6 +99,7 @@ public class QProfileFactory { .setOrganizationUuid(organization.getUuid()) .setLanguage(name.getLanguage()) .setDefault(isDefault) + .setIsBuiltIn(isBuiltIn) .setRulesUpdatedAtAsDate(now); db.qualityProfileDao().insert(dbSession, dto); return dto; diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java index 57d9e6565df..71a1256a80b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java @@ -49,33 +49,33 @@ public class QProfileResetImpl implements QProfileReset { private final QProfileFactory factory; private final RuleActivator activator; private final ActiveRuleIndexer activeRuleIndexer; - private final DefinedQProfileRepository definedQProfileRepositories; + private final BuiltInQProfileRepository builtInQProfileRepositories; public QProfileResetImpl(DbClient db, RuleActivator activator, ActiveRuleIndexer activeRuleIndexer, QProfileFactory factory, - DefinedQProfileRepository definedQProfileRepository) { + BuiltInQProfileRepository builtInQProfileRepository) { this.db = db; this.activator = activator; this.activeRuleIndexer = activeRuleIndexer; this.factory = factory; - this.definedQProfileRepositories = definedQProfileRepository; + this.builtInQProfileRepositories = builtInQProfileRepository; } @Override public void resetLanguage(DbSession dbSession, OrganizationDto organization, String language) { - definedQProfileRepositories.getQProfilesByLanguage() + builtInQProfileRepositories.getQProfilesByLanguage() .entrySet() .stream() .filter(entry -> entry.getKey().equals(language)) .map(Map.Entry::getValue) .flatMap(List::stream) - .forEach(definedQProfile -> resetProfile(dbSession, organization, definedQProfile)); + .forEach(builtInQProfile -> resetProfile(dbSession, organization, builtInQProfile)); } - private void resetProfile(DbSession dbSession, OrganizationDto organization, DefinedQProfile definedQProfile) { - QualityProfileDto profile = factory.getOrCreate(dbSession, organization, definedQProfile.getQProfileName()); + private void resetProfile(DbSession dbSession, OrganizationDto organization, BuiltInQProfile builtInQProfile) { + QualityProfileDto profile = factory.getOrCreateCustom(dbSession, organization, builtInQProfile.getQProfileName()); List activations = Lists.newArrayList(); - definedQProfile.getActiveRules().forEach(activeRule -> activations.add(getRuleActivation(dbSession, activeRule))); + builtInQProfile.getActiveRules().forEach(activeRule -> activations.add(getRuleActivation(dbSession, activeRule))); reset(dbSession, profile, activations); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java index c003b4d5ba2..87d699c1081 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java @@ -20,6 +20,7 @@ package org.sonar.server.qualityprofile; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.log.Logger; @@ -42,16 +43,16 @@ public class RegisterQualityProfiles { private static final Logger LOGGER = Loggers.get(RegisterQualityProfiles.class); private static final Pagination PROCESSED_ORGANIZATIONS_BATCH_SIZE = forPage(1).andSize(2000); - private final DefinedQProfileRepository definedQProfileRepository; + private final BuiltInQProfileRepository builtInQProfileRepository; private final DbClient dbClient; - private final DefinedQProfileCreation definedQProfileCreation; + private final BuiltInQProfileCreation builtInQProfileCreation; private final ActiveRuleIndexer activeRuleIndexer; - public RegisterQualityProfiles(DefinedQProfileRepository definedQProfileRepository, - DbClient dbClient, DefinedQProfileCreation definedQProfileCreation, ActiveRuleIndexer activeRuleIndexer) { - this.definedQProfileRepository = definedQProfileRepository; + public RegisterQualityProfiles(BuiltInQProfileRepository builtInQProfileRepository, + DbClient dbClient, BuiltInQProfileCreation builtInQProfileCreation, ActiveRuleIndexer activeRuleIndexer) { + this.builtInQProfileRepository = builtInQProfileRepository; this.dbClient = dbClient; - this.definedQProfileCreation = definedQProfileCreation; + this.builtInQProfileCreation = builtInQProfileCreation; this.activeRuleIndexer = activeRuleIndexer; } @@ -60,36 +61,57 @@ public class RegisterQualityProfiles { try (DbSession session = dbClient.openSession(false)) { List changes = new ArrayList<>(); - definedQProfileRepository.getQProfilesByLanguage().entrySet() - .forEach(entry -> registerPerLanguage(session, entry.getValue(), changes)); + builtInQProfileRepository.getQProfilesByLanguage().forEach( + (key, value) -> registerPerLanguage(session, value, changes)); activeRuleIndexer.index(changes); profiler.stopDebug(); } } - private void registerPerLanguage(DbSession session, List qualityProfiles, List changes) { + private void registerPerLanguage(DbSession session, List qualityProfiles, List changes) { qualityProfiles.forEach(qp -> registerPerQualityProfile(session, qp, changes)); session.commit(); } - private void registerPerQualityProfile(DbSession session, DefinedQProfile qualityProfile, List changes) { + private void registerPerQualityProfile(DbSession dbSession, BuiltInQProfile qualityProfile, List changes) { LOGGER.info("Register profile {}", qualityProfile.getQProfileName()); + renameOutdatedProfiles(dbSession, qualityProfile); + List organizationDtos; - while (!(organizationDtos = getOrganizationsWithoutQP(session, qualityProfile)).isEmpty()) { - organizationDtos.forEach(organization -> registerPerQualityProfileAndOrganization(session, qualityProfile, organization, changes)); + while (!(organizationDtos = getOrganizationsWithoutQP(dbSession, qualityProfile)).isEmpty()) { + organizationDtos.forEach(organization -> registerPerQualityProfileAndOrganization(dbSession, qualityProfile, organization, changes)); + } + } + + /** + * The Quality profiles created by users should be renamed when they have the same name + * as the built-in profile to be persisted. + * + * When upgrading from < 6.5 , all existing profiles are considered as "custom" (created + * by users) because the concept of built-in profile is not persisted. The "Sonar way" profiles + * are renamed to "Sonar way (outdated copy) in order to avoid conflicts with the new + * built-in profile "Sonar way", which has probably different configuration. + */ + private void renameOutdatedProfiles(DbSession dbSession, BuiltInQProfile profile) { + Collection profileKeys = dbClient.qualityProfileDao().selectOutdatedProfiles(dbSession, profile.getLanguage(), profile.getName()); + if (profileKeys.isEmpty()) { + return; } + String newName = profile.getName() + " (outdated copy)"; + LOGGER.info("Rename Quality profiles [{}/{}] to [{}] in {} organizations", profile.getLanguage(), profile.getName(), newName, profileKeys.size()); + dbClient.qualityProfileDao().renameAndCommit(dbSession, profileKeys, newName); } - private List getOrganizationsWithoutQP(DbSession session, DefinedQProfile qualityProfile) { + private List getOrganizationsWithoutQP(DbSession session, BuiltInQProfile qualityProfile) { return dbClient.organizationDao().selectOrganizationsWithoutLoadedTemplate(session, qualityProfile.getLoadedTemplateType(), PROCESSED_ORGANIZATIONS_BATCH_SIZE); } - private void registerPerQualityProfileAndOrganization(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes) { + private void registerPerQualityProfileAndOrganization(DbSession session, BuiltInQProfile qualityProfile, OrganizationDto organization, List changes) { LOGGER.debug("Register profile {} for organization {}", qualityProfile.getQProfileName(), organization.getKey()); - definedQProfileCreation.create(session, qualityProfile, organization, changes); + builtInQProfileCreation.create(session, qualityProfile, organization, changes); session.commit(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java index 09b4cd83999..18718ab7b11 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java @@ -123,7 +123,7 @@ public class CreateAction implements QProfileWsAction { private CreateWsResponse doHandle(DbSession dbSession, CreateRequest createRequest, Request request, OrganizationDto organization) { QProfileResult result = new QProfileResult(); - QualityProfileDto profile = profileFactory.checkAndCreate(dbSession, organization, + QualityProfileDto profile = profileFactory.checkAndCreateCustom(dbSession, organization, QProfileName.createFor(createRequest.getLanguage(), createRequest.getProfileName())); result.setProfile(profile); for (ProfileImporter importer : importers) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java index 6144c27542d..b86c3bcc65e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java @@ -50,9 +50,9 @@ import org.sonar.server.es.EsTester; import org.sonar.server.es.SearchOptions; import org.sonar.server.language.LanguageTesting; import org.sonar.server.qualityprofile.ActiveRuleChange; -import org.sonar.server.qualityprofile.DefinedQProfile; -import org.sonar.server.qualityprofile.DefinedQProfileInsertRule; -import org.sonar.server.qualityprofile.DefinedQProfileRepositoryRule; +import org.sonar.server.qualityprofile.BuiltInQProfile; +import org.sonar.server.qualityprofile.BuiltInQProfileInsertRule; +import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryRule; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.user.index.UserIndex; import org.sonar.server.user.index.UserIndexDefinition; @@ -94,9 +94,9 @@ public class OrganizationCreationImplTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule - public DefinedQProfileRepositoryRule definedQProfileRepositoryRule = new DefinedQProfileRepositoryRule(); + public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule(); @Rule - public DefinedQProfileInsertRule definedQProfileCreationRule = new DefinedQProfileInsertRule(); + public BuiltInQProfileInsertRule builtInQProfileCreationRule = new BuiltInQProfileInsertRule(); private DbSession dbSession = dbTester.getSession(); @@ -110,7 +110,7 @@ public class OrganizationCreationImplTest { private DefaultGroupCreator defaultGroupCreator = new DefaultGroupCreatorImpl(dbClient); private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); private OrganizationCreationImpl underTest = new OrganizationCreationImpl(dbClient, system2, uuidFactory, organizationValidation, settings, userIndexer, - definedQProfileRepositoryRule, definedQProfileCreationRule, defaultGroupCreator, activeRuleIndexer); + builtInQProfileRepositoryRule, builtInQProfileCreationRule, defaultGroupCreator, activeRuleIndexer); private UserDto someUser; @@ -167,7 +167,7 @@ public class OrganizationCreationImplTest { } @Test - public void create_fails_with_ISE_if_DefinedQProfileRepository_has_not_been_initialized() throws OrganizationCreation.KeyConflictException { + public void create_fails_with_ISE_if_BuiltInQProfileRepository_has_not_been_initialized() throws OrganizationCreation.KeyConflictException { mockForSuccessfulInsert(SOME_UUID, SOME_DATE); expectedException.expect(IllegalStateException.class); @@ -189,7 +189,7 @@ public class OrganizationCreationImplTest { @Test public void create_creates_unguarded_organization_with_properties_from_NewOrganization_arg() throws OrganizationCreation.KeyConflictException { mockForSuccessfulInsert(SOME_UUID, SOME_DATE); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.create(dbSession, someUser, FULL_POPULATED_NEW_ORGANIZATION); @@ -210,7 +210,7 @@ public class OrganizationCreationImplTest { public void create_creates_owners_group_with_all_permissions_for_new_organization_and_add_current_user_to_it() throws OrganizationCreation.KeyConflictException { UserDto user = dbTester.users().insertUser(); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION); @@ -221,7 +221,7 @@ public class OrganizationCreationImplTest { public void create_creates_members_group_and_add_current_user_to_it() throws OrganizationCreation.KeyConflictException { UserDto user = dbTester.users().insertUser(); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION); @@ -231,7 +231,7 @@ public class OrganizationCreationImplTest { @Test public void create_does_not_require_description_url_and_avatar_to_be_non_null() throws OrganizationCreation.KeyConflictException { mockForSuccessfulInsert(SOME_UUID, SOME_DATE); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.create(dbSession, someUser, newOrganizationBuilder() .setKey("key") @@ -251,7 +251,7 @@ public class OrganizationCreationImplTest { @Test public void create_creates_default_template_for_new_organization() throws OrganizationCreation.KeyConflictException { mockForSuccessfulInsert(SOME_UUID, SOME_DATE); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.create(dbSession, someUser, FULL_POPULATED_NEW_ORGANIZATION); @@ -277,7 +277,7 @@ public class OrganizationCreationImplTest { userIndexer.index(user.getLogin()); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); userIndexer.index(someUser.getLogin()); underTest.create(dbSession, someUser, FULL_POPULATED_NEW_ORGANIZATION); @@ -287,26 +287,26 @@ public class OrganizationCreationImplTest { } @Test - public void create_creates_QualityProfile_for_each_DefinedQProfile_in_repository_and_index_ActiveRule_changes_in_order() throws OrganizationCreation.KeyConflictException { - DefinedQProfile definedQProfile1 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp1"); - DefinedQProfile definedQProfile2 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp2"); - DefinedQProfile definedQProfile3 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp3"); - DefinedQProfile definedQProfile4 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp4"); - definedQProfileRepositoryRule.initialize(); + public void create_creates_QualityProfile_for_each_BuiltInQProfile_in_repository_and_index_ActiveRule_changes_in_order() throws OrganizationCreation.KeyConflictException { + BuiltInQProfile builtInQProfile1 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp1"); + BuiltInQProfile builtInQProfile2 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp2"); + BuiltInQProfile builtInQProfile3 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp3"); + BuiltInQProfile builtInQProfile4 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp4"); + builtInQProfileRepositoryRule.initialize(); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); underTest.create(dbSession, someUser, FULL_POPULATED_NEW_ORGANIZATION); OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get(); - assertThat(definedQProfileCreationRule.getCallLogs()) + assertThat(builtInQProfileCreationRule.getCallLogs()) .hasSize(4) - .extracting(DefinedQProfileInsertRule.CallLog::getOrganizationDto) + .extracting(BuiltInQProfileInsertRule.CallLog::getOrganizationDto) .extracting(OrganizationDto::getUuid) .containsOnly(organization.getUuid()); - assertThat(definedQProfileCreationRule.getCallLogs()) - .extracting(DefinedQProfileInsertRule.CallLog::getDefinedQProfile) - .extracting(DefinedQProfile::getName) - .containsExactly(definedQProfile1.getName(), definedQProfile2.getName(), definedQProfile3.getName(), definedQProfile4.getName()); + assertThat(builtInQProfileCreationRule.getCallLogs()) + .extracting(BuiltInQProfileInsertRule.CallLog::getDefinedQProfile) + .extracting(BuiltInQProfile::getName) + .containsExactly(builtInQProfile1.getName(), builtInQProfile2.getName(), builtInQProfile3.getName(), builtInQProfile4.getName()); } @Test @@ -344,7 +344,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -382,7 +382,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -397,7 +397,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -410,7 +410,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -438,7 +438,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -453,7 +453,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -469,7 +469,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(login)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -485,7 +485,7 @@ public class OrganizationCreationImplTest { when(organizationValidation.generateKeyFrom(login)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.createForUser(dbSession, user); @@ -498,26 +498,26 @@ public class OrganizationCreationImplTest { public void createForUser_creates_QualityProfile_for_each_DefinedQProfile_in_repository_and_index_ActiveRule_changes_in_order() throws OrganizationCreation.KeyConflictException { UserDto user = dbTester.users().insertUser(A_LOGIN); when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); - DefinedQProfile definedQProfile1 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp1"); - DefinedQProfile definedQProfile2 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp2"); - DefinedQProfile definedQProfile3 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp3"); - DefinedQProfile definedQProfile4 = definedQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp4"); - definedQProfileRepositoryRule.initialize(); + BuiltInQProfile builtInQProfile1 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp1"); + BuiltInQProfile builtInQProfile2 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp2"); + BuiltInQProfile builtInQProfile3 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp3"); + BuiltInQProfile builtInQProfile4 = builtInQProfileRepositoryRule.add(LanguageTesting.newLanguage("foo"), "qp4"); + builtInQProfileRepositoryRule.initialize(); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); enableCreatePersonalOrg(true); underTest.createForUser(dbSession, user); OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get(); - assertThat(definedQProfileCreationRule.getCallLogs()) + assertThat(builtInQProfileCreationRule.getCallLogs()) .hasSize(4) - .extracting(DefinedQProfileInsertRule.CallLog::getOrganizationDto) + .extracting(BuiltInQProfileInsertRule.CallLog::getOrganizationDto) .extracting(OrganizationDto::getUuid) .containsOnly(organization.getUuid()); - assertThat(definedQProfileCreationRule.getCallLogs()) - .extracting(DefinedQProfileInsertRule.CallLog::getDefinedQProfile) - .extracting(DefinedQProfile::getName) - .containsExactly(definedQProfile1.getName(), definedQProfile2.getName(), definedQProfile3.getName(), definedQProfile4.getName()); + assertThat(builtInQProfileCreationRule.getCallLogs()) + .extracting(BuiltInQProfileInsertRule.CallLog::getDefinedQProfile) + .extracting(BuiltInQProfile::getName) + .containsExactly(builtInQProfile1.getName(), builtInQProfile2.getName(), builtInQProfile3.getName(), builtInQProfile4.getName()); } private static ActiveRuleChange newActiveRuleChange(String id) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java index 97c2c676528..391c021900a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java @@ -55,8 +55,8 @@ import org.sonar.server.organization.OrganizationCreationImpl; import org.sonar.server.organization.OrganizationValidation; import org.sonar.server.organization.OrganizationValidationImpl; import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.qualityprofile.DefinedQProfileInsert; -import org.sonar.server.qualityprofile.DefinedQProfileRepository; +import org.sonar.server.qualityprofile.BuiltInQProfileInsert; +import org.sonar.server.qualityprofile.BuiltInQProfileRepository; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.user.index.UserIndex; @@ -103,7 +103,7 @@ public class CreateActionTest { private UserIndex userIndex = new UserIndex(es.client()); private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); private OrganizationCreation organizationCreation = new OrganizationCreationImpl(dbClient, system2, uuidFactory, organizationValidation, settings, userIndexer, - mock(DefinedQProfileRepository.class), mock(DefinedQProfileInsert.class), new DefaultGroupCreatorImpl(dbClient), activeRuleIndexer); + mock(BuiltInQProfileRepository.class), mock(BuiltInQProfileInsert.class), new DefaultGroupCreatorImpl(dbClient), activeRuleIndexer); private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone().setEnabled(true); private UserDto user; diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImplTest.java new file mode 100644 index 00000000000..6d054b65d8e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationImplTest.java @@ -0,0 +1,332 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import javax.annotation.Nullable; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.resources.Language; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.ActiveRule; +import org.sonar.api.rules.RuleParam; +import org.sonar.api.rules.RulePriority; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.qualityprofile.ActiveRuleKey; +import org.sonar.db.qualityprofile.QualityProfileDto; +import org.sonar.server.language.LanguageTesting; +import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; +import org.sonar.server.tester.UserSessionRule; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.core.util.UtcDateUtils.formatDateTime; + +public class BuiltInQProfileCreationImplTest { + private static final Language FOO_LANGUAGE = LanguageTesting.newLanguage("foo", "foo", "foo"); + private static final String TABLE_RULES_PROFILES = "RULES_PROFILES"; + private static final String TABLE_LOADED_TEMPLATES = "loaded_templates"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + @Rule + public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule(); + + private DbClient dbClient = dbTester.getDbClient(); + private DbSession dbSession = dbClient.openSession(false); + private UuidFactory mockedUuidFactory = mock(UuidFactory.class); + private System2 mockedSystem2 = mock(System2.class); + private RuleActivator mockedRuleActivator = mock(RuleActivator.class); + private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); + private BuiltInQProfileCreationImpl underTest = new BuiltInQProfileCreationImpl( + dbClient, + new QProfileFactory(dbClient, mockedUuidFactory, mockedSystem2, activeRuleIndexer), + mockedRuleActivator); + private List activeRuleChanges = new ArrayList<>(); + + @After + public void tearDown() throws Exception { + dbSession.close(); + } + + @Test + public void create_creates_qp_and_store_flag_in_loaded_templates_for_specified_organization() { + OrganizationDto organization = dbTester.organizations().insert(); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", false); + long date = 2_456_789L; + String uuid = "uuid 1"; + mockForSingleQPInsert(uuid, date); + + underTest.create(dbSession, builtInQProfile, organization, activeRuleChanges); + dbSession.commit(); + + QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1"); + assertThat(dto.getId()).isNotNull(); + assertThat(dto.getOrganizationUuid()).isEqualTo(organization.getUuid()); + assertThat(dto.getLanguage()).isEqualTo(FOO_LANGUAGE.getKey()); + assertThat(dto.getName()).isEqualTo("foo1"); + assertThat(dto.getKee()).isEqualTo(uuid); + assertThat(dto.getKey()).isEqualTo(dto.getKee()); + assertThat(dto.getParentKee()).isNull(); + assertThat(dto.getRulesUpdatedAt()).isIn(formatDateTime(new Date(date))); + assertThat(dto.getLastUsed()).isNull(); + assertThat(dto.getUserUpdatedAt()).isNull(); + assertThat(dto.isDefault()).isFalse(); + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1); + + assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(builtInQProfile.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession())) + .isEqualTo(1); + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_LOADED_TEMPLATES)).isEqualTo(1); + assertThat(activeRuleChanges).isEmpty(); + } + + @Test + public void create_persists_default_flag_of_DefinedQProfile() { + OrganizationDto organization = dbTester.organizations().insert(); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true); + mockForSingleQPInsert(); + + underTest.create(dbSession, builtInQProfile, organization, activeRuleChanges); + dbSession.commit(); + + assertThat(getPersistedQP(organization, FOO_LANGUAGE, "foo1").isDefault()).isTrue(); + assertThat(activeRuleChanges).isEmpty(); + } + + @Test + public void create_does_not_update_existing_profile_if_it_already_exists() { + OrganizationDto organization = dbTester.organizations().insert(); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", false); + long date = 2_456_789L; + String uuid = "uuid 1"; + mockForSingleQPInsert(uuid, date); + QualityProfileDto existing = dbTester.qualityProfiles().insertQualityProfile( + QualityProfileDto.createFor("a key") + .setName(builtInQProfile.getName()) + .setLanguage(builtInQProfile.getLanguage()) + .setOrganizationUuid(organization.getUuid())); + + underTest.create(dbSession, builtInQProfile, organization, activeRuleChanges); + dbSession.commit(); + + QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1"); + assertThat(dto.getId()).isEqualTo(existing.getId()); + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1); + } + + @Test + public void create_persists_relies_on_ruleActivator_to_persist_activerules_and_return_all_changes_in_order() { + List callLogs = new ArrayList<>(); + ActiveRuleChange[] changes = {newActiveRuleChange("0"), newActiveRuleChange("1"), newActiveRuleChange("2"), + newActiveRuleChange("3"), newActiveRuleChange("4")}; + mockRuleActivatorActivate(callLogs, + asList(changes[4], changes[1]), + Collections.emptyList(), + Collections.singletonList(changes[0]), + Collections.emptyList(), + asList(changes[2], changes[3])); + + OrganizationDto organization = dbTester.organizations().insert(); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, + activeRule("A", RulePriority.INFO), activeRule("B", RulePriority.MINOR), activeRule("C", RulePriority.MAJOR), + activeRule("D", RulePriority.CRITICAL), activeRule("E", RulePriority.BLOCKER)); + mockForSingleQPInsert(); + + underTest.create(dbSession, builtInQProfile, organization, activeRuleChanges); + dbSession.commit(); + + assertThat(callLogs) + .hasSize(5); + assertThat(activeRuleChanges) + .containsExactly(changes[4], changes[1], changes[0], changes[2], changes[3]); + } + + @Test + public void create_creates_ruleKey_in_RuleActivation_from_ActiveRule() { + List callLogs = new ArrayList<>(); + mockRuleActivatorActivate(callLogs, Collections.emptyList()); + + OrganizationDto organization = dbTester.organizations().insert(); + ActiveRule activeRule = activeRule("A", RulePriority.BLOCKER); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, activeRule); + mockForSingleQPInsert(); + + underTest.create(dbSession, builtInQProfile, organization, activeRuleChanges); + dbSession.commit(); + + assertThat(callLogs) + .hasSize(1); + assertThat(callLogs.get(0).getRuleActivation()) + .matches(s -> s.getRuleKey().equals(RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()))); + assertThat(callLogs.get(0).qualityProfileDto.getId()) + .isEqualTo(getPersistedQP(organization, FOO_LANGUAGE, "foo1").getId()); + } + + @Test + public void create_supports_all_RulePriority_values_and_null() { + List callLogs = new ArrayList<>(); + mockRuleActivatorActivate(callLogs, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList()); + + OrganizationDto organization = dbTester.organizations().insert(); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, + activeRule("A", RulePriority.INFO), activeRule("B", RulePriority.MINOR), activeRule("C", RulePriority.MAJOR), + activeRule("D", RulePriority.CRITICAL), activeRule("E", RulePriority.BLOCKER), activeRule("F", null)); + mockForSingleQPInsert(); + + underTest.create(dbSession, builtInQProfile, organization, activeRuleChanges); + dbSession.commit(); + + assertThat(callLogs) + .extracting(CallLog::getRuleActivation) + .extracting(RuleActivation::getSeverity) + .containsExactly("INFO", "MINOR", "MAJOR", "CRITICAL", "BLOCKER", "MAJOR"); + assertThat(callLogs) + .extracting(CallLog::getQualityProfileDto) + .extracting(QualityProfileDto::getId) + .containsOnly(getPersistedQP(organization, FOO_LANGUAGE, "foo1").getId()); + } + + @Test + public void create_adds_all_parameters_to_RuleActivation() { + List callLogs = new ArrayList<>(); + mockRuleActivatorActivate(callLogs, Collections.emptyList()); + + OrganizationDto organization = dbTester.organizations().insert(); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, + activeRule("A", RulePriority.INFO, + "param1", "value1", + "param2", "value2", + "param3", "value3", + "param4", "value4")); + mockForSingleQPInsert(); + + underTest.create(dbSession, builtInQProfile, organization, activeRuleChanges); + dbSession.commit(); + + Set> parameters = callLogs.get(0).getRuleActivation().getParameters().entrySet(); + assertThat(parameters) + .extracting(Map.Entry::getKey) + .containsOnlyOnce("param1", "param2", "param3", "param4"); + assertThat(parameters) + .extracting(Map.Entry::getValue) + .containsOnlyOnce("value1", "value2", "value3", "value4"); + } + + private void mockRuleActivatorActivate(List callLogs, List... changesPerCall) { + Iterator> changesPerCallIt = Arrays.asList(changesPerCall).iterator(); + doAnswer(t -> { + RuleActivation ruleActivation = t.getArgumentAt(1, RuleActivation.class); + QualityProfileDto qualityProfileDto = t.getArgumentAt(2, QualityProfileDto.class); + callLogs.add(new CallLog(ruleActivation, qualityProfileDto)); + return changesPerCallIt.next(); + }).when(mockedRuleActivator) + .activate(any(DbSession.class), any(RuleActivation.class), any(QualityProfileDto.class)); + } + + private static ActiveRule activeRule(String id, @Nullable RulePriority rulePriority, String... parameters) { + org.sonar.api.rules.Rule rule = new org.sonar.api.rules.Rule("plugin_name_" + id, "rule_key_" + id); + rule.setParams(Arrays.stream(parameters) + .filter(new EvenElementPredicate()) + .map(s -> new RuleParam(rule, s, "desc of s", "type of s")) + .collect(MoreCollectors.toList(parameters.length / 2))); + ActiveRule res = new ActiveRule( + new RulesProfile("rule_profile_name_" + id, "language_" + id), + rule.setSeverity(rulePriority), + rulePriority); + for (int i = 0; i < parameters.length; i++) { + res.setParameter(parameters[i], parameters[i + 1]); + i++; + } + return res; + } + + private static final class CallLog { + private final RuleActivation ruleActivation; + private final QualityProfileDto qualityProfileDto; + + private CallLog(RuleActivation ruleActivation, QualityProfileDto qualityProfileDto) { + this.ruleActivation = ruleActivation; + this.qualityProfileDto = qualityProfileDto; + } + + public RuleActivation getRuleActivation() { + return ruleActivation; + } + + public QualityProfileDto getQualityProfileDto() { + return qualityProfileDto; + } + } + + private void mockForSingleQPInsert() { + mockForSingleQPInsert("generated uuid", 2_456_789); + } + + 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")); + } + + private QualityProfileDto getPersistedQP(OrganizationDto organization, Language language, String name) { + return dbClient.qualityProfileDao().selectByNameAndLanguage(organization, name, language.getKey(), dbTester.getSession()); + } + + private static ActiveRuleChange newActiveRuleChange(String id) { + return ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(id, RuleKey.of(id + "1", id + "2"))); + } + + private static class EvenElementPredicate implements Predicate { + private int counter = -1; + + @Override + public boolean test(String s) { + counter++; + return counter % 2 == 0; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationRule.java new file mode 100644 index 00000000000..e43042e61b0 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileCreationRule.java @@ -0,0 +1,85 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.junit.rules.ExternalResource; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; + +import static com.google.common.base.Preconditions.checkState; + +public final class BuiltInQProfileCreationRule extends ExternalResource implements BuiltInQProfileCreation { + private final List callLogs = new ArrayList<>(); + private List> changesPerCall = null; + private Iterator> changesPerCallIterator = null; + + @Override + protected void before() throws Throwable { + callLogs.clear(); + changesPerCall = null; + changesPerCallIterator = null; + } + + @Override + public void create(DbSession session, BuiltInQProfile qualityProfile, OrganizationDto organization, List changes) { + callLogs.add(new CallLog(qualityProfile, organization)); + + if (changesPerCallIterator == null) { + changesPerCallIterator = changesPerCall == null ? Collections.>emptyList().iterator() : changesPerCall.iterator(); + } + changes.addAll(changesPerCallIterator.next()); + } + + public BuiltInQProfileCreationRule addChanges(ActiveRuleChange... changes) { + checkState(changesPerCallIterator == null, "Can't add changes if BuiltInQProfileCreation is in use"); + if (changesPerCall == null) { + changesPerCall = new ArrayList<>(); + } + changesPerCall.add(Arrays.asList(changes)); + return this; + } + + public List getCallLogs() { + return callLogs; + } + + public static final class CallLog { + private final BuiltInQProfile builtInQProfile; + private final OrganizationDto organizationDto; + + private CallLog(BuiltInQProfile builtInQProfile, OrganizationDto organizationDto) { + this.builtInQProfile = builtInQProfile; + this.organizationDto = organizationDto; + } + + public BuiltInQProfile getDefinedQProfile() { + return builtInQProfile; + } + + public OrganizationDto getOrganizationDto() { + return organizationDto; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertRule.java new file mode 100644 index 00000000000..63cc17571b5 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertRule.java @@ -0,0 +1,62 @@ +/* + * 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.List; +import org.junit.rules.ExternalResource; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; + +public class BuiltInQProfileInsertRule extends ExternalResource implements BuiltInQProfileInsert { + private final List callLogs = new ArrayList<>(); + + @Override + protected void before() throws Throwable { + callLogs.clear(); + } + + @Override + public void create(DbSession session, DbSession batchSession, BuiltInQProfile builtInQProfile, OrganizationDto organization) { + callLogs.add(new BuiltInQProfileInsertRule.CallLog(builtInQProfile, organization)); + } + + public List getCallLogs() { + return callLogs; + } + + public static final class CallLog { + private final BuiltInQProfile builtInQProfile; + private final OrganizationDto organizationDto; + + private CallLog(BuiltInQProfile builtInQProfile, OrganizationDto organizationDto) { + this.builtInQProfile = builtInQProfile; + this.organizationDto = organizationDto; + } + + public BuiltInQProfile getDefinedQProfile() { + return builtInQProfile; + } + + public OrganizationDto getOrganizationDto() { + return organizationDto; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileLoaderTest.java new file mode 100644 index 00000000000..096bdb86c7b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileLoaderTest.java @@ -0,0 +1,39 @@ +/* + * 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 org.junit.Rule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BuiltInQProfileLoaderTest { + @Rule + public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule(); + + private BuiltInQProfileLoader underTest = new BuiltInQProfileLoader(builtInQProfileRepositoryRule); + + @Test + public void start_initializes_DefinedQProfileRepository() { + underTest.start(); + + assertThat(builtInQProfileRepositoryRule.isInitialized()).isTrue(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java new file mode 100644 index 00000000000..c1234f87376 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java @@ -0,0 +1,291 @@ +/* + * 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 BuiltInQProfileRepositoryImplTest { + 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() { + BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(new Languages()); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("initialize must be called first"); + + underTest.getQProfilesByLanguage(); + } + + @Test + public void initialize_throws_ISE_if_called_twice() { + BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(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() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(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() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(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); + 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_qp_of_a_language_default_even_if_not_flagged_as_so() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", false)); + + underTest.initialize(); + + Map> qProfilesByLanguage = underTest.getQProfilesByLanguage(); + assertThat(qProfilesByLanguage) + .hasSize(1) + .containsOnlyKeys(FOO_LANGUAGE.getKey()); + assertThat(qProfilesByLanguage.get(FOO_LANGUAGE.getKey())) + .extracting(BuiltInQProfile::isDefault) + .containsExactly(true); + } + + @Test + public void initialize_makes_single_qp_of_a_language_default_even_if_flagged_as_so() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", true)); + + underTest.initialize(); + + Map> qProfilesByLanguage = underTest.getQProfilesByLanguage(); + assertThat(qProfilesByLanguage) + .hasSize(1) + .containsOnlyKeys(FOO_LANGUAGE.getKey()); + assertThat(qProfilesByLanguage.get(FOO_LANGUAGE.getKey())) + .extracting(BuiltInQProfile::isDefault) + .containsExactly(true); + } + + @Test + public void initialize_makes_first_qp_of_a_language_default_when_none_flagged_as_so() { + List 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(); + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), definitions.stream().toArray(ProfileDefinition[]::new)); + + underTest.initialize(); + + Map> qProfilesByLanguage = underTest.getQProfilesByLanguage(); + assertThat(qProfilesByLanguage) + .hasSize(1) + .containsOnlyKeys(FOO_LANGUAGE.getKey()); + List fooBuiltInQProfiles = qProfilesByLanguage.get(FOO_LANGUAGE.getKey()); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::getName) + .containsExactly(firstQPName, secondQPName); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::isDefault) + .containsExactly(true, false); + } + + @Test + public void initialize_fails_with_ISE_when_two_sq_with_different_name_are_default_for_the_same_language() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(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(); + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), + new DummyProfileDefinition("foo", name, flag), new DummyProfileDefinition("foo", name, !flag)); + + underTest.initialize(); + + assertThat(underTest.getQProfilesByLanguage().get(FOO_LANGUAGE.getKey())) + .extracting(BuiltInQProfile::isDefault) + .containsExactly(true); + } + + @Test + public void initialize_creates_single_qp_if_several_profile_have_the_same_name_for_a_given_language() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(new Languages(FOO_LANGUAGE), + new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo1", true)); + + underTest.initialize(); + + Map> qProfilesByLanguage = underTest.getQProfilesByLanguage(); + assertThat(qProfilesByLanguage) + .hasSize(1) + .containsOnlyKeys(FOO_LANGUAGE.getKey()); + assertThat(qProfilesByLanguage.get(FOO_LANGUAGE.getKey())) + .extracting(BuiltInQProfile::getName) + .containsExactly("foo1"); + } + + @Test + public void initialize_creates_qp_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl( + 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> qProfilesByLanguage = underTest.getQProfilesByLanguage(); + assertThat(qProfilesByLanguage) + .hasSize(1) + .containsOnlyKeys(FOO_LANGUAGE.getKey()); + List fooBuiltInQProfiles = qProfilesByLanguage.get(FOO_LANGUAGE.getKey()); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::getName) + .containsExactly("doh", "boo", SONAR_WAY_QP_NAME, "goo"); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::isDefault) + .containsExactly(false, false, true, false); + } + + @Test + public void initialize_does_not_create_Sonar_Way_as_default_if_other_profile_is_defined_as_default() { + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl( + new Languages(FOO_LANGUAGE), + new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", true)); + + underTest.initialize(); + + List fooBuiltInQProfiles = underTest.getQProfilesByLanguage().get(FOO_LANGUAGE.getKey()); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::getName) + .containsExactly(SONAR_WAY_QP_NAME, "goo"); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::isDefault) + .containsExactly(false, true); + } + + @Test + public void initialize_matches_Sonar_Way_default_with_case_sensitivity() { + String sonarWayInOtherCase = SONAR_WAY_QP_NAME.toUpperCase(); + BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl( + new Languages(FOO_LANGUAGE), + new DummyProfileDefinition("foo", "goo", false), new DummyProfileDefinition("foo", sonarWayInOtherCase, false)); + + underTest.initialize(); + + List fooBuiltInQProfiles = underTest.getQProfilesByLanguage().get(FOO_LANGUAGE.getKey()); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::getName) + .containsExactly("goo", sonarWayInOtherCase); + assertThat(fooBuiltInQProfiles) + .extracting(BuiltInQProfile::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; + } + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java new file mode 100644 index 00000000000..6bb2427bf92 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java @@ -0,0 +1,95 @@ +/* + * 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.MoreCollectors; + +import static com.google.common.base.Preconditions.checkState; +import static org.sonar.core.util.stream.MoreCollectors.toList; + +public class BuiltInQProfileRepositoryRule extends ExternalResource implements BuiltInQProfileRepository { + private boolean initializeCalled = false; + private Map> 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> getQProfilesByLanguage() { + checkState(initializeCalled, "initialize must be called first"); + + return ImmutableMap.copyOf(qProfilesbyLanguage); + } + + public boolean isInitialized() { + return initializeCalled; + } + + public BuiltInQProfileRepositoryRule set(String languageKey, BuiltInQProfile first, BuiltInQProfile... others) { + qProfilesbyLanguage.put( + languageKey, + Stream.concat(Stream.of(first), Arrays.stream(others)).collect(toList(1 + others.length))); + return this; + } + + public BuiltInQProfile add(Language language, String profileName) { + return add(language, profileName, false); + } + + public BuiltInQProfile add(Language language, String profileName, boolean isDefault) { + BuiltInQProfile builtInQProfile = create(language, profileName, isDefault); + qProfilesbyLanguage.compute(language.getKey(), + (key, existing) -> { + if (existing == null) { + return ImmutableList.of(builtInQProfile); + } + return Stream.concat(existing.stream(), Stream.of(builtInQProfile)).collect(MoreCollectors.toList(existing.size() + 1)); + }); + return builtInQProfile; + } + + public BuiltInQProfile create(Language language, String profileName, boolean isDefault, org.sonar.api.rules.ActiveRule... rules) { + return new BuiltInQProfile.Builder() + .setLanguage(language.getKey()) + .setName(profileName) + .setDeclaredDefault(isDefault) + .addRules(Arrays.asList(rules)) + .build(DigestUtils.getMd5Digest()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java deleted file mode 100644 index 20add6673d4..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * 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.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Predicate; -import javax.annotation.Nullable; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.resources.Language; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.ActiveRule; -import org.sonar.api.rules.RuleParam; -import org.sonar.api.rules.RulePriority; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactory; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.qualityprofile.ActiveRuleKey; -import org.sonar.db.qualityprofile.QualityProfileDto; -import org.sonar.server.language.LanguageTesting; -import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; -import org.sonar.server.tester.UserSessionRule; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.core.util.UtcDateUtils.formatDateTime; - -public class DefinedQProfileCreationImplTest { - private static final Language FOO_LANGUAGE = LanguageTesting.newLanguage("foo", "foo", "foo"); - private static final String TABLE_RULES_PROFILES = "RULES_PROFILES"; - private static final String TABLE_LOADED_TEMPLATES = "loaded_templates"; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public UserSessionRule userSessionRule = UserSessionRule.standalone(); - @Rule - public DefinedQProfileRepositoryRule definedQProfileRepositoryRule = new DefinedQProfileRepositoryRule(); - - private DbClient dbClient = dbTester.getDbClient(); - private DbSession dbSession = dbClient.openSession(false); - private UuidFactory mockedUuidFactory = mock(UuidFactory.class); - private System2 mockedSystem2 = mock(System2.class); - private RuleActivator mockedRuleActivator = mock(RuleActivator.class); - private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); - private DefinedQProfileCreationImpl underTest = new DefinedQProfileCreationImpl( - dbClient, - new QProfileFactory(dbClient, mockedUuidFactory, mockedSystem2, activeRuleIndexer), - mockedRuleActivator); - private List activeRuleChanges = new ArrayList<>(); - - @After - public void tearDown() throws Exception { - dbSession.close(); - } - - @Test - public void create_creates_qp_and_store_flag_in_loaded_templates_for_specified_organization() { - OrganizationDto organization = dbTester.organizations().insert(); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", false); - long date = 2_456_789L; - String uuid = "uuid 1"; - mockForSingleQPInsert(uuid, date); - - underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); - dbSession.commit(); - - QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1"); - assertThat(dto.getId()).isNotNull(); - assertThat(dto.getOrganizationUuid()).isEqualTo(organization.getUuid()); - assertThat(dto.getLanguage()).isEqualTo(FOO_LANGUAGE.getKey()); - assertThat(dto.getName()).isEqualTo("foo1"); - assertThat(dto.getKee()).isEqualTo(uuid); - assertThat(dto.getKey()).isEqualTo(dto.getKee()); - assertThat(dto.getParentKee()).isNull(); - assertThat(dto.getRulesUpdatedAt()).isIn(formatDateTime(new Date(date))); - assertThat(dto.getLastUsed()).isNull(); - assertThat(dto.getUserUpdatedAt()).isNull(); - assertThat(dto.isDefault()).isFalse(); - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1); - - assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession())) - .isEqualTo(1); - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_LOADED_TEMPLATES)).isEqualTo(1); - assertThat(activeRuleChanges).isEmpty(); - } - - @Test - public void create_persists_default_flag_of_DefinedQProfile() { - OrganizationDto organization = dbTester.organizations().insert(); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true); - mockForSingleQPInsert(); - - underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); - dbSession.commit(); - - assertThat(getPersistedQP(organization, FOO_LANGUAGE, "foo1").isDefault()).isTrue(); - assertThat(activeRuleChanges).isEmpty(); - } - - @Test - public void create_does_not_update_existing_profile_if_it_already_exists() { - OrganizationDto organization = dbTester.organizations().insert(); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", false); - long date = 2_456_789L; - String uuid = "uuid 1"; - mockForSingleQPInsert(uuid, date); - QualityProfileDto existing = dbTester.qualityProfiles().insertQualityProfile( - QualityProfileDto.createFor("a key") - .setName(definedQProfile.getName()) - .setLanguage(definedQProfile.getLanguage()) - .setOrganizationUuid(organization.getUuid())); - - underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); - dbSession.commit(); - - QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1"); - assertThat(dto.getId()).isEqualTo(existing.getId()); - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1); - } - - @Test - public void create_persists_relies_on_ruleActivator_to_persist_activerules_and_return_all_changes_in_order() { - List callLogs = new ArrayList<>(); - ActiveRuleChange[] changes = {newActiveRuleChange("0"), newActiveRuleChange("1"), newActiveRuleChange("2"), - newActiveRuleChange("3"), newActiveRuleChange("4")}; - mockRuleActivatorActivate(callLogs, - asList(changes[4], changes[1]), - Collections.emptyList(), - Collections.singletonList(changes[0]), - Collections.emptyList(), - asList(changes[2], changes[3])); - - OrganizationDto organization = dbTester.organizations().insert(); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, - activeRule("A", RulePriority.INFO), activeRule("B", RulePriority.MINOR), activeRule("C", RulePriority.MAJOR), - activeRule("D", RulePriority.CRITICAL), activeRule("E", RulePriority.BLOCKER)); - mockForSingleQPInsert(); - - underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); - dbSession.commit(); - - assertThat(callLogs) - .hasSize(5); - assertThat(activeRuleChanges) - .containsExactly(changes[4], changes[1], changes[0], changes[2], changes[3]); - } - - @Test - public void create_creates_ruleKey_in_RuleActivation_from_ActiveRule() { - List callLogs = new ArrayList<>(); - mockRuleActivatorActivate(callLogs, Collections.emptyList()); - - OrganizationDto organization = dbTester.organizations().insert(); - ActiveRule activeRule = activeRule("A", RulePriority.BLOCKER); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, activeRule); - mockForSingleQPInsert(); - - underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); - dbSession.commit(); - - assertThat(callLogs) - .hasSize(1); - assertThat(callLogs.get(0).getRuleActivation()) - .matches(s -> s.getRuleKey().equals(RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()))); - assertThat(callLogs.get(0).qualityProfileDto.getId()) - .isEqualTo(getPersistedQP(organization, FOO_LANGUAGE, "foo1").getId()); - } - - @Test - public void create_supports_all_RulePriority_values_and_null() { - List callLogs = new ArrayList<>(); - mockRuleActivatorActivate(callLogs, - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList()); - - OrganizationDto organization = dbTester.organizations().insert(); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, - activeRule("A", RulePriority.INFO), activeRule("B", RulePriority.MINOR), activeRule("C", RulePriority.MAJOR), - activeRule("D", RulePriority.CRITICAL), activeRule("E", RulePriority.BLOCKER), activeRule("F", null)); - mockForSingleQPInsert(); - - underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); - dbSession.commit(); - - assertThat(callLogs) - .extracting(CallLog::getRuleActivation) - .extracting(RuleActivation::getSeverity) - .containsExactly("INFO", "MINOR", "MAJOR", "CRITICAL", "BLOCKER", "MAJOR"); - assertThat(callLogs) - .extracting(CallLog::getQualityProfileDto) - .extracting(QualityProfileDto::getId) - .containsOnly(getPersistedQP(organization, FOO_LANGUAGE, "foo1").getId()); - } - - @Test - public void create_adds_all_parameters_to_RuleActivation() { - List callLogs = new ArrayList<>(); - mockRuleActivatorActivate(callLogs, Collections.emptyList()); - - OrganizationDto organization = dbTester.organizations().insert(); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, - activeRule("A", RulePriority.INFO, - "param1", "value1", - "param2", "value2", - "param3", "value3", - "param4", "value4")); - mockForSingleQPInsert(); - - underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); - dbSession.commit(); - - Set> parameters = callLogs.get(0).getRuleActivation().getParameters().entrySet(); - assertThat(parameters) - .extracting(Map.Entry::getKey) - .containsOnlyOnce("param1", "param2", "param3", "param4"); - assertThat(parameters) - .extracting(Map.Entry::getValue) - .containsOnlyOnce("value1", "value2", "value3", "value4"); - } - - private void mockRuleActivatorActivate(List callLogs, List... changesPerCall) { - Iterator> changesPerCallIt = Arrays.asList(changesPerCall).iterator(); - doAnswer(t -> { - RuleActivation ruleActivation = t.getArgumentAt(1, RuleActivation.class); - QualityProfileDto qualityProfileDto = t.getArgumentAt(2, QualityProfileDto.class); - callLogs.add(new CallLog(ruleActivation, qualityProfileDto)); - return changesPerCallIt.next(); - }).when(mockedRuleActivator) - .activate(any(DbSession.class), any(RuleActivation.class), any(QualityProfileDto.class)); - } - - private static ActiveRule activeRule(String id, @Nullable RulePriority rulePriority, String... parameters) { - org.sonar.api.rules.Rule rule = new org.sonar.api.rules.Rule("plugin_name_" + id, "rule_key_" + id); - rule.setParams(Arrays.stream(parameters) - .filter(new EvenElementPredicate()) - .map(s -> new RuleParam(rule, s, "desc of s", "type of s")) - .collect(MoreCollectors.toList(parameters.length / 2))); - ActiveRule res = new ActiveRule( - new RulesProfile("rule_profile_name_" + id, "language_" + id), - rule.setSeverity(rulePriority), - rulePriority); - for (int i = 0; i < parameters.length; i++) { - res.setParameter(parameters[i], parameters[i + 1]); - i++; - } - return res; - } - - private static final class CallLog { - private final RuleActivation ruleActivation; - private final QualityProfileDto qualityProfileDto; - - private CallLog(RuleActivation ruleActivation, QualityProfileDto qualityProfileDto) { - this.ruleActivation = ruleActivation; - this.qualityProfileDto = qualityProfileDto; - } - - public RuleActivation getRuleActivation() { - return ruleActivation; - } - - public QualityProfileDto getQualityProfileDto() { - return qualityProfileDto; - } - } - - private void mockForSingleQPInsert() { - mockForSingleQPInsert("generated uuid", 2_456_789); - } - - 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")); - } - - private QualityProfileDto getPersistedQP(OrganizationDto organization, Language language, String name) { - return dbClient.qualityProfileDao().selectByNameAndLanguage(organization, name, language.getKey(), dbTester.getSession()); - } - - private static ActiveRuleChange newActiveRuleChange(String id) { - return ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(id, RuleKey.of(id + "1", id + "2"))); - } - - private static class EvenElementPredicate implements Predicate { - private int counter = -1; - - @Override - public boolean test(String s) { - counter++; - return counter % 2 == 0; - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationRule.java deleted file mode 100644 index 1904225bd32..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationRule.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import org.junit.rules.ExternalResource; -import org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; - -import static com.google.common.base.Preconditions.checkState; - -public final class DefinedQProfileCreationRule extends ExternalResource implements DefinedQProfileCreation { - private final List callLogs = new ArrayList<>(); - private List> changesPerCall = null; - private Iterator> changesPerCallIterator = null; - - @Override - protected void before() throws Throwable { - callLogs.clear(); - changesPerCall = null; - changesPerCallIterator = null; - } - - @Override - public void create(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes) { - callLogs.add(new CallLog(qualityProfile, organization)); - - if (changesPerCallIterator == null) { - changesPerCallIterator = changesPerCall == null ? Collections.>emptyList().iterator() : changesPerCall.iterator(); - } - changes.addAll(changesPerCallIterator.next()); - } - - public DefinedQProfileCreationRule addChanges(ActiveRuleChange... changes) { - checkState(changesPerCallIterator == null, "Can't add changes if DefinedQProfileCreation is in use"); - if (changesPerCall == null) { - changesPerCall = new ArrayList<>(); - } - changesPerCall.add(Arrays.asList(changes)); - return this; - } - - public List getCallLogs() { - return callLogs; - } - - public static final class CallLog { - private final DefinedQProfile definedQProfile; - private final OrganizationDto organizationDto; - - private CallLog(DefinedQProfile definedQProfile, OrganizationDto organizationDto) { - this.definedQProfile = definedQProfile; - this.organizationDto = organizationDto; - } - - public DefinedQProfile getDefinedQProfile() { - return definedQProfile; - } - - public OrganizationDto getOrganizationDto() { - return organizationDto; - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileInsertRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileInsertRule.java deleted file mode 100644 index bbf09367d3a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileInsertRule.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.List; -import org.junit.rules.ExternalResource; -import org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; - -public class DefinedQProfileInsertRule extends ExternalResource implements DefinedQProfileInsert { - private final List callLogs = new ArrayList<>(); - - @Override - protected void before() throws Throwable { - callLogs.clear(); - } - - @Override - public void create(DbSession session, DbSession batchSession, DefinedQProfile definedQProfile, OrganizationDto organization) { - callLogs.add(new DefinedQProfileInsertRule.CallLog(definedQProfile, organization)); - } - - public List getCallLogs() { - return callLogs; - } - - public static final class CallLog { - private final DefinedQProfile definedQProfile; - private final OrganizationDto organizationDto; - - private CallLog(DefinedQProfile definedQProfile, OrganizationDto organizationDto) { - this.definedQProfile = definedQProfile; - this.organizationDto = organizationDto; - } - - public DefinedQProfile getDefinedQProfile() { - return definedQProfile; - } - - public OrganizationDto getOrganizationDto() { - return organizationDto; - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileLoaderTest.java deleted file mode 100644 index 8828ff22fcc..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileLoaderTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 org.junit.Rule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DefinedQProfileLoaderTest { - @Rule - public DefinedQProfileRepositoryRule definedQProfileRepositoryRule = new DefinedQProfileRepositoryRule(); - - private DefinedQProfileLoader underTest = new DefinedQProfileLoader(definedQProfileRepositoryRule); - - @Test - public void start_initializes_DefinedQProfileRepository() { - underTest.start(); - - assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImplTest.java deleted file mode 100644 index a302f6dd78e..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImplTest.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * 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> 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> 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 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> qProfilesByLanguage = underTest.getQProfilesByLanguage(); - assertThat(qProfilesByLanguage) - .hasSize(1) - .containsOnlyKeys(FOO_LANGUAGE.getKey()); - List 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> 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> qProfilesByLanguage = underTest.getQProfilesByLanguage(); - assertThat(qProfilesByLanguage) - .hasSize(1) - .containsOnlyKeys(FOO_LANGUAGE.getKey()); - List 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 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 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; - } - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java deleted file mode 100644 index 65a6091a7b6..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.MoreCollectors; - -import static com.google.common.base.Preconditions.checkState; -import static org.sonar.core.util.stream.MoreCollectors.toList; - -public class DefinedQProfileRepositoryRule extends ExternalResource implements DefinedQProfileRepository { - private boolean initializeCalled = false; - private Map> 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> 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(MoreCollectors.toList(existing.size() + 1)); - }); - return definedQProfile; - } - - public DefinedQProfile create(Language language, String profileName, boolean isDefault, org.sonar.api.rules.ActiveRule... rules) { - return new DefinedQProfile.Builder() - .setLanguage(language.getKey()) - .setName(profileName) - .setDeclaredDefault(isDefault) - .addRules(Arrays.asList(rules)) - .build(DigestUtils.getMd5Digest()); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileFactoryMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileFactoryMediumTest.java index 64cea1e7730..8e4bf5839c2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileFactoryMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileFactoryMediumTest.java @@ -69,7 +69,7 @@ public class QProfileFactoryMediumTest { public void checkAndCreate() { String uuid = organization.getUuid(); - QualityProfileDto writtenDto = factory.checkAndCreate(dbSession, organization, new QProfileName("xoo", "P1")); + QualityProfileDto writtenDto = factory.checkAndCreateCustom(dbSession, organization, new QProfileName("xoo", "P1")); dbSession.commit(); dbSession.clearCache(); assertThat(writtenDto.getOrganizationUuid()).isEqualTo(uuid); @@ -77,6 +77,7 @@ public class QProfileFactoryMediumTest { assertThat(writtenDto.getName()).isEqualTo("P1"); assertThat(writtenDto.getLanguage()).isEqualTo("xoo"); assertThat(writtenDto.getId()).isNotNull(); + assertThat(writtenDto.isBuiltIn()).isFalse(); // reload the dto QualityProfileDto readDto = db.qualityProfileDao().selectByNameAndLanguage(organization, "P1", "xoo", dbSession); @@ -89,7 +90,7 @@ public class QProfileFactoryMediumTest { public void create() { String uuid = organization.getUuid(); - QualityProfileDto writtenDto = factory.create(dbSession, organization, new QProfileName("xoo", "P1"), true); + QualityProfileDto writtenDto = factory.createBuiltIn(dbSession, organization, new QProfileName("xoo", "P1"), true); dbSession.commit(); dbSession.clearCache(); assertThat(writtenDto.getOrganizationUuid()).isEqualTo(uuid); @@ -113,7 +114,7 @@ public class QProfileFactoryMediumTest { expectBadRequestException("quality_profiles.profile_name_cant_be_blank"); - factory.checkAndCreate(dbSession, organization, name); + factory.checkAndCreateCustom(dbSession, organization, name); } @Test @@ -122,19 +123,19 @@ public class QProfileFactoryMediumTest { expectBadRequestException("quality_profiles.profile_name_cant_be_blank"); - factory.checkAndCreate(dbSession, organization, name); + factory.checkAndCreateCustom(dbSession, organization, name); } @Test public void checkAndCreate_throws_BadRequestException_if_already_exists() { QProfileName name = new QProfileName("xoo", "P1"); - factory.checkAndCreate(dbSession, organization, name); + factory.checkAndCreateCustom(dbSession, organization, name); dbSession.commit(); dbSession.clearCache(); expectBadRequestException("Quality profile already exists: {lang=xoo, name=P1}"); - factory.checkAndCreate(dbSession, organization, name); + factory.checkAndCreateCustom(dbSession, organization, name); } @Test @@ -143,7 +144,7 @@ public class QProfileFactoryMediumTest { expectBadRequestException("quality_profiles.profile_name_cant_be_blank"); - factory.create(dbSession, organization, name, true); + factory.createBuiltIn(dbSession, organization, name, true); } @Test @@ -152,17 +153,17 @@ public class QProfileFactoryMediumTest { expectBadRequestException("quality_profiles.profile_name_cant_be_blank"); - factory.create(dbSession, organization, name, false); + factory.createBuiltIn(dbSession, organization, name, false); } @Test public void create_does_not_fail_if_already_exists() { QProfileName name = new QProfileName("xoo", "P1"); - factory.create(dbSession, organization, name, true); + factory.createBuiltIn(dbSession, organization, name, true); dbSession.commit(); dbSession.clearCache(); - assertThat(factory.create(dbSession, organization, name, true)).isNotNull(); + assertThat(factory.createBuiltIn(dbSession, organization, name, true)).isNotNull(); } private void expectBadRequestException(String message) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesMediumTest.java index ff6b5a3f1c9..55c107ed8e4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesMediumTest.java @@ -86,6 +86,7 @@ public class RegisterQualityProfilesMediumTest { assertThat(qualityProfileDao.selectAll(dbSession, organization)).hasSize(1); QualityProfileDto profile = qualityProfileDao.selectByNameAndLanguage(organization, "Basic", "xoo", dbSession); assertThat(profile).isNotNull(); + assertThat(profile.isBuiltIn()).isTrue(); // Check ActiveRules in DB ActiveRuleDao activeRuleDao = dbClient.activeRuleDao(); @@ -100,6 +101,7 @@ public class RegisterQualityProfilesMediumTest { tester.get(Platform.class).restart(); + assertThat(profile.isBuiltIn()).isTrue(); assertThat(activeRuleDao.selectByKey(dbSession, activeRuleKey)).isPresent(); // Check ActiveRules @@ -134,6 +136,7 @@ public class RegisterQualityProfilesMediumTest { assertThat(qualityProfileDao.selectAll(dbSession, organization)).hasSize(1); QualityProfileDto profile = qualityProfileDao.selectByNameAndLanguage(organization, "Basic", "xoo", dbSession); assertThat(profile).isNotNull(); + assertThat(profile.isBuiltIn()).isTrue(); // Check Default Profile verifyDefaultProfile(organization, "xoo", "Basic"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java index 28abb7f9052..9f5f84f535a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java @@ -32,12 +32,15 @@ import org.mockito.ArgumentCaptor; import org.sonar.api.resources.Language; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.loadedtemplate.LoadedTemplateDto; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.qualityprofile.ActiveRuleKey; +import org.sonar.db.qualityprofile.QualityProfileDto; import org.sonar.server.language.LanguageTesting; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.tester.UserSessionRule; @@ -59,16 +62,18 @@ public class RegisterQualityProfilesTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule - public DefinedQProfileRepositoryRule definedQProfileRepositoryRule = new DefinedQProfileRepositoryRule(); + public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule(); + @Rule + public LogTester logTester = new LogTester(); private DbClient dbClient = dbTester.getDbClient(); private DbClient mockedDbClient = mock(DbClient.class); private ActiveRuleIndexer mockedActiveRuleIndexer = mock(ActiveRuleIndexer.class); - private DummyDefinedQProfileCreation definedQProfileCreation = new DummyDefinedQProfileCreation(); + private DummyBuiltInQProfileCreation builtInQProfileCreation = new DummyBuiltInQProfileCreation(); private RegisterQualityProfiles underTest = new RegisterQualityProfiles( - definedQProfileRepositoryRule, + builtInQProfileRepositoryRule, dbClient, - definedQProfileCreation, + builtInQProfileCreation, mockedActiveRuleIndexer); @Test @@ -81,12 +86,12 @@ public class RegisterQualityProfilesTest { @Test public void no_action_in_DB_nothing_to_index_when_there_is_no_DefinedQProfile() { - RegisterQualityProfiles underTest = new RegisterQualityProfiles(definedQProfileRepositoryRule, mockedDbClient, null, mockedActiveRuleIndexer); - definedQProfileRepositoryRule.initialize(); + RegisterQualityProfiles underTest = new RegisterQualityProfiles(builtInQProfileRepositoryRule, mockedDbClient, null, mockedActiveRuleIndexer); + builtInQProfileRepositoryRule.initialize(); underTest.start(); - assertThat(definedQProfileCreation.getCallLogs()).isEmpty(); + assertThat(builtInQProfileCreation.getCallLogs()).isEmpty(); verify(mockedDbClient).openSession(false); verify(mockedActiveRuleIndexer).index(Collections.emptyList()); verifyNoMoreInteractions(mockedDbClient, mockedActiveRuleIndexer); @@ -96,46 +101,46 @@ public class RegisterQualityProfilesTest { public void start_creates_qps_for_every_organization_in_DB_when_LoadedTemplate_table_is_empty() { OrganizationDto organization1 = dbTester.organizations().insert(); OrganizationDto organization2 = dbTester.organizations().insert(); - DefinedQProfile definedQProfile = definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1"); - definedQProfileRepositoryRule.initialize(); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1"); + builtInQProfileRepositoryRule.initialize(); underTest.start(); - assertThat(definedQProfileCreation.getCallLogs()) + assertThat(builtInQProfileCreation.getCallLogs()) .containsExactly( - callLog(definedQProfile, dbTester.getDefaultOrganization()), - callLog(definedQProfile, organization1), - callLog(definedQProfile, organization2)); + callLog(builtInQProfile, dbTester.getDefaultOrganization()), + callLog(builtInQProfile, organization1), + callLog(builtInQProfile, organization2)); } @Test public void start_creates_qps_only_for_organizations_in_DB_without_loaded_template() { OrganizationDto org1 = dbTester.organizations().insert(); OrganizationDto org2 = dbTester.organizations().insert(); - 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()); + BuiltInQProfile builtInQProfile = builtInQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1"); + dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(dbTester.getDefaultOrganization().getUuid(), builtInQProfile.getLoadedTemplateType()), dbTester.getSession()); + dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(org1.getUuid(), builtInQProfile.getLoadedTemplateType()), dbTester.getSession()); dbTester.commit(); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.initialize(); underTest.start(); - assertThat(definedQProfileCreation.getCallLogs()) - .containsExactly(callLog(definedQProfile, org2)); + assertThat(builtInQProfileCreation.getCallLogs()) + .containsExactly(callLog(builtInQProfile, org2)); } @Test public void start_creates_different_qps_and_their_loaded_templates_if_several_profile_has_same_name_for_different_languages() { String name = "doh"; - DefinedQProfile definedQProfile1 = definedQProfileRepositoryRule.add(FOO_LANGUAGE, name, true); - DefinedQProfile definedQProfile2 = definedQProfileRepositoryRule.add(BAR_LANGUAGE, name, true); - definedQProfileRepositoryRule.initialize(); + BuiltInQProfile builtInQProfile1 = builtInQProfileRepositoryRule.add(FOO_LANGUAGE, name, true); + BuiltInQProfile builtInQProfile2 = builtInQProfileRepositoryRule.add(BAR_LANGUAGE, name, true); + builtInQProfileRepositoryRule.initialize(); underTest.start(); - assertThat(definedQProfileCreation.getCallLogs()) - .containsExactly(callLog(definedQProfile2, dbTester.getDefaultOrganization()), callLog(definedQProfile1, dbTester.getDefaultOrganization())); + assertThat(builtInQProfileCreation.getCallLogs()) + .containsExactly(callLog(builtInQProfile2, dbTester.getDefaultOrganization()), callLog(builtInQProfile1, dbTester.getDefaultOrganization())); } @Test @@ -143,17 +148,17 @@ public class RegisterQualityProfilesTest { dbTester.organizations().insert(); dbTester.organizations().insert(); dbTester.organizations().insert(); - definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1", false); - definedQProfileRepositoryRule.initialize(); + builtInQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1", false); + builtInQProfileRepositoryRule.initialize(); ActiveRuleChange ruleChange1 = newActiveRuleChange("1"); ActiveRuleChange ruleChange2 = newActiveRuleChange("2"); ActiveRuleChange ruleChange3 = newActiveRuleChange("3"); ActiveRuleChange ruleChange4 = newActiveRuleChange("4"); - definedQProfileCreation.addChangesPerCall(ruleChange1, ruleChange3); + builtInQProfileCreation.addChangesPerCall(ruleChange1, ruleChange3); // no change for second org - definedQProfileCreation.addChangesPerCall(); - definedQProfileCreation.addChangesPerCall(ruleChange2); - definedQProfileCreation.addChangesPerCall(ruleChange4); + builtInQProfileCreation.addChangesPerCall(); + builtInQProfileCreation.addChangesPerCall(ruleChange2); + builtInQProfileCreation.addChangesPerCall(ruleChange4); ArgumentCaptor> indexedChangesCaptor = ArgumentCaptor.forClass((Class>) (Object) List.class); doNothing().when(mockedActiveRuleIndexer).index(indexedChangesCaptor.capture()); @@ -163,20 +168,37 @@ public class RegisterQualityProfilesTest { .containsExactly(ruleChange1, ruleChange3, ruleChange2, ruleChange4); } + @Test + public void rename_custom_outdated_profiles_if_same_name_than_builtin_profile() { + OrganizationDto org1 = dbTester.organizations().insert(org -> org.setKey("org1")); + OrganizationDto org2 = dbTester.organizations().insert(org -> org.setKey("org2")); + + QualityProfileDto outdatedProfileInOrg1 = dbTester.qualityProfiles().insert(org1, p -> p.setIsBuiltIn(false).setLanguage(FOO_LANGUAGE.getKey()).setName("Sonar way")); + QualityProfileDto outdatedProfileInOrg2 = dbTester.qualityProfiles().insert(org2, p -> p.setIsBuiltIn(false).setLanguage(FOO_LANGUAGE.getKey()).setName("Sonar way")); + builtInQProfileRepositoryRule.add(FOO_LANGUAGE, "Sonar way", false); + builtInQProfileRepositoryRule.initialize(); + + underTest.start(); + + assertThat(dbTester.qualityProfiles().selectByKey(outdatedProfileInOrg1.getKey()).get().getName()).isEqualTo("Sonar way (outdated copy)"); + assertThat(dbTester.qualityProfiles().selectByKey(outdatedProfileInOrg2.getKey()).get().getName()).isEqualTo("Sonar way (outdated copy)"); + assertThat(logTester.logs(LoggerLevel.INFO)).contains("Rename Quality profiles [foo/Sonar way] to [Sonar way (outdated copy)] in 2 organizations"); + } + private static ActiveRuleChange newActiveRuleChange(String id) { return ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(id, RuleKey.of(id + "1", id + "2"))); } - private class DummyDefinedQProfileCreation implements DefinedQProfileCreation { + private class DummyBuiltInQProfileCreation implements BuiltInQProfileCreation { private List> changesPerCall; private Iterator> changesPerCallIterator; private final List callLogs = new ArrayList<>(); @Override - public void create(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes) { + public void create(DbSession session, BuiltInQProfile qualityProfile, OrganizationDto organization, List changes) { callLogs.add(callLog(qualityProfile, organization)); - // RegisterQualityProfiles relies on the fact that DefinedQProfileCreation populates table LOADED_TEMPLATE each time create is called + // RegisterQualityProfiles relies on the fact that BuiltInQProfileCreation populates table LOADED_TEMPLATE each time create is called // to not loop infinitely dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()), session); @@ -201,11 +223,11 @@ public class RegisterQualityProfilesTest { } private static final class CallLog { - private final DefinedQProfile definedQProfile; + private final BuiltInQProfile builtInQProfile; private final OrganizationDto organization; - private CallLog(DefinedQProfile definedQProfile, OrganizationDto organization) { - this.definedQProfile = definedQProfile; + private CallLog(BuiltInQProfile builtInQProfile, OrganizationDto organization) { + this.builtInQProfile = builtInQProfile; this.organization = organization; } @@ -218,25 +240,25 @@ public class RegisterQualityProfilesTest { return false; } CallLog callLog = (CallLog) o; - return definedQProfile == callLog.definedQProfile && + return builtInQProfile == callLog.builtInQProfile && organization.getUuid().equals(callLog.organization.getUuid()); } @Override public int hashCode() { - return Objects.hash(definedQProfile, organization); + return Objects.hash(builtInQProfile, organization); } @Override public String toString() { return "CallLog{" + - "qp=" + definedQProfile.getLanguage() + '-' + definedQProfile.getName() + '-' + definedQProfile.isDefault() + + "qp=" + builtInQProfile.getLanguage() + '-' + builtInQProfile.getName() + '-' + builtInQProfile.isDefault() + ", org=" + organization.getKey() + '}'; } } - private static CallLog callLog(DefinedQProfile definedQProfile, OrganizationDto organizationDto) { - return new CallLog(definedQProfile, organizationDto); + private static CallLog callLog(BuiltInQProfile builtInQProfile, OrganizationDto organizationDto) { + return new CallLog(builtInQProfile, organizationDto); } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest/index.xml b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest/index.xml index c0d3dfcafa5..2524004697a 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest/index.xml +++ b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest/index.xml @@ -22,6 +22,7 @@ kee="sonar-way" language="xoo" parent_kee="[null]" + is_built_in="[false]" is_default="[false]"/>