entry 'okhttp'
entry 'mockwebserver'
}
+ dependency 'org.json:json:20211205'
dependency 'com.tngtech.java:junit-dataprovider:1.13.1'
dependency 'info.picocli:picocli:3.6.1'
dependencySet(group: 'io.jsonwebtoken', version: '0.11.2') {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.api.notifications.Notification;
-
-public class BuiltInQPChangeNotification extends Notification {
- static final String TYPE = "built-in-quality-profiles";
-
- public BuiltInQPChangeNotification() {
- super(TYPE);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.IntStream;
-import org.sonar.api.notifications.Notification;
-
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.Integer.parseInt;
-import static java.lang.Long.parseLong;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-
-public class BuiltInQPChangeNotificationBuilder {
-
- private static final String NUMBER_OF_PROFILES = "numberOfProfiles";
- private static final String PROFILE_NAME = ".profileName";
- private static final String LANGUAGE_KEY = ".languageKey";
- private static final String LANGUAGE_NAME = ".languageName";
- private static final String NEW_RULES = ".newRules";
- private static final String UPDATED_RULES = ".updatedRules";
- private static final String REMOVED_RULES = ".removedRules";
- private static final String START_DATE = ".startDate";
- private static final String END_DATE = ".endDate";
-
- private final List<Profile> profiles = new ArrayList<>();
-
- public BuiltInQPChangeNotificationBuilder addProfile(Profile profile) {
- profiles.add(profile);
- return this;
- }
-
- public BuiltInQPChangeNotification build() {
- BuiltInQPChangeNotification notification = new BuiltInQPChangeNotification();
- notification.setFieldValue(NUMBER_OF_PROFILES, String.valueOf(profiles.size()));
- AtomicInteger count = new AtomicInteger();
- profiles.forEach(profile -> {
- int index = count.getAndIncrement();
- notification.setFieldValue(index + PROFILE_NAME, profile.getProfileName());
- notification.setFieldValue(index + LANGUAGE_KEY, profile.getLanguageKey());
- notification.setFieldValue(index + LANGUAGE_NAME, profile.getLanguageName());
- notification.setFieldValue(index + NEW_RULES, String.valueOf(profile.getNewRules()));
- notification.setFieldValue(index + UPDATED_RULES, String.valueOf(profile.getUpdatedRules()));
- notification.setFieldValue(index + REMOVED_RULES, String.valueOf(profile.getRemovedRules()));
- notification.setFieldValue(index + START_DATE, String.valueOf(profile.getStartDate()));
- notification.setFieldValue(index + END_DATE, String.valueOf(profile.getEndDate()));
- });
- return notification;
- }
-
- public static BuiltInQPChangeNotificationBuilder parse(Notification notification) {
- checkState(BuiltInQPChangeNotification.TYPE.equals(notification.getType()),
- "Expected notification of type %s but got %s", BuiltInQPChangeNotification.TYPE, notification.getType());
- BuiltInQPChangeNotificationBuilder notif = new BuiltInQPChangeNotificationBuilder();
- String numberOfProfilesText = notification.getFieldValue(NUMBER_OF_PROFILES);
- checkState(numberOfProfilesText != null, "Could not read the built-in quality profile notification");
- Integer numberOfProfiles = Integer.valueOf(numberOfProfilesText);
- IntStream.rangeClosed(0, numberOfProfiles - 1)
- .mapToObj(index -> Profile.newBuilder()
- .setProfileName(getNonNullFieldValue(notification, index + PROFILE_NAME))
- .setLanguageKey(getNonNullFieldValue(notification, index + LANGUAGE_KEY))
- .setLanguageName(getNonNullFieldValue(notification, index + LANGUAGE_NAME))
- .setNewRules(parseInt(getNonNullFieldValue(notification, index + NEW_RULES)))
- .setUpdatedRules(parseInt(getNonNullFieldValue(notification, index + UPDATED_RULES)))
- .setRemovedRules(parseInt(getNonNullFieldValue(notification, index + REMOVED_RULES)))
- .setStartDate(parseLong(getNonNullFieldValue(notification, index + START_DATE)))
- .setEndDate(parseLong(getNonNullFieldValue(notification, index + END_DATE)))
- .build())
- .forEach(notif::addProfile);
- return notif;
- }
-
- private static String getNonNullFieldValue(Notification notification, String key) {
- String value = notification.getFieldValue(key);
- return requireNonNull(value, format("Notification field '%s' is null", key));
- }
-
- public List<Profile> getProfiles() {
- return profiles;
- }
-
- public static class Profile {
- private final String profileName;
- private final String languageKey;
- private final String languageName;
- private final int newRules;
- private final int updatedRules;
- private final int removedRules;
- private final long startDate;
- private final long endDate;
-
- public Profile(Builder builder) {
- this.profileName = builder.profileName;
- this.languageKey = builder.languageKey;
- this.languageName = builder.languageName;
- this.newRules = builder.newRules;
- this.updatedRules = builder.updatedRules;
- this.removedRules = builder.removedRules;
- this.startDate = builder.startDate;
- this.endDate = builder.endDate;
- }
-
- public String getProfileName() {
- return profileName;
- }
-
- public String getLanguageKey() {
- return languageKey;
- }
-
- public String getLanguageName() {
- return languageName;
- }
-
- public int getNewRules() {
- return newRules;
- }
-
- public int getUpdatedRules() {
- return updatedRules;
- }
-
- public int getRemovedRules() {
- return removedRules;
- }
-
- public long getStartDate() {
- return startDate;
- }
-
- public long getEndDate() {
- return endDate;
- }
-
- public static Builder newBuilder() {
- return new Builder();
- }
-
- public static class Builder {
- private String profileName;
- private String languageKey;
- private String languageName;
- private int newRules;
- private int updatedRules;
- private int removedRules;
- private long startDate;
- private long endDate;
-
- private Builder() {
- }
-
- public Builder setLanguageKey(String languageKey) {
- this.languageKey = requireNonNull(languageKey, "languageKEy should not be null");
- return this;
- }
-
- public Builder setLanguageName(String languageName) {
- this.languageName = requireNonNull(languageName, "languageName should not be null");
- return this;
- }
-
- public Builder setProfileName(String profileName) {
- this.profileName = requireNonNull(profileName, "profileName should not be null");
- return this;
- }
-
- public Builder setNewRules(int newRules) {
- checkState(newRules >= 0, "newRules should not be negative");
- this.newRules = newRules;
- return this;
- }
-
- public Builder setUpdatedRules(int updatedRules) {
- checkState(updatedRules >= 0, "updatedRules should not be negative");
- this.updatedRules = updatedRules;
- return this;
- }
-
- public Builder setRemovedRules(int removedRules) {
- checkState(removedRules >= 0, "removedRules should not be negative");
- this.removedRules = removedRules;
- return this;
- }
-
- public Builder setStartDate(long startDate) {
- this.startDate = startDate;
- return this;
- }
-
- public Builder setEndDate(long endDate) {
- this.endDate = endDate;
- return this;
- }
-
- public Profile build() {
- return new Profile(this);
- }
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Collection;
-import java.util.Optional;
-import java.util.Set;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.server.notification.EmailNotificationHandler;
-import org.sonar.server.notification.NotificationDispatcherMetadata;
-import org.sonar.server.notification.email.EmailNotificationChannel;
-import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
-
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-
-public class BuiltInQPChangeNotificationHandler extends EmailNotificationHandler<BuiltInQPChangeNotification> {
- private final DbClient dbClient;
-
- public BuiltInQPChangeNotificationHandler(DbClient dbClient, EmailNotificationChannel emailNotificationChannel) {
- super(emailNotificationChannel);
- this.dbClient = dbClient;
- }
-
- @Override
- public Optional<NotificationDispatcherMetadata> getMetadata() {
- return Optional.empty();
- }
-
- @Override
- public Class<BuiltInQPChangeNotification> getNotificationClass() {
- return BuiltInQPChangeNotification.class;
- }
-
- @Override
- public Set<EmailDeliveryRequest> toEmailDeliveryRequests(Collection<BuiltInQPChangeNotification> notifications) {
- try (DbSession session = dbClient.openSession(false)) {
- return dbClient.authorizationDao()
- .selectQualityProfileAdministratorLogins(session)
- .stream()
- .flatMap(t -> notifications.stream().map(notification -> new EmailDeliveryRequest(t.getEmail(), notification)))
- .collect(toSet());
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Comparator;
-import java.util.Date;
-import javax.annotation.CheckForNull;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.platform.Server;
-import org.sonar.server.issue.notification.EmailMessage;
-import org.sonar.server.issue.notification.EmailTemplate;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.sonar.api.utils.DateUtils.formatDate;
-import static org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile;
-import static org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.parse;
-
-public class BuiltInQPChangeNotificationTemplate implements EmailTemplate {
-
- private final Server server;
-
- public BuiltInQPChangeNotificationTemplate(Server server) {
- this.server = server;
- }
-
- @Override
- @CheckForNull
- public EmailMessage format(Notification notification) {
- if (!BuiltInQPChangeNotification.TYPE.equals(notification.getType())) {
- return null;
- }
-
- BuiltInQPChangeNotificationBuilder profilesNotification = parse(notification);
- StringBuilder message = new StringBuilder("The following built-in profiles have been updated:\n\n");
- profilesNotification.getProfiles().stream()
- .sorted(Comparator.comparing(Profile::getLanguageName).thenComparing(Profile::getProfileName))
- .forEach(profile -> {
- message.append("\"")
- .append(profile.getProfileName())
- .append("\" - ")
- .append(profile.getLanguageName())
- .append(": ")
- .append(server.getPublicRootUrl()).append("/profiles/changelog?language=")
- .append(profile.getLanguageKey())
- .append("&name=")
- .append(encode(profile.getProfileName()))
- .append("&since=")
- .append(formatDate(new Date(profile.getStartDate())))
- .append("&to=")
- .append(formatDate(new Date(profile.getEndDate())))
- .append("\n");
- int newRules = profile.getNewRules();
- if (newRules > 0) {
- message.append(" ").append(newRules).append(" new rule")
- .append(plural(newRules))
- .append('\n');
- }
- int updatedRules = profile.getUpdatedRules();
- if (updatedRules > 0) {
- message.append(" ").append(updatedRules).append(" rule")
- .append(updatedRules > 1 ? "s have been updated" : " has been updated")
- .append("\n");
- }
- int removedRules = profile.getRemovedRules();
- if (removedRules > 0) {
- message.append(" ").append(removedRules).append(" rule")
- .append(plural(removedRules))
- .append(" removed\n");
- }
- message.append("\n");
- });
-
- message.append("This is a good time to review your quality profiles and update them to benefit from the latest evolutions: ");
- message.append(server.getPublicRootUrl()).append("/profiles");
-
- // And finally return the email that will be sent
- return new EmailMessage()
- .setMessageId(BuiltInQPChangeNotification.TYPE)
- .setSubject("Built-in quality profiles have been updated")
- .setPlainTextMessage(message.toString());
- }
-
- private static String plural(int count) {
- return count > 1 ? "s" : "";
- }
-
- public String encode(String text) {
- try {
- return URLEncoder.encode(text, UTF_8.name());
- } catch (UnsupportedEncodingException e) {
- throw new IllegalStateException(String.format("Cannot encode %s", text), e);
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualityprofile;
-
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-
-import static org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
-import static org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.OverriddenParam;
-
-/**
- * Represent a Quality Profile as computed from {@link BuiltInQualityProfilesDefinition} provided by installed plugins.
- */
-@Immutable
-public final class BuiltInQProfile {
- private final QProfileName qProfileName;
- private final boolean isDefault;
- private final List<ActiveRule> activeRules;
-
- private BuiltInQProfile(Builder builder) {
- this.qProfileName = new QProfileName(builder.language, builder.name);
- this.isDefault = builder.declaredDefault || builder.computedDefault;
- this.activeRules = ImmutableList.copyOf(builder.activeRules);
- }
-
- public String getName() {
- return qProfileName.getName();
- }
-
- public String getLanguage() {
- return qProfileName.getLanguage();
- }
-
- public QProfileName getQProfileName() {
- return qProfileName;
- }
-
- public boolean isDefault() {
- return isDefault;
- }
-
- public List<ActiveRule> getActiveRules() {
- return activeRules;
- }
-
- static final class ActiveRule {
- private final String ruleUuid;
- private final RuleKey ruleKey;
- private final String severity;
- private final List<OverriddenParam> params;
-
- ActiveRule(String ruleUuid, BuiltInActiveRule builtIn) {
- this(ruleUuid, RuleKey.of(builtIn.repoKey(), builtIn.ruleKey()), builtIn.overriddenSeverity(), builtIn.overriddenParams());
- }
-
- ActiveRule(String ruleUuid, RuleKey ruleKey, @Nullable String severity, List<OverriddenParam> params) {
- this.ruleUuid = ruleUuid;
- this.ruleKey = ruleKey;
- this.severity = severity;
- this.params = params;
- }
-
- public String getRuleUuid() {
- return ruleUuid;
- }
-
- public RuleKey getRuleKey() {
- return ruleKey;
- }
-
- @CheckForNull
- public String getSeverity() {
- return severity;
- }
-
- public List<OverriddenParam> getParams() {
- return params;
- }
- }
-
- static final class Builder {
- private String language;
- private String name;
- private boolean declaredDefault;
- private boolean computedDefault;
- private final List<ActiveRule> activeRules = new ArrayList<>();
-
- public Builder setLanguage(String language) {
- this.language = language;
- return this;
- }
-
- Builder setName(String name) {
- this.name = name;
- return this;
- }
-
- String getName() {
- return name;
- }
-
- Builder setDeclaredDefault(boolean declaredDefault) {
- this.declaredDefault = declaredDefault;
- return this;
- }
-
- boolean isDeclaredDefault() {
- return declaredDefault;
- }
-
- Builder setComputedDefault(boolean flag) {
- computedDefault = flag;
- return this;
- }
-
- Builder addRule(ActiveRule activeRule) {
- this.activeRules.add(activeRule);
- return this;
- }
-
- BuiltInQProfile build() {
- return new BuiltInQProfile(this);
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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;
-
-public interface BuiltInQProfileInsert {
- /**
- * Persist a new built-in profile
- * Db sessions are committed and Elasticsearch indices are updated
- */
- void create(DbSession batchSession, BuiltInQProfile builtInQProfile);
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-import org.sonar.api.server.rule.RuleParamType;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.DefaultQProfileDto;
-import org.sonar.db.qualityprofile.OrgQProfileDto;
-import org.sonar.db.qualityprofile.RulesProfileDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.rule.ServerRuleFinder;
-import org.sonar.server.util.TypeValidations;
-
-import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Collections.emptySet;
-import static java.util.Objects.requireNonNull;
-
-public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
- private final DbClient dbClient;
- private final ServerRuleFinder ruleFinder;
- private final System2 system2;
- private final UuidFactory uuidFactory;
- private final TypeValidations typeValidations;
- private final ActiveRuleIndexer activeRuleIndexer;
- private RuleRepository ruleRepository;
-
- public BuiltInQProfileInsertImpl(DbClient dbClient, ServerRuleFinder ruleFinder, System2 system2, UuidFactory uuidFactory,
- TypeValidations typeValidations, ActiveRuleIndexer activeRuleIndexer) {
- this.dbClient = dbClient;
- this.ruleFinder = ruleFinder;
- this.system2 = system2;
- this.uuidFactory = uuidFactory;
- this.typeValidations = typeValidations;
- this.activeRuleIndexer = activeRuleIndexer;
- }
-
- @Override
- public void create(DbSession batchDbSession, BuiltInQProfile builtInQProfile) {
- initRuleRepository(batchDbSession);
-
- Date now = new Date(system2.now());
- RulesProfileDto ruleProfile = insertRulesProfile(batchDbSession, builtInQProfile, now);
-
- List<ActiveRuleChange> changes = builtInQProfile.getActiveRules().stream()
- .map(activeRule -> insertActiveRule(batchDbSession, ruleProfile, activeRule, now.getTime()))
- .collect(MoreCollectors.toList());
-
- changes.forEach(change -> dbClient.qProfileChangeDao().insert(batchDbSession, change.toDto(null)));
-
- createDefaultAndOrgQProfiles(batchDbSession, builtInQProfile, ruleProfile);
-
- activeRuleIndexer.commitAndIndex(batchDbSession, changes);
- }
-
-
- private void createDefaultAndOrgQProfiles(DbSession batchDbSession, BuiltInQProfile builtIn, RulesProfileDto rulesProfileDto) {
- Optional<String> qProfileUuid = dbClient.defaultQProfileDao().selectDefaultQProfileUuid(batchDbSession, builtIn.getLanguage());
-
- OrgQProfileDto dto = new OrgQProfileDto()
- .setRulesProfileUuid(rulesProfileDto.getUuid())
- .setUuid(uuidFactory.create());
-
- if (builtIn.isDefault() && qProfileUuid.isEmpty()) {
- DefaultQProfileDto defaultQProfileDto = new DefaultQProfileDto()
- .setQProfileUuid(dto.getUuid())
- .setLanguage(builtIn.getLanguage());
- dbClient.defaultQProfileDao().insert(batchDbSession, defaultQProfileDto);
- }
-
- dbClient.qualityProfileDao().insert(batchDbSession, dto);
- }
-
- private void initRuleRepository(DbSession dbSession) {
- if (ruleRepository == null) {
- ruleRepository = new RuleRepository(dbClient, dbSession, ruleFinder);
- }
- }
-
- private RulesProfileDto insertRulesProfile(DbSession dbSession, BuiltInQProfile builtIn, Date now) {
- RulesProfileDto dto = new RulesProfileDto()
- .setUuid(uuidFactory.create())
- .setName(builtIn.getName())
- .setLanguage(builtIn.getLanguage())
- .setIsBuiltIn(true)
- .setRulesUpdatedAtAsDate(now);
- dbClient.qualityProfileDao().insert(dbSession, dto);
- return dto;
- }
-
- private ActiveRuleChange insertActiveRule(DbSession batchDbSession, RulesProfileDto rulesProfileDto, BuiltInQProfile.ActiveRule activeRule, long now) {
- RuleKey ruleKey = activeRule.getRuleKey();
- RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey)
- .orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey));
-
- ActiveRuleDto dto = new ActiveRuleDto();
- dto.setProfileUuid(rulesProfileDto.getUuid());
- dto.setRuleUuid(ruleDefinitionDto.getUuid());
- dto.setKey(ActiveRuleKey.of(rulesProfileDto, ruleDefinitionDto.getKey()));
- dto.setSeverity(firstNonNull(activeRule.getSeverity(), ruleDefinitionDto.getSeverityString()));
- dto.setUpdatedAt(now);
- dto.setCreatedAt(now);
- dbClient.activeRuleDao().insert(batchDbSession, dto);
-
- List<ActiveRuleParamDto> paramDtos = insertActiveRuleParams(batchDbSession, activeRule, dto);
-
- ActiveRuleChange change = new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, dto, ruleDefinitionDto);
- change.setSeverity(dto.getSeverityString());
- paramDtos.forEach(paramDto -> change.setParameter(paramDto.getKey(), paramDto.getValue()));
- return change;
- }
-
- private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, BuiltInQProfile.ActiveRule activeRule, ActiveRuleDto activeRuleDto) {
- Map<String, String> valuesByParamKey = activeRule.getParams().stream()
- .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
- List<ActiveRuleParamDto> rules = ruleRepository.getRuleParams(activeRule.getRuleKey()).stream()
- .map(param -> createParamDto(param, Optional.ofNullable(valuesByParamKey.get(param.getName())).orElse(param.getDefaultValue())))
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
-
- rules.forEach(paramDto -> dbClient.activeRuleDao().insertParam(session, activeRuleDto, paramDto));
- return rules;
- }
-
- @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;
- }
-
- private String validateParam(RuleParamDto ruleParam, String value) {
- RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType());
- if (ruleParamType.multiple()) {
- List<String> values = newArrayList(Splitter.on(",").split(value));
- typeValidations.validate(values, ruleParamType.type(), ruleParamType.values());
- } else {
- typeValidations.validate(value, ruleParamType.type(), ruleParamType.values());
- }
- return value;
- }
-
- private static class RuleRepository {
- private final Map<RuleKey, Set<RuleParamDto>> params;
- private final ServerRuleFinder ruleFinder;
-
- private RuleRepository(DbClient dbClient, DbSession session, ServerRuleFinder ruleFinder) {
- this.ruleFinder = ruleFinder;
- this.params = new HashMap<>();
-
- for (RuleParamDto ruleParam : dbClient.ruleDao().selectAllRuleParams(session)) {
- Optional<RuleKey> ruleKey = ruleFinder.findDtoByUuid(ruleParam.getRuleUuid())
- .map(r -> RuleKey.of(r.getRepositoryKey(), r.getRuleKey()));
-
- if (ruleKey.isPresent()) {
- params.computeIfAbsent(ruleKey.get(), r -> new HashSet<>()).add(ruleParam);
- }
- }
- }
-
- private Optional<RuleDefinitionDto> getDefinition(RuleKey ruleKey) {
- return ruleFinder.findDtoByKey(requireNonNull(ruleKey, "RuleKey can't be null"));
- }
-
- private Set<RuleParamDto> getRuleParams(RuleKey ruleKey) {
- return params.getOrDefault(requireNonNull(ruleKey, "RuleKey can't be null"), emptySet());
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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;
-
-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 list
- *
- * @throws IllegalStateException if {@link #initialize()} has not been called
- */
- List<BuiltInQProfile> get();
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Multimap;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.api.utils.log.Profiler;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.rule.DeprecatedRuleKeyDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.server.rule.ServerRuleFinder;
-
-import static com.google.common.base.Preconditions.checkState;
-
-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 DbClient dbClient;
- private final ServerRuleFinder ruleFinder;
- private final Languages languages;
- private final List<BuiltInQualityProfilesDefinition> definitions;
- private List<BuiltInQProfile> qProfiles;
-
- /**
- * Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all
- */
- public BuiltInQProfileRepositoryImpl(DbClient dbClient, ServerRuleFinder ruleFinder, Languages languages) {
- this(dbClient, ruleFinder, languages, new BuiltInQualityProfilesDefinition[0]);
- }
-
- public BuiltInQProfileRepositoryImpl(DbClient dbClient, ServerRuleFinder ruleFinder, Languages languages, BuiltInQualityProfilesDefinition... definitions) {
- this.dbClient = dbClient;
- this.ruleFinder = ruleFinder;
- this.languages = languages;
- this.definitions = ImmutableList.copyOf(definitions);
- }
-
- @Override
- public void initialize() {
- checkState(qProfiles == null, "initialize must be called only once");
-
- Profiler profiler = Profiler.create(LOGGER).startInfo("Load quality profiles");
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- for (BuiltInQualityProfilesDefinition definition : definitions) {
- definition.define(context);
- }
- Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage = validateAndClean(context);
- this.qProfiles = toFlatList(rulesProfilesByLanguage);
- ensureAllLanguagesHaveAtLeastOneBuiltInQP();
- profiler.stopDebug();
- }
-
- @Override
- public List<BuiltInQProfile> get() {
- checkState(qProfiles != null, "initialize must be called first");
-
- return qProfiles;
- }
-
- private void ensureAllLanguagesHaveAtLeastOneBuiltInQP() {
- Set<String> languagesWithBuiltInQProfiles = qProfiles.stream().map(BuiltInQProfile::getLanguage).collect(Collectors.toSet());
- Set<String> languagesWithoutBuiltInQProfiles = Arrays.stream(languages.all())
- .map(Language::getKey)
- .filter(key -> !languagesWithBuiltInQProfiles.contains(key))
- .collect(Collectors.toSet());
-
- checkState(languagesWithoutBuiltInQProfiles.isEmpty(), "The following languages have no built-in quality profiles: %s",
- String.join("", languagesWithoutBuiltInQProfiles));
- }
-
- private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) {
- Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = context.profilesByLanguageAndName();
- profilesByLanguageAndName.entrySet()
- .removeIf(entry -> {
- String language = entry.getKey();
- if (languages.get(language) == null) {
- LOGGER.info("Language {} is not installed, related quality profiles are ignored", language);
- return true;
- }
- return false;
- });
-
- return profilesByLanguageAndName;
- }
-
- private List<BuiltInQProfile> toFlatList(Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage) {
- if (rulesProfilesByLanguage.isEmpty()) {
- return Collections.emptyList();
- }
- Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = loadRuleDefinitionsByRuleKey();
- Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = rulesProfilesByLanguage
- .entrySet()
- .stream()
- .collect(MoreCollectors.uniqueIndex(
- Map.Entry::getKey,
- rulesProfilesByLanguageAndName -> toQualityProfileBuilders(rulesProfilesByLanguageAndName, rulesByRuleKey)));
- return buildersByLanguage
- .entrySet()
- .stream()
- .filter(BuiltInQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
- .map(entry -> toQualityProfiles(entry.getValue()))
- .flatMap(Collection::stream)
- .collect(MoreCollectors.toList());
- }
-
- private Map<RuleKey, RuleDefinitionDto> loadRuleDefinitionsByRuleKey() {
- try (DbSession dbSession = dbClient.openSession(false)) {
- Collection<RuleDefinitionDto> ruleDefinitions = ruleFinder.findAll();
- Multimap<String, DeprecatedRuleKeyDto> deprecatedRuleKeysByRuleId = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
- .collect(MoreCollectors.index(DeprecatedRuleKeyDto::getRuleUuid));
- Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = new HashMap<>();
- for (RuleDefinitionDto ruleDefinition : ruleDefinitions) {
- rulesByRuleKey.put(ruleDefinition.getKey(), ruleDefinition);
- deprecatedRuleKeysByRuleId.get(ruleDefinition.getUuid()).forEach(t -> rulesByRuleKey.put(RuleKey.of(t.getOldRepositoryKey(), t.getOldRuleKey()), ruleDefinition));
- }
- return rulesByRuleKey;
- }
- }
-
- /**
- * Creates {@link BuiltInQProfile.Builder} for each unique quality profile name for a given language.
- * Builders will have the following properties populated:
- * <ul>
- * <li>{@link BuiltInQProfile.Builder#language language}: key of the method's parameter</li>
- * <li>{@link BuiltInQProfile.Builder#name name}: {@link RulesProfile#getName()}</li>
- * <li>{@link BuiltInQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile
- * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li>
- * <li>{@link BuiltInQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all
- * RulesProfile with a given name</li>
- * </ul>
- */
- private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguageAndName,
- Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
- String language = rulesProfilesByLanguageAndName.getKey();
- // use a LinkedHashMap to keep order of insertion of RulesProfiles
- Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
- for (BuiltInQualityProfile builtInProfile : rulesProfilesByLanguageAndName.getValue().values()) {
- qualityProfileBuildersByName.compute(
- builtInProfile.name(),
- (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, builtInProfile, rulesByRuleKey));
- }
- 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<String, List<BuiltInQProfile.Builder>> entry) {
- Set<String> 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, BuiltInQualityProfile builtInProfile,
- Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
- BuiltInQProfile.Builder builder = createOrReuseBuilder(existingBuilder, language, builtInProfile);
- builder.setDeclaredDefault(builtInProfile.isDefault());
- builtInProfile.rules().forEach(builtInActiveRule -> {
- RuleKey ruleKey = RuleKey.of(builtInActiveRule.repoKey(), builtInActiveRule.ruleKey());
- RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey);
- checkState(ruleDefinition != null, "Rule with key '%s' not found", ruleKey);
- builder.addRule(new BuiltInQProfile.ActiveRule(ruleDefinition.getUuid(), ruleDefinition.getKey(),
- builtInActiveRule.overriddenSeverity(), builtInActiveRule.overriddenParams()));
- });
- return builder;
- }
-
- private static BuiltInQProfile.Builder createOrReuseBuilder(@Nullable BuiltInQProfile.Builder existingBuilder, String language, BuiltInQualityProfile builtInProfile) {
- if (existingBuilder == null) {
- return new BuiltInQProfile.Builder()
- .setLanguage(language)
- .setName(builtInProfile.name());
- }
- return existingBuilder;
- }
-
- private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) {
- if (builders.stream().noneMatch(BuiltInQProfile.Builder::isDeclaredDefault)) {
- Optional<BuiltInQProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst();
- if (sonarWayProfile.isPresent()) {
- sonarWayProfile.get().setComputedDefault(true);
- } else {
- builders.iterator().next().setComputedDefault(true);
- }
- }
- return builders.stream()
- .map(BuiltInQProfile.Builder::build)
- .collect(MoreCollectors.toList(builders.size()));
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.qualityprofile.RulesProfileDto;
-
-public interface BuiltInQProfileUpdate {
- /**
- * Persist an existing built-in profile.
- * Db session is committed and Elasticsearch indices are updated.
- */
- List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInQProfile, RulesProfileDto ruleProfile);
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Stream;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.RulesProfileDto;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-
-public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate {
-
- private final DbClient dbClient;
- private final RuleActivator ruleActivator;
- private final ActiveRuleIndexer activeRuleIndexer;
- private final QualityProfileChangeEventService qualityProfileChangeEventService;
-
- public BuiltInQProfileUpdateImpl(DbClient dbClient, RuleActivator ruleActivator, ActiveRuleIndexer activeRuleIndexer,
- QualityProfileChangeEventService qualityProfileChangeEventService) {
- this.dbClient = dbClient;
- this.ruleActivator = ruleActivator;
- this.activeRuleIndexer = activeRuleIndexer;
- this.qualityProfileChangeEventService = qualityProfileChangeEventService;
- }
-
- public List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInDefinition, RulesProfileDto initialRuleProfile) {
- // Keep reference to all the activated rules before update
- Set<String> deactivatedRuleUuids = dbClient.activeRuleDao().selectByRuleProfile(dbSession, initialRuleProfile)
- .stream()
- .map(ActiveRuleDto::getRuleUuid)
- .collect(MoreCollectors.toHashSet());
-
- // all rules, including those which are removed from built-in profile
- Set<String> ruleUuids = Stream.concat(
- deactivatedRuleUuids.stream(),
- builtInDefinition.getActiveRules().stream().map(BuiltInQProfile.ActiveRule::getRuleUuid))
- .collect(toSet());
-
- Collection<RuleActivation> activations = new ArrayList<>();
- for (BuiltInQProfile.ActiveRule ar : builtInDefinition.getActiveRules()) {
- RuleActivation activation = convert(ar);
- activations.add(activation);
- deactivatedRuleUuids.remove(activation.getRuleUuid());
- }
-
- RuleActivationContext context = ruleActivator.createContextForBuiltInProfile(dbSession, initialRuleProfile, ruleUuids);
- List<ActiveRuleChange> changes = new ArrayList<>();
-
- changes.addAll(ruleActivator.activate(dbSession, activations, context));
-
- // these rules are no longer part of the built-in profile
- deactivatedRuleUuids.forEach(ruleUuid -> changes.addAll(ruleActivator.deactivate(dbSession, context, ruleUuid, false)));
-
- if (!changes.isEmpty()) {
- qualityProfileChangeEventService.distributeRuleChangeEvent(context.getProfiles(), changes, initialRuleProfile.getLanguage());
- }
-
- activeRuleIndexer.commitAndIndex(dbSession, changes);
- return changes;
- }
-
- private static RuleActivation convert(BuiltInQProfile.ActiveRule ar) {
- Map<String, String> params = ar.getParams().stream()
- .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
- return RuleActivation.create(ar.getRuleUuid(), ar.getSeverity(), params);
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Multimap;
-import java.util.Collection;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.server.notification.NotificationManager;
-import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile;
-
-import static org.sonar.core.config.CorePropertyDefinitions.DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED;
-
-public class BuiltInQualityProfilesUpdateListener {
-
- private final NotificationManager notificationManager;
- private final Languages languages;
- private final Configuration config;
-
- public BuiltInQualityProfilesUpdateListener(NotificationManager notificationManager, Languages languages, Configuration config) {
- this.notificationManager = notificationManager;
- this.languages = languages;
- this.config = config;
- }
-
- void onChange(Multimap<QProfileName, ActiveRuleChange> changedProfiles, long startDate, long endDate) {
- if (config.getBoolean(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES).orElse(false)) {
- return;
- }
-
- BuiltInQPChangeNotificationBuilder builder = new BuiltInQPChangeNotificationBuilder();
- changedProfiles.keySet().stream()
- .map(changedProfile -> {
- String profileName = changedProfile.getName();
- Language language = languages.get(changedProfile.getLanguage());
- Collection<ActiveRuleChange> activeRuleChanges = changedProfiles.get(changedProfile);
- int newRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(ACTIVATED::equals).count();
- int updatedRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(UPDATED::equals).count();
- int removedRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(DEACTIVATED::equals).count();
- return Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(language.getKey())
- .setLanguageName(language.getName())
- .setNewRules(newRules)
- .setUpdatedRules(updatedRules)
- .setRemovedRules(removedRules)
- .setStartDate(startDate)
- .setEndDate(endDate)
- .build();
- })
- .forEach(builder::addProfile);
-
- notificationManager.scheduleForSending(builder.build());
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Collection;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-
-@FunctionalInterface
-public interface DescendantProfilesSupplier {
-
- Result get(Collection<QProfileDto> profiles, Collection<String> ruleUuids);
-
- final class Result {
- private final Collection<QProfileDto> profiles;
- private final Collection<ActiveRuleDto> activeRules;
- private final Collection<ActiveRuleParamDto> activeRuleParams;
-
- public Result(Collection<QProfileDto> profiles, Collection<ActiveRuleDto> activeRules, Collection<ActiveRuleParamDto> activeRuleParams) {
- this.profiles = profiles;
- this.activeRules = activeRules;
- this.activeRuleParams = activeRuleParams;
- }
-
- public Collection<QProfileDto> getProfiles() {
- return profiles;
- }
-
- public Collection<ActiveRuleDto> getActiveRules() {
- return activeRules;
- }
-
- public Collection<ActiveRuleParamDto> getActiveRuleParams() {
- return activeRuleParams;
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.api.server.ServerSide;
-import org.sonar.core.util.RuleActivationListener;
-import org.sonar.core.util.RuleSetChangeEvent;
-import org.sonar.process.cluster.hz.HazelcastMember;
-
-@ServerSide
-public class DistributedRuleActivatorEventsDistributor implements RuleActivatorEventsDistributor {
-
- private HazelcastMember hazelcastMember;
-
- public DistributedRuleActivatorEventsDistributor(HazelcastMember hazelcastMember) {
- this.hazelcastMember = hazelcastMember;
- }
-
- @Override
- public void subscribe(RuleActivationListener listener) {
- hazelcastMember.subscribeRuleActivationTopic(listener);
- }
-
- @Override
- public void pushEvent(RuleSetChangeEvent event) {
- hazelcastMember.publishEvent(event);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 javax.annotation.Nullable;
-
-public class QProfileName {
- private final String lang;
- private final String name;
-
- public QProfileName(String lang, String name) {
- this.lang = lang;
- this.name = name;
- }
-
- public String getLanguage() {
- return lang;
- }
-
- public String getName() {
- return name;
- }
-
- public static QProfileName createFor(String lang, String name){
- return new QProfileName(lang, name);
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- QProfileName that = (QProfileName) o;
- if (!lang.equals(that.lang)) {
- return false;
- }
- return name.equals(that.name);
- }
-
- @Override
- public int hashCode() {
- int result = lang.hashCode();
- result = 31 * result + name.hashCode();
- return result;
- }
-
- @Override
- public String toString() {
- return String.format("%s/%s", lang, name);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Collection;
-import java.util.List;
-import java.util.Optional;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-
-public interface QualityProfileChangeEventService {
-
- void publishRuleActivationToSonarLintClients(ProjectDto project, Optional<QProfileDto> activatedProfile, Optional<QProfileDto> deactivatedProfile);
-
- void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language);
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.jetbrains.annotations.NotNull;
-import org.sonar.api.server.ServerSide;
-import org.sonar.core.util.ParamChange;
-import org.sonar.core.util.RuleChange;
-import org.sonar.core.util.RuleSetChangeEvent;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.ProjectQprofileAssociationDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.server.rule.index.RuleIndex;
-import org.sonar.server.rule.index.RuleQuery;
-
-@ServerSide
-public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService {
-
- private final DbClient dbClient;
- private final RuleIndex ruleIndex;
- private final RuleActivatorEventsDistributor eventsDistributor;
-
- public QualityProfileChangeEventServiceImpl(DbClient dbClient, RuleIndex ruleIndex, RuleActivatorEventsDistributor eventsDistributor) {
- this.dbClient = dbClient;
- this.ruleIndex = ruleIndex;
- this.eventsDistributor = eventsDistributor;
- }
-
- @Override
- public void publishRuleActivationToSonarLintClients(ProjectDto project, Optional<QProfileDto> activatedProfile, Optional<QProfileDto> deactivatedProfile) {
- List<RuleChange> activatedRules = new ArrayList<>();
- List<RuleChange> deactivatedRules = new ArrayList<>();
-
- try (DbSession dbSession = dbClient.openSession(false)) {
-
- if (activatedProfile.isPresent()) {
- RuleQuery query = new RuleQuery().setQProfile(activatedProfile.get()).setActivation(true).setIncludeExternal(true);
- // .setLanguages() ?
- Iterator<String> searchIdResult = ruleIndex.searchAll(query);
- List<String> uuids = new ArrayList<>();
- while (searchIdResult.hasNext()) {
- uuids.add(searchIdResult.next());
- }
-
- List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, uuids);
- Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, uuids)
- .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
-
- for (RuleDto ruleDto : ruleDtos) {
- RuleChange ruleChange = toRuleChange(ruleDto, paramsByRuleUuid);
- activatedRules.add(ruleChange);
- }
- }
-
- if (deactivatedProfile.isPresent()) {
- RuleQuery query = new RuleQuery().setQProfile(deactivatedProfile.get()).setActivation(true).setIncludeExternal(true);
- // .setLanguages() ?
- Iterator<String> searchIdResult = ruleIndex.searchAll(query);
- List<String> uuids = new ArrayList<>();
- while (searchIdResult.hasNext()) {
- uuids.add(searchIdResult.next());
- }
-
- List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, uuids);
- Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, uuids)
- .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
-
- for (RuleDto ruleDto : ruleDtos) {
- RuleChange ruleChange = toRuleChange(ruleDto, paramsByRuleUuid);
- deactivatedRules.add(ruleChange);
- }
- }
-
- }
-
- RuleSetChangeEvent event = new RuleSetChangeEvent(new String[]{project.getKey()}, activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new RuleChange[0]));
- eventsDistributor.pushEvent(event);
- }
-
- @NotNull
- private RuleChange toRuleChange(RuleDto ruleDto, Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid) {
- RuleChange ruleChange = new RuleChange();
- ruleChange.setKey(ruleDto.getRuleKey());
- ruleChange.setLanguage(ruleDto.getLanguage());
- ruleChange.setSeverity(ruleDto.getSeverityString());
-
- List<ParamChange> paramChanges = new ArrayList<>();
- List<ActiveRuleParamDto> activeRuleParamDtos = paramsByRuleUuid.getOrDefault(ruleDto.getUuid(), new ArrayList<>());
- for (ActiveRuleParamDto activeRuleParam : activeRuleParamDtos) {
- paramChanges.add(new ParamChange(activeRuleParam.getKey(), activeRuleParam.getValue()));
- }
- ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
-
- String templateUuid = ruleDto.getTemplateUuid();
- if (templateUuid != null && !"".equals(templateUuid)) {
- try (DbSession dbSession = dbClient.openSession(false)) {
- RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
- .orElseThrow(() -> new IllegalStateException(String.format("unknow Template Rule '%s'", templateUuid)));
- ruleChange.setTemplateKey(templateRule.getRuleKey());
- }
- }
-
- return ruleChange;
- }
-
- public void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language) {
- if (activeRuleChanges.isEmpty()) {
- return;
- }
-
- Set<RuleChange> activatedRules = new HashSet<>();
- Set<RuleChange> deactivatedRules = new HashSet<>();
-
- for (ActiveRuleChange arc : activeRuleChanges) {
-
- RuleChange ruleChange = new RuleChange();
- ruleChange.setKey(arc.getActiveRule().getRuleKey().rule());
- ruleChange.setSeverity(arc.getSeverity());
- ruleChange.setLanguage(language);
-
- Optional<String> templateKey = templateKey(arc);
- templateKey.ifPresent(ruleChange::setTemplateKey);
-
- // params
- List<ParamChange> paramChanges = new ArrayList<>();
- for (Map.Entry<String, String> entry : arc.getParameters().entrySet()) {
- paramChanges.add(new ParamChange(entry.getKey(), entry.getValue()));
- }
- ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
-
- switch (arc.getType()) {
- case ACTIVATED:
- case UPDATED:
- activatedRules.add(ruleChange);
- break;
- case DEACTIVATED:
- deactivatedRules.add(ruleChange);
- break;
- }
- }
-
- Set<String> projectKeys = getProjectKeys(profiles);
-
- RuleSetChangeEvent event = new RuleSetChangeEvent(projectKeys.toArray(new String[0]), activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new RuleChange[0]));
- eventsDistributor.pushEvent(event);
-
- }
-
- private Optional<String> templateKey(ActiveRuleChange arc) {
- try (DbSession dbSession = dbClient.openSession(false)) {
- String ruleUuid = arc.getRuleUuid();
- RuleDto rule = dbClient.ruleDao().selectByUuid(ruleUuid, dbSession).orElseThrow(() -> new IllegalStateException("unknow rule"));
- String templateUuid = rule.getTemplateUuid();
- if (templateUuid != null && !"".equals(templateUuid)) {
- RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
- .orElseThrow(() -> new IllegalStateException(String.format("unknow Template Rule '%s'", templateUuid)));
- return Optional.of(templateRule.getRuleKey());
- }
- }
- return Optional.empty();
- }
-
- private Set<String> getProjectKeys(Collection<QProfileDto> profiles) {
- Set<String> projectKeys = new HashSet<>();
- try (DbSession dbSession = dbClient.openSession(false)) {
- for (QProfileDto profileDto : profiles) {
- List<ProjectQprofileAssociationDto> associationDtos = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profileDto, null);
- for (ProjectQprofileAssociationDto associationDto : associationDtos) {
- projectKeys.add(associationDto.getProjectKey());
- }
- }
- return projectKeys;
- }
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualityprofile;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.qualityprofile.RulesProfileDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleParamDto;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.Objects.requireNonNull;
-import static org.sonar.core.util.stream.MoreCollectors.index;
-import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-import static org.sonar.server.exceptions.BadRequestException.checkRequest;
-
-/**
- * Cache of the data required to activate/deactivate
- * multiple rules on a Quality profile, including
- * the rule definitions, the rule parameters, the tree
- * of profiles hierarchy and its related active rules.
- */
-class RuleActivationContext {
-
- private final long date;
-
- // The profile that is initially targeted by the operation
- private final RulesProfileDto baseRulesProfile;
-
- private final Map<String, QProfileDto> profilesByUuid = new HashMap<>();
- private final ListMultimap<String, QProfileDto> profilesByParentUuid = ArrayListMultimap.create();
-
- // The rules/active rules involved in the group of activations/de-activations
- private final Map<String, RuleWrapper> rulesByUuid = new HashMap<>();
- private final Map<ActiveRuleKey, ActiveRuleWrapper> activeRulesByKey = new HashMap<>();
-
- // Cursors used to move in the rules and in the tree of profiles.
-
- private RulesProfileDto currentRulesProfile;
- // Cardinality is zero-to-many when cursor is on a built-in rules profile,
- // otherwise it's always one, and only one (cursor on descendants or on non-built-in base profile).
- private Collection<QProfileDto> currentProfiles;
- private RuleWrapper currentRule;
- private ActiveRuleWrapper currentActiveRule;
- private ActiveRuleWrapper currentParentActiveRule;
-
- private boolean descendantsLoaded = false;
- private final DescendantProfilesSupplier descendantProfilesSupplier;
-
- private RuleActivationContext(Builder builder) {
- this.date = builder.date;
- this.descendantProfilesSupplier = builder.descendantProfilesSupplier;
-
- ListMultimap<String, RuleParamDto> paramsByRuleId = builder.ruleParams.stream().collect(index(RuleParamDto::getRuleUuid));
- for (RuleDefinitionDto rule : builder.rules) {
- RuleWrapper wrapper = new RuleWrapper(rule, paramsByRuleId.get(rule.getUuid()));
- rulesByUuid.put(rule.getUuid(), wrapper);
- }
-
- this.baseRulesProfile = builder.baseRulesProfile;
- register(builder.profiles);
- register(builder.activeRules, builder.activeRuleParams);
- }
-
- private void register(Collection<QProfileDto> profiles) {
- for (QProfileDto profile : profiles) {
- profilesByUuid.put(profile.getKee(), profile);
- if (profile.getParentKee() != null) {
- profilesByParentUuid.put(profile.getParentKee(), profile);
- }
- }
- }
-
- private void register(Collection<ActiveRuleDto> activeRules, Collection<ActiveRuleParamDto> activeRuleParams) {
- ListMultimap<String, ActiveRuleParamDto> paramsByActiveRuleUuid = activeRuleParams.stream().collect(index(ActiveRuleParamDto::getActiveRuleUuid));
- for (ActiveRuleDto activeRule : activeRules) {
- ActiveRuleWrapper wrapper = new ActiveRuleWrapper(activeRule, paramsByActiveRuleUuid.get(activeRule.getUuid()));
- this.activeRulesByKey.put(activeRule.getKey(), wrapper);
- }
- }
-
- long getDate() {
- return date;
- }
-
- /**
- * The rule currently selected.
- */
- RuleWrapper getRule() {
- checkState(currentRule != null, "Rule has not been set yet");
- return currentRule;
- }
-
- @CheckForNull
- String getRequestedParamValue(RuleActivation request, String key) {
- if (currentRule.rule.isCustomRule()) {
- return null;
- }
- return request.getParameter(key);
- }
-
- boolean hasRequestedParamValue(RuleActivation request, String key) {
- return request.hasParameter(key);
- }
-
- /**
- * The rules profile being selected.
- */
- RulesProfileDto getRulesProfile() {
- checkState(currentRulesProfile != null, "Rule profile has not been set yet");
- return currentRulesProfile;
- }
-
- /**
- * The active rule related to the selected profile and rule.
- * @return null if the selected rule is not activated on the selected profile.
- * @see #getRulesProfile()
- * @see #getRule()
- */
- @CheckForNull
- ActiveRuleWrapper getActiveRule() {
- return currentActiveRule;
- }
-
- /**
- * The active rule related to the rule and the parent of the selected profile.
- * @return null if the selected rule is not activated on the parent profile.
- * @see #getRule()
- */
- @CheckForNull
- ActiveRuleWrapper getParentActiveRule() {
- return currentParentActiveRule;
- }
-
- /**
- * Whether the profile cursor is on the base profile or not.
- */
- boolean isCascading() {
- return currentRulesProfile != null && !currentRulesProfile.getUuid().equals(baseRulesProfile.getUuid());
- }
-
- /**
- * The profiles being selected. Can be zero or many if {@link #getRulesProfile()} is built-in.
- * Else the collection always contains a single profile.
- */
- Collection<QProfileDto> getProfiles() {
- checkState(currentProfiles != null, "Profiles have not been set yet");
- return currentProfiles;
- }
-
- /**
- * The children of {@link #getProfiles()}
- */
- Collection<QProfileDto> getChildProfiles() {
- loadDescendants();
- return getProfiles().stream()
- .flatMap(p -> profilesByParentUuid.get(p.getKee()).stream())
- .collect(Collectors.toList());
- }
-
- private void loadDescendants() {
- if (descendantsLoaded) {
- return;
- }
- Collection<QProfileDto> baseProfiles = profilesByUuid.values().stream()
- .filter(p -> p.getRulesProfileUuid().equals(baseRulesProfile.getUuid()))
- .collect(toArrayList(profilesByUuid.size()));
- DescendantProfilesSupplier.Result result = descendantProfilesSupplier.get(baseProfiles, rulesByUuid.keySet());
- register(result.getProfiles());
- register(result.getActiveRules(), result.getActiveRuleParams());
- descendantsLoaded = true;
- }
-
- /**
- * Move the cursor to the given rule and back to the base profile.
- */
- public void reset(String ruleUuid) {
- doSwitch(this.baseRulesProfile, ruleUuid);
- }
-
- /**
- * Moves cursor to a child profile
- */
- void selectChild(QProfileDto to) {
- checkState(!to.isBuiltIn());
- QProfileDto qp = requireNonNull(this.profilesByUuid.get(to.getKee()), () -> "No profile with uuid " + to.getKee());
-
- RulesProfileDto ruleProfile = RulesProfileDto.from(qp);
- doSwitch(ruleProfile, getRule().get().getUuid());
- }
-
- private void doSwitch(RulesProfileDto ruleProfile, String ruleUuid) {
- this.currentRule = rulesByUuid.get(ruleUuid);
- checkRequest(this.currentRule != null, "Rule with UUID %s not found", ruleUuid);
- RuleKey ruleKey = currentRule.get().getKey();
-
- this.currentRulesProfile = ruleProfile;
- this.currentProfiles = profilesByUuid.values().stream()
- .filter(p -> p.getRulesProfileUuid().equals(ruleProfile.getUuid()))
- .collect(Collectors.toList());
- this.currentActiveRule = this.activeRulesByKey.get(ActiveRuleKey.of(ruleProfile, ruleKey));
- this.currentParentActiveRule = this.currentProfiles.stream()
- .map(QProfileDto::getParentKee)
- .filter(Objects::nonNull)
- .map(profilesByUuid::get)
- .filter(Objects::nonNull)
- .findFirst()
- .map(profile -> activeRulesByKey.get(ActiveRuleKey.of(profile, ruleKey)))
- .orElse(null);
- }
-
- static final class Builder {
- private long date = System.currentTimeMillis();
- private RulesProfileDto baseRulesProfile;
- private Collection<RuleDefinitionDto> rules;
- private Collection<RuleParamDto> ruleParams;
- private Collection<QProfileDto> profiles;
- private Collection<ActiveRuleDto> activeRules;
- private Collection<ActiveRuleParamDto> activeRuleParams;
- private DescendantProfilesSupplier descendantProfilesSupplier;
-
- Builder setDate(long l) {
- this.date = l;
- return this;
- }
-
- Builder setBaseProfile(RulesProfileDto p) {
- this.baseRulesProfile = p;
- return this;
- }
-
- Builder setRules(Collection<RuleDefinitionDto> rules) {
- this.rules = rules;
- return this;
- }
-
- Builder setRuleParams(Collection<RuleParamDto> ruleParams) {
- this.ruleParams = ruleParams;
- return this;
- }
-
- /**
- * All the profiles involved in the activation workflow, including the
- * parent profile, even if it's not updated.
- */
- Builder setProfiles(Collection<QProfileDto> profiles) {
- this.profiles = profiles;
- return this;
- }
-
- Builder setActiveRules(Collection<ActiveRuleDto> activeRules) {
- this.activeRules = activeRules;
- return this;
- }
-
- Builder setActiveRuleParams(Collection<ActiveRuleParamDto> activeRuleParams) {
- this.activeRuleParams = activeRuleParams;
- return this;
- }
-
- Builder setDescendantProfilesSupplier(DescendantProfilesSupplier d) {
- this.descendantProfilesSupplier = d;
- return this;
- }
-
- RuleActivationContext build() {
- checkArgument(date > 0, "date is not set");
- requireNonNull(baseRulesProfile, "baseRulesProfile is null");
- requireNonNull(rules, "rules is null");
- requireNonNull(ruleParams, "ruleParams is null");
- requireNonNull(profiles, "profiles is null");
- requireNonNull(activeRules, "activeRules is null");
- requireNonNull(activeRuleParams, "activeRuleParams is null");
- requireNonNull(descendantProfilesSupplier, "descendantProfilesSupplier is null");
- return new RuleActivationContext(this);
- }
- }
-
- static final class RuleWrapper {
- private final RuleDefinitionDto rule;
- private final Map<String, RuleParamDto> paramsByKey;
-
- private RuleWrapper(RuleDefinitionDto rule, Collection<RuleParamDto> params) {
- this.rule = rule;
- this.paramsByKey = params.stream().collect(uniqueIndex(RuleParamDto::getName));
- }
-
- RuleDefinitionDto get() {
- return rule;
- }
-
- Collection<RuleParamDto> getParams() {
- return paramsByKey.values();
- }
-
- @CheckForNull
- RuleParamDto getParam(String key) {
- return paramsByKey.get(key);
- }
-
- @CheckForNull
- String getParamDefaultValue(String key) {
- RuleParamDto param = getParam(key);
- return param != null ? param.getDefaultValue() : null;
- }
- }
-
- static final class ActiveRuleWrapper {
- private final ActiveRuleDto activeRule;
- private final Map<String, ActiveRuleParamDto> paramsByKey;
-
- private ActiveRuleWrapper(ActiveRuleDto activeRule, Collection<ActiveRuleParamDto> params) {
- this.activeRule = activeRule;
- this.paramsByKey = params.stream().collect(uniqueIndex(ActiveRuleParamDto::getKey));
- }
-
- ActiveRuleDto get() {
- return activeRule;
- }
-
- Collection<ActiveRuleParamDto> getParams() {
- return paramsByKey.values();
- }
-
- @CheckForNull
- ActiveRuleParamDto getParam(String key) {
- return paramsByKey.get(key);
- }
-
- @CheckForNull
- String getParamValue(String key) {
- ActiveRuleParamDto param = paramsByKey.get(key);
- return param != null ? param.getValue() : null;
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Stream;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.server.rule.RuleParamType;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.qualityprofile.ActiveRuleDao;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.OrgQProfileDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.qualityprofile.RulesProfileDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.server.qualityprofile.RuleActivationContext.ActiveRuleWrapper;
-import org.sonar.server.qualityprofile.RuleActivationContext.RuleWrapper;
-import org.sonar.server.user.UserSession;
-import org.sonar.server.util.TypeValidations;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.stream.Collectors.toList;
-import static org.sonar.server.exceptions.BadRequestException.checkRequest;
-
-/**
- * Activation and deactivation of rules in Quality profiles
- */
-@ServerSide
-public class RuleActivator {
-
- private final System2 system2;
- private final DbClient db;
- private final TypeValidations typeValidations;
- private final UserSession userSession;
-
- public RuleActivator(System2 system2, DbClient db, TypeValidations typeValidations, UserSession userSession) {
- this.system2 = system2;
- this.db = db;
- this.typeValidations = typeValidations;
- this.userSession = userSession;
- }
-
-
- public List<ActiveRuleChange> activate(DbSession dbSession, Collection<RuleActivation> activations, RuleActivationContext context) {
- return activations.stream().map(a -> activate(dbSession, a, context))
- .flatMap(List::stream)
- .collect(toList());
- }
-
- public List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
- context.reset(activation.getRuleUuid());
- List<ActiveRuleChange> activeRuleChanges = doActivate(dbSession, activation, context);
- return activeRuleChanges;
- }
-
- private List<ActiveRuleChange> doActivate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
- RuleDefinitionDto rule = context.getRule().get();
- checkRequest(RuleStatus.REMOVED != rule.getStatus(), "Rule was removed: %s", rule.getKey());
- checkRequest(!rule.isTemplate(), "Rule template can't be activated on a Quality profile: %s", rule.getKey());
- checkRequest(context.getRulesProfile().getLanguage().equals(rule.getLanguage()),
- "%s rule %s cannot be activated on %s profile %s", rule.getLanguage(), rule.getKey(), context.getRulesProfile().getLanguage(),context.getRulesProfile().getName());
- List<ActiveRuleChange> changes = new ArrayList<>();
- ActiveRuleChange change;
- boolean stopCascading = false;
-
- ActiveRuleWrapper activeRule = context.getActiveRule();
- ActiveRuleKey activeRuleKey = ActiveRuleKey.of(context.getRulesProfile(), rule.getKey());
- if (activeRule == null) {
- if (activation.isReset()) {
- // ignore reset when rule is not activated
- return changes;
- }
- // new activation
- change = new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, activeRuleKey, rule);
- applySeverityAndParamToChange(activation, context, change);
- if (context.isCascading() || isSameAsParent(change, context)) {
- change.setInheritance(ActiveRuleInheritance.INHERITED);
- }
- } else {
- // already activated
- if (context.isCascading() && activeRule.get().doesOverride()) {
- // propagating to descendants, but child profile already overrides rule -> stop propagation
- return changes;
- }
- change = new ActiveRuleChange(ActiveRuleChange.Type.UPDATED, activeRuleKey, rule);
- if (context.isCascading() && activeRule.get().getInheritance() == null) {
- // activate on child, then on parent -> mark child as overriding parent
- change.setInheritance(ActiveRuleInheritance.OVERRIDES);
- change.setSeverity(activeRule.get().getSeverityString());
- for (ActiveRuleParamDto activeParam : activeRule.getParams()) {
- change.setParameter(activeParam.getKey(), activeParam.getValue());
- }
- stopCascading = true;
- } else {
- applySeverityAndParamToChange(activation, context, change);
- if (!context.isCascading() && context.getParentActiveRule() != null) {
- // override rule which is already declared on parents
- change.setInheritance(isSameAsParent(change, context) ? ActiveRuleInheritance.INHERITED : ActiveRuleInheritance.OVERRIDES);
- }
- }
- if (isSame(change, activeRule)) {
- change = null;
- stopCascading = true;
- }
- }
-
- if (change != null) {
- changes.add(change);
- persist(change, context, dbSession);
- }
-
- if (!changes.isEmpty()) {
- updateProfileDates(dbSession, context);
- }
-
- if (!stopCascading) {
- changes.addAll(propagateActivationToDescendants(dbSession, activation, context));
- }
-
- return changes;
- }
-
- private void updateProfileDates(DbSession dbSession, RuleActivationContext context) {
- RulesProfileDto ruleProfile = context.getRulesProfile();
- ruleProfile.setRulesUpdatedAtAsDate(new Date(context.getDate()));
- db.qualityProfileDao().update(dbSession, ruleProfile);
-
- if (userSession.isLoggedIn()) {
- context.getProfiles().forEach(p -> db.qualityProfileDao().update(dbSession, OrgQProfileDto.from(p).setUserUpdatedAt(context.getDate())));
- }
- }
-
- /**
- * Update severity and params
- */
- private void applySeverityAndParamToChange(RuleActivation request, RuleActivationContext context, ActiveRuleChange change) {
- RuleWrapper rule = context.getRule();
- ActiveRuleWrapper activeRule = context.getActiveRule();
- ActiveRuleWrapper parentActiveRule = context.getParentActiveRule();
-
- if (request.isReset()) {
- applySeverityAndParamsWhenResetRequested(change, rule, parentActiveRule);
- } else if (context.getRulesProfile().isBuiltIn()) {
- applySeverityAndParamsWhenBuiltInProfile(request, context, change, rule);
- } else {
- applySeverityAndParamsWhenNonBuiltInProfile(request, context, change, rule, activeRule, parentActiveRule);
- }
- }
-
- private void applySeverityAndParamsWhenResetRequested(ActiveRuleChange change, RuleWrapper rule, @Nullable ActiveRuleWrapper parentActiveRule) {
- String severity = firstNonNull(
- parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null,
- rule.get().getSeverityString());
- change.setSeverity(severity);
-
- for (RuleParamDto ruleParamDto : rule.getParams()) {
- String paramKey = ruleParamDto.getName();
- // load params from parent profile, else from default values
- String paramValue = firstNonNull(
- parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null,
- rule.getParamDefaultValue(paramKey));
-
- change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
- }
- }
-
- private void applySeverityAndParamsWhenBuiltInProfile(RuleActivation request, RuleActivationContext context, ActiveRuleChange change,
- RuleWrapper rule) {
- // for builtin quality profiles, the severity from profile, when null use the default severity of the rule
- String severity = firstNonNull(request.getSeverity(), rule.get().getSeverityString());
- change.setSeverity(severity);
-
- for (RuleParamDto ruleParamDto : rule.getParams()) {
- String paramKey = ruleParamDto.getName();
- // use the value defined in the profile definition, else the rule default value
- String paramValue = firstNonNull(
- context.getRequestedParamValue(request, paramKey),
- rule.getParamDefaultValue(paramKey));
- change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
- }
- }
-
- /**
- * 1. apply requested severity and param
- * 2. if rule activated and overridden - apply user value
- * 3. apply parent value
- * 4. apply defaults
- */
- private void applySeverityAndParamsWhenNonBuiltInProfile(RuleActivation request, RuleActivationContext context, ActiveRuleChange change,
- RuleWrapper rule, @Nullable ActiveRuleWrapper activeRule, @Nullable ActiveRuleWrapper parentActiveRule) {
- String severity = getSeverityForNonBuiltInProfile(request, rule, activeRule, parentActiveRule);
- change.setSeverity(severity);
-
- for (RuleParamDto ruleParamDto : rule.getParams()) {
- String paramKey = ruleParamDto.getName();
- String parentValue = parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null;
- String paramValue;
- if (context.hasRequestedParamValue(request, paramKey)) {
- // If the request contains the parameter then we're using either value from request, or parent value, or default value
- paramValue = firstNonNull(
- context.getRequestedParamValue(request, paramKey),
- parentValue,
- rule.getParamDefaultValue(paramKey));
- } else if (activeRule != null) {
- // If the request doesn't contain the parameter, then we're using either user value from db, or parent value if rule inherited, or default
- // value
- paramValue = firstNonNull(
- activeRule.get().doesOverride() ? activeRule.getParamValue(paramKey) : null,
- parentValue == null ? activeRule.getParamValue(paramKey) : parentValue,
- rule.getParamDefaultValue(paramKey));
- } else {
- paramValue = firstNonNull(
- parentValue,
- rule.getParamDefaultValue(paramKey));
- }
- change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
- }
- }
-
- private static String getSeverityForNonBuiltInProfile(RuleActivation request, RuleWrapper rule, @Nullable ActiveRuleWrapper activeRule,
- @Nullable ActiveRuleWrapper parentActiveRule) {
- String severity;
- if (activeRule != null) {
- ActiveRuleDto activeRuleDto = activeRule.get();
- // load severity from request, else keep existing one (if overridden), else from parent if rule inherited, else from default
- severity = firstNonNull(
- request.getSeverity(),
- activeRuleDto.doesOverride() ? activeRuleDto.getSeverityString() : null,
- parentActiveRule != null ? parentActiveRule.get().getSeverityString() : activeRuleDto.getSeverityString(),
- rule.get().getSeverityString());
- } else {
- // load severity from request, else from parent, else from default
- severity = firstNonNull(
- request.getSeverity(),
- parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null,
- rule.get().getSeverityString());
- }
- return severity;
- }
-
- private List<ActiveRuleChange> propagateActivationToDescendants(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
- List<ActiveRuleChange> changes = new ArrayList<>();
-
- // get all inherited profiles
- context.getChildProfiles().forEach(child -> {
- context.selectChild(child);
- changes.addAll(doActivate(dbSession, activation, context));
- });
- return changes;
- }
-
- private void persist(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
- ActiveRuleDto activeRule = null;
- if (change.getType() == ActiveRuleChange.Type.ACTIVATED) {
- activeRule = doInsert(change, context, dbSession);
- } else if (change.getType() == ActiveRuleChange.Type.DEACTIVATED) {
- ActiveRuleDao dao = db.activeRuleDao();
- activeRule = dao.delete(dbSession, change.getKey()).orElse(null);
-
- } else if (change.getType() == ActiveRuleChange.Type.UPDATED) {
- activeRule = doUpdate(change, context, dbSession);
- }
- change.setActiveRule(activeRule);
- db.qProfileChangeDao().insert(dbSession, change.toDto(userSession.getUuid()));
- }
-
- private ActiveRuleDto doInsert(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
- ActiveRuleDao dao = db.activeRuleDao();
- RuleWrapper rule = context.getRule();
-
- ActiveRuleDto activeRule = new ActiveRuleDto();
- activeRule.setProfileUuid(context.getRulesProfile().getUuid());
- activeRule.setRuleUuid(rule.get().getUuid());
- activeRule.setKey(ActiveRuleKey.of(context.getRulesProfile(), rule.get().getKey()));
- String severity = change.getSeverity();
- if (severity != null) {
- activeRule.setSeverity(severity);
- }
- ActiveRuleInheritance inheritance = change.getInheritance();
- if (inheritance != null) {
- activeRule.setInheritance(inheritance.name());
- }
- activeRule.setUpdatedAt(system2.now());
- activeRule.setCreatedAt(system2.now());
- dao.insert(dbSession, activeRule);
- for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
- if (param.getValue() != null) {
- ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(rule.getParam(param.getKey()));
- paramDto.setValue(param.getValue());
- dao.insertParam(dbSession, activeRule, paramDto);
- }
- }
- return activeRule;
- }
-
- private ActiveRuleDto doUpdate(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
- ActiveRuleWrapper activeRule = context.getActiveRule();
- if (activeRule == null) {
- return null;
- }
- ActiveRuleDao dao = db.activeRuleDao();
- String severity = change.getSeverity();
- if (severity != null) {
- activeRule.get().setSeverity(severity);
- }
- ActiveRuleInheritance inheritance = change.getInheritance();
- if (inheritance != null) {
- activeRule.get().setInheritance(inheritance.name());
- }
- activeRule.get().setUpdatedAt(system2.now());
- dao.update(dbSession, activeRule.get());
-
- for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
- ActiveRuleParamDto activeRuleParamDto = activeRule.getParam(param.getKey());
- if (activeRuleParamDto == null) {
- // did not exist
- if (param.getValue() != null) {
- activeRuleParamDto = ActiveRuleParamDto.createFor(context.getRule().getParam(param.getKey()));
- activeRuleParamDto.setValue(param.getValue());
- dao.insertParam(dbSession, activeRule.get(), activeRuleParamDto);
- }
- } else {
- if (param.getValue() != null) {
- activeRuleParamDto.setValue(param.getValue());
- dao.updateParam(dbSession, activeRuleParamDto);
- } else {
- dao.deleteParam(dbSession, activeRuleParamDto);
- }
- }
- }
- return activeRule.get();
- }
-
- public List<ActiveRuleChange> deactivate(DbSession dbSession, RuleActivationContext context, String ruleUuid, boolean force) {
- context.reset(ruleUuid);
- List<ActiveRuleChange> activeRuleChanges = doDeactivate(dbSession, context, force);
- return activeRuleChanges;
- }
-
- private List<ActiveRuleChange> doDeactivate(DbSession dbSession, RuleActivationContext context, boolean force) {
- List<ActiveRuleChange> changes = new ArrayList<>();
- ActiveRuleWrapper activeRule = context.getActiveRule();
- if (activeRule == null) {
- return changes;
- }
-
- ActiveRuleChange change;
- checkRequest(force || context.isCascading() || activeRule.get().getInheritance() == null, "Cannot deactivate inherited rule '%s'", context.getRule().get().getKey());
- change = new ActiveRuleChange(ActiveRuleChange.Type.DEACTIVATED, activeRule.get(), context.getRule().get());
- changes.add(change);
- persist(change, context, dbSession);
-
- // get all inherited profiles (they are not built-in by design)
- context.getChildProfiles().forEach(child -> {
- context.selectChild(child);
- changes.addAll(doDeactivate(dbSession, context, force));
- });
-
- if (!changes.isEmpty()) {
- updateProfileDates(dbSession, context);
- }
-
- return changes;
- }
-
- @CheckForNull
- private String validateParam(RuleParamDto ruleParam, @Nullable String value) {
- if (value != null) {
- RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType());
- if (ruleParamType.multiple()) {
- List<String> values = Splitter.on(",").splitToList(value);
- typeValidations.validate(values, ruleParamType.type(), ruleParamType.values());
- } else {
- typeValidations.validate(value, ruleParamType.type(), ruleParamType.values());
- }
- }
- return value;
- }
-
- public RuleActivationContext createContextForBuiltInProfile(DbSession dbSession, RulesProfileDto builtInProfile, Collection<String> ruleUuids) {
- checkArgument(builtInProfile.isBuiltIn(), "Rules profile with UUID %s is not built-in", builtInProfile.getUuid());
-
- RuleActivationContext.Builder builder = new RuleActivationContext.Builder();
- builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession));
-
- // load rules
- completeWithRules(dbSession, builder, ruleUuids);
-
- // load org profiles. Their parents are null by nature.
- List<QProfileDto> profiles = db.qualityProfileDao().selectQProfilesByRuleProfile(dbSession, builtInProfile);
- builder.setProfiles(profiles);
- builder.setBaseProfile(builtInProfile);
-
- // load active rules
- Collection<String> ruleProfileUuids = Stream
- .concat(Stream.of(builtInProfile.getUuid()), profiles.stream().map(QProfileDto::getRulesProfileUuid))
- .collect(MoreCollectors.toHashSet(profiles.size() + 1));
- completeWithActiveRules(dbSession, builder, ruleUuids, ruleProfileUuids);
- return builder.build();
- }
-
- public RuleActivationContext createContextForUserProfile(DbSession dbSession, QProfileDto profile, Collection<String> ruleUuids) {
- checkArgument(!profile.isBuiltIn(), "Profile with UUID %s is built-in", profile.getKee());
- RuleActivationContext.Builder builder = new RuleActivationContext.Builder();
- builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession));
-
- // load rules
- completeWithRules(dbSession, builder, ruleUuids);
-
- // load profiles
- List<QProfileDto> profiles = new ArrayList<>();
- profiles.add(profile);
- if (profile.getParentKee() != null) {
- profiles.add(db.qualityProfileDao().selectByUuid(dbSession, profile.getParentKee()));
- }
- builder.setProfiles(profiles);
- builder.setBaseProfile(RulesProfileDto.from(profile));
-
- // load active rules
- Collection<String> ruleProfileUuids = profiles.stream()
- .map(QProfileDto::getRulesProfileUuid)
- .collect(MoreCollectors.toHashSet(profiles.size()));
- completeWithActiveRules(dbSession, builder, ruleUuids, ruleProfileUuids);
-
- return builder.build();
- }
-
- DescendantProfilesSupplier createDescendantProfilesSupplier(DbSession dbSession) {
- return (parents, ruleUuids) -> {
- Collection<QProfileDto> profiles = db.qualityProfileDao().selectDescendants(dbSession, parents);
- Set<String> ruleProfileUuids = profiles.stream()
- .map(QProfileDto::getRulesProfileUuid)
- .collect(MoreCollectors.toHashSet());
- Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleUuids, ruleProfileUuids);
- List<String> activeRuleUuids = activeRules.stream().map(ActiveRuleDto::getUuid).collect(MoreCollectors.toArrayList(activeRules.size()));
- List<ActiveRuleParamDto> activeRuleParams = db.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids);
- return new DescendantProfilesSupplier.Result(profiles, activeRules, activeRuleParams);
- };
- }
-
- private void completeWithRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<String> ruleUuids) {
- List<RuleDefinitionDto> rules = db.ruleDao().selectDefinitionByUuids(dbSession, ruleUuids);
- builder.setRules(rules);
- builder.setRuleParams(db.ruleDao().selectRuleParamsByRuleUuids(dbSession, ruleUuids));
- }
-
- private void completeWithActiveRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<String> ruleUuids, Collection<String> ruleProfileUuids) {
- Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleUuids, ruleProfileUuids);
- builder.setActiveRules(activeRules);
- List<String> activeRuleUuids = activeRules.stream().map(ActiveRuleDto::getUuid).collect(MoreCollectors.toArrayList(activeRules.size()));
- builder.setActiveRuleParams(db.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids));
- }
-
- private static boolean isSame(ActiveRuleChange change, ActiveRuleWrapper activeRule) {
- ActiveRuleInheritance inheritance = change.getInheritance();
- if (inheritance != null && !inheritance.name().equals(activeRule.get().getInheritance())) {
- return false;
- }
- String severity = change.getSeverity();
- if (severity != null && !severity.equals(activeRule.get().getSeverityString())) {
- return false;
- }
- for (Map.Entry<String, String> changeParam : change.getParameters().entrySet()) {
- String activeParamValue = activeRule.getParamValue(changeParam.getKey());
- if (changeParam.getValue() == null && activeParamValue != null) {
- return false;
- }
- if (changeParam.getValue() != null && (activeParamValue == null || !StringUtils.equals(changeParam.getValue(), activeParamValue))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * True if trying to override an inherited rule but with exactly the same values
- */
- private static boolean isSameAsParent(ActiveRuleChange change, RuleActivationContext context) {
- ActiveRuleWrapper parentActiveRule = context.getParentActiveRule();
- if (parentActiveRule == null) {
- return false;
- }
- if (!StringUtils.equals(change.getSeverity(), parentActiveRule.get().getSeverityString())) {
- return false;
- }
- for (Map.Entry<String, String> entry : change.getParameters().entrySet()) {
- if (entry.getValue() != null && !entry.getValue().equals(parentActiveRule.getParamValue(entry.getKey()))) {
- return false;
- }
- }
- return true;
- }
-
- @CheckForNull
- private static String firstNonNull(String... strings) {
- for (String s : strings) {
- if (s != null) {
- return s;
- }
- }
- return null;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.core.util.RuleActivationListener;
-import org.sonar.core.util.RuleSetChangeEvent;
-
-public interface RuleActivatorEventsDistributor {
-
- void subscribe(RuleActivationListener listener);
-
- void pushEvent(RuleSetChangeEvent event);
-}
\ No newline at end of file
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.sonar.api.server.ServerSide;
-import org.sonar.core.util.RuleActivationListener;
-import org.sonar.core.util.RuleSetChangeEvent;
-
-@ServerSide
-public class StandaloneRuleActivatorEventsDistributor implements RuleActivatorEventsDistributor {
-
- private List<RuleActivationListener> listeners = new ArrayList<>();
-
- @Override
- public void subscribe(RuleActivationListener listener) {
- listeners.add(listener);
- }
-
- @Override
- public void pushEvent(RuleSetChangeEvent event) {
- listeners.forEach(l -> l.listen(event));
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.qualityprofile;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Random;
-import java.util.Set;
-import java.util.stream.IntStream;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.EmailSubscriberDto;
-import org.sonar.db.permission.AuthorizationDao;
-import org.sonar.server.notification.email.EmailNotificationChannel;
-
-import static java.util.Collections.emptySet;
-import static java.util.stream.Collectors.toSet;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-public class BuiltInQPChangeNotificationHandlerTest {
- private DbClient dbClient = mock(DbClient.class);
- private DbSession dbSession = mock(DbSession.class);
- private AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
- private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
-
- private BuiltInQPChangeNotificationHandler underTest = new BuiltInQPChangeNotificationHandler(dbClient, emailNotificationChannel);
-
- @Before
- public void wire_mocks() {
- when(dbClient.openSession(false)).thenReturn(dbSession);
- when(dbClient.authorizationDao()).thenReturn(authorizationDao);
- }
-
- @Test
- public void getMetadata_returns_empty() {
- assertThat(underTest.getMetadata()).isEmpty();
- }
-
- @Test
- public void getNotificationClass_is_BuiltInQPChangeNotification() {
- assertThat(underTest.getNotificationClass()).isEqualTo(BuiltInQPChangeNotification.class);
- }
-
- @Test
- public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() {
- when(emailNotificationChannel.isActivated()).thenReturn(false);
- Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
- .mapToObj(i -> mock(BuiltInQPChangeNotification.class))
- .collect(toSet());
-
- int deliver = underTest.deliver(notifications);
-
- assertThat(deliver).isZero();
- verify(emailNotificationChannel).isActivated();
- verifyNoMoreInteractions(emailNotificationChannel);
- verifyZeroInteractions(dbClient);
- notifications.forEach(Mockito::verifyZeroInteractions);
- }
-
- @Test
- public void deliver_has_no_effect_if_there_is_no_global_administer_email_subscriber() {
- when(emailNotificationChannel.isActivated()).thenReturn(true);
- Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
- .mapToObj(i -> mock(BuiltInQPChangeNotification.class))
- .collect(toSet());
- when(authorizationDao.selectQualityProfileAdministratorLogins(dbSession))
- .thenReturn(emptySet());
-
- int deliver = underTest.deliver(notifications);
-
- assertThat(deliver).isZero();
- verify(emailNotificationChannel).isActivated();
- verifyNoMoreInteractions(emailNotificationChannel);
- verify(dbClient).openSession(false);
- verify(dbClient).authorizationDao();
- verifyNoMoreInteractions(dbClient);
- verify(authorizationDao).selectQualityProfileAdministratorLogins(dbSession);
- verifyNoMoreInteractions(authorizationDao);
- notifications.forEach(Mockito::verifyZeroInteractions);
- }
-
- @Test
- public void deliver_create_emailRequest_for_each_notification_and_for_each_global_administer_email_subscriber() {
- when(emailNotificationChannel.isActivated()).thenReturn(true);
- Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
- .mapToObj(i -> mock(BuiltInQPChangeNotification.class))
- .collect(toSet());
- Set<EmailSubscriberDto> emailSubscribers = IntStream.range(0, 1 + new Random().nextInt(10))
- .mapToObj(i -> EmailSubscriberDto.create("login_" + i, true, "login_" + i + "@foo"))
- .collect(toSet());
- when(authorizationDao.selectQualityProfileAdministratorLogins(dbSession))
- .thenReturn(emailSubscribers);
- Set<EmailNotificationChannel.EmailDeliveryRequest> expectedRequests = notifications.stream()
- .flatMap(notification -> emailSubscribers.stream().map(subscriber -> new EmailNotificationChannel.EmailDeliveryRequest(subscriber.getEmail(), notification)))
- .collect(toSet());
- int deliveries = new Random().nextInt(expectedRequests.size());
- when(emailNotificationChannel.deliverAll(expectedRequests)).thenReturn(deliveries);
-
- int deliver = underTest.deliver(notifications);
-
- assertThat(deliver).isEqualTo(deliveries);
- verify(emailNotificationChannel).isActivated();
- verify(emailNotificationChannel).deliverAll(expectedRequests);
- verifyNoMoreInteractions(emailNotificationChannel);
- verify(dbClient).openSession(false);
- verify(dbClient).authorizationDao();
- verifyNoMoreInteractions(dbClient);
- verify(authorizationDao).selectQualityProfileAdministratorLogins(dbSession);
- verifyNoMoreInteractions(authorizationDao);
- notifications.forEach(Mockito::verifyZeroInteractions);
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Date;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.platform.Server;
-import org.sonar.server.issue.notification.EmailMessage;
-import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.utils.DateUtils.formatDate;
-
-public class BuiltInQPChangeNotificationTemplateTest {
-
- private Server server = mock(Server.class);
-
- private BuiltInQPChangeNotificationTemplate underTest = new BuiltInQPChangeNotificationTemplate(server);
-
- @Before
- public void setUp() {
- when(server.getPublicRootUrl()).thenReturn("http://" + randomAlphanumeric(10));
- }
-
- @Test
- public void notification_contains_a_subject() {
- String profileName = newProfileName();
- String languageKey = newLanguageKey();
- String languageName = newLanguageName();
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setNewRules(2)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertThat(emailMessage.getSubject()).isEqualTo("Built-in quality profiles have been updated");
- }
-
- @Test
- public void notification_contains_count_of_new_rules() {
- String profileName = newProfileName();
- String languageKey = newLanguageKey();
- String languageName = newLanguageName();
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setNewRules(2)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertMessage(emailMessage, "\n 2 new rules\n");
- }
-
- @Test
- public void notification_contains_count_of_updated_rules() {
- String profileName = newProfileName();
- String languageKey = newLanguageKey();
- String languageName = newLanguageName();
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setUpdatedRules(2)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertMessage(emailMessage, "\n 2 rules have been updated\n");
- }
-
- @Test
- public void notification_contains_count_of_removed_rules() {
- String profileName = newProfileName();
- String languageKey = newLanguageKey();
- String languageName = newLanguageName();
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setRemovedRules(2)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertMessage(emailMessage, "\n 2 rules removed\n");
- }
-
- @Test
- public void notification_supports_grammar_for_single_rule_added_removed_or_updated() {
- String profileName = newProfileName();
- String languageKey = newLanguageKey();
- String languageName = newLanguageName();
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setNewRules(1)
- .setUpdatedRules(1)
- .setRemovedRules(1)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertThat(emailMessage.getMessage())
- .contains("\n 1 new rule\n")
- .contains("\n 1 rule has been updated\n")
- .contains("\n 1 rule removed\n");
- }
-
- @Test
- public void notification_contains_list_of_new_updated_and_removed_rules() {
- String profileName = newProfileName();
- String languageKey = newLanguageKey();
- String languageName = newLanguageName();
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setNewRules(2)
- .setUpdatedRules(3)
- .setRemovedRules(4)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertMessage(emailMessage,
- "\n" +
- " 2 new rules\n" +
- " 3 rules have been updated\n" +
- " 4 rules removed\n");
- }
-
- @Test
- public void notification_contains_many_profiles() {
- String profileName1 = "profile1_" + randomAlphanumeric(20);
- String languageKey1 = "langkey1_" + randomAlphanumeric(20);
- String languageName1 = "langName1_" + randomAlphanumeric(20);
- String profileName2 = "profile2_" + randomAlphanumeric(20);
- String languageKey2 = "langkey2_" + randomAlphanumeric(20);
- String languageName2 = "langName2_" + randomAlphanumeric(20);
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName1)
- .setLanguageKey(languageKey1)
- .setLanguageName(languageName1)
- .setNewRules(2)
- .build())
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName2)
- .setLanguageKey(languageKey2)
- .setLanguageName(languageName2)
- .setNewRules(13)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertThat(emailMessage.getMessage()).containsSubsequence("The following built-in profiles have been updated:\n",
- profileTitleText(profileName1, languageKey1, languageName1),
- " 2 new rules\n",
- profileTitleText(profileName2, languageKey2, languageName2),
- " 13 new rules\n",
- "This is a good time to review your quality profiles and update them to benefit from the latest evolutions: " + server.getPublicRootUrl() + "/profiles");
- }
-
- @Test
- public void notification_contains_profiles_sorted_by_language_then_by_profile_name() {
- String languageKey1 = "langkey1_" + randomAlphanumeric(20);
- String languageName1 = "langName1_" + randomAlphanumeric(20);
- String languageKey2 = "langKey2_" + randomAlphanumeric(20);
- String languageName2 = "langName2_" + randomAlphanumeric(20);
- String profileName1 = "profile1_" + randomAlphanumeric(20);
- String profileName2 = "profile2_" + randomAlphanumeric(20);
- String profileName3 = "profile3_" + randomAlphanumeric(20);
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder().setProfileName(profileName3).setLanguageKey(languageKey2).setLanguageName(languageName2).build())
- .addProfile(Profile.newBuilder().setProfileName(profileName2).setLanguageKey(languageKey1).setLanguageName(languageName1).build())
- .addProfile(Profile.newBuilder().setProfileName(profileName1).setLanguageKey(languageKey2).setLanguageName(languageName2).build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertThat(emailMessage.getMessage()).containsSubsequence(
- "\"" + profileName2 + "\" - " + languageName1,
- "\"" + profileName1 + "\" - " + languageName2,
- "\"" + profileName3 + "\" - " + languageName2);
- }
-
- @Test
- public void notification_contains_encoded_profile_name() {
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName("Sonar Way")
- .setLanguageKey("java")
- .setLanguageName(newLanguageName())
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertThat(emailMessage.getMessage()).contains(server.getPublicRootUrl() + "/profiles/changelog?language=java&name=Sonar+Way");
- }
-
- @Test
- public void notification_contains_from_and_to_date() {
- String profileName = newProfileName();
- String languageKey = newLanguageKey();
- String languageName = newLanguageName();
- long startDate = 1_000_000_000_000L;
- long endDate = startDate + 1_100_000_000_000L;
- BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setStartDate(startDate)
- .setEndDate(endDate)
- .build());
-
- EmailMessage emailMessage = underTest.format(notification.build());
-
- assertMessage(emailMessage,
- profileTitleText(profileName, languageKey, languageName, formatDate(new Date(startDate)), formatDate(new Date(endDate))));
- }
-
- private void assertMessage(EmailMessage emailMessage, String expectedProfileDetails) {
- assertThat(emailMessage.getMessage())
- .containsSubsequence(
- "The following built-in profiles have been updated:\n\n",
- expectedProfileDetails,
- "\nThis is a good time to review your quality profiles and update them to benefit from the latest evolutions: " + server.getPublicRootUrl() + "/profiles");
- }
-
- private String profileTitleText(String profileName, String languageKey, String languageName) {
- return "\"" + profileName + "\" - " + languageName + ": " + server.getPublicRootUrl() + "/profiles/changelog?language=" + languageKey + "&name=" + profileName;
- }
-
- private String profileTitleText(String profileName, String languageKey, String languageName, String startDate, String endDate) {
- return "\"" + profileName + "\" - " + languageName + ": " + server.getPublicRootUrl() + "/profiles/changelog?language=" + languageKey + "&name=" + profileName +
- "&since=" + startDate + "&to=" + endDate + "\n";
- }
-
- private static String newProfileName() {
- return "profileName_" + randomAlphanumeric(20);
- }
-
- private static String newLanguageName() {
- return "languageName_" + randomAlphanumeric(20);
- }
-
- private static String newLanguageKey() {
- return "languageKey_" + randomAlphanumeric(20);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Random;
-import org.junit.Test;
-import org.sonar.api.notifications.Notification;
-import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.Assertions.tuple;
-
-public class BuiltInQPChangeNotificationTest {
-
- private static final Random RANDOM = new Random();
-
-
- @Test
- public void serialize_and_parse_no_profile() {
- Notification notification = new BuiltInQPChangeNotificationBuilder().build();
-
- BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
-
- assertThat(result.getProfiles()).isEmpty();
- }
-
- @Test
- public void serialize_and_parse_single_profile() {
- String profileName = randomAlphanumeric(20);
- String languageKey = randomAlphanumeric(20);
- String languageName = randomAlphanumeric(20);
- int newRules = RANDOM.nextInt(5000);
- int updatedRules = RANDOM.nextInt(5000);
- int removedRules = RANDOM.nextInt(5000);
- long startDate = RANDOM.nextInt(5000);
- long endDate = startDate + RANDOM.nextInt(5000);
-
- BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setNewRules(newRules)
- .setUpdatedRules(updatedRules)
- .setRemovedRules(removedRules)
- .setStartDate(startDate)
- .setEndDate(endDate)
- .build())
- .build();
- BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
-
- assertThat(result.getProfiles())
- .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules, Profile::getUpdatedRules, Profile::getRemovedRules,
- Profile::getStartDate, Profile::getEndDate)
- .containsExactlyInAnyOrder(tuple(profileName, languageKey, languageName, newRules, updatedRules, removedRules, startDate, endDate));
- }
-
- @Test
- public void serialize_and_parse_multiple_profiles() {
- String profileName1 = randomAlphanumeric(20);
- String languageKey1 = randomAlphanumeric(20);
- String languageName1 = randomAlphanumeric(20);
- String profileName2 = randomAlphanumeric(20);
- String languageKey2 = randomAlphanumeric(20);
- String languageName2 = randomAlphanumeric(20);
-
- BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName1)
- .setLanguageKey(languageKey1)
- .setLanguageName(languageName1)
- .build())
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName2)
- .setLanguageKey(languageKey2)
- .setLanguageName(languageName2)
- .build())
- .build();
- BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
-
- assertThat(result.getProfiles()).extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName)
- .containsExactlyInAnyOrder(tuple(profileName1, languageKey1, languageName1), tuple(profileName2, languageKey2, languageName2));
- }
-
- @Test
- public void serialize_and_parse_max_values() {
- String profileName = randomAlphanumeric(20);
- String languageKey = randomAlphanumeric(20);
- String languageName = randomAlphanumeric(20);
- int newRules = Integer.MAX_VALUE;
- int updatedRules = Integer.MAX_VALUE;
- int removedRules = Integer.MAX_VALUE;
- long startDate = Long.MAX_VALUE;
- long endDate = Long.MAX_VALUE;
-
- BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder()
- .addProfile(Profile.newBuilder()
- .setProfileName(profileName)
- .setLanguageKey(languageKey)
- .setLanguageName(languageName)
- .setNewRules(newRules)
- .setUpdatedRules(updatedRules)
- .setRemovedRules(removedRules)
- .setStartDate(startDate)
- .setEndDate(endDate)
- .build())
- .build();
- BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
-
- assertThat(result.getProfiles())
- .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules, Profile::getUpdatedRules, Profile::getRemovedRules,
- Profile::getStartDate, Profile::getEndDate)
- .containsExactlyInAnyOrder(tuple(profileName, languageKey, languageName, newRules, updatedRules, removedRules, startDate, endDate));
- }
-
- @Test
- public void fail_with_ISE_when_parsing_empty_notification() {
- assertThatThrownBy(() -> BuiltInQPChangeNotificationBuilder.parse(new Notification(BuiltInQPChangeNotification.TYPE)))
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Could not read the built-in quality profile notification");
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualityprofile;
-
-import java.util.Arrays;
-import java.util.List;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.PropertyType;
-import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.QProfileChangeDto;
-import org.sonar.db.qualityprofile.QProfileChangeQuery;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.rule.DefaultRuleFinder;
-import org.sonar.server.rule.ServerRuleFinder;
-import org.sonar.server.util.StringTypeValidation;
-import org.sonar.server.util.TypeValidations;
-
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class BuiltInQProfileInsertImplTest {
-
- @Rule
- public BuiltInQProfileRepositoryRule builtInQProfileRepository = new BuiltInQProfileRepositoryRule();
- @Rule
- public DbTester db = DbTester.create();
-
- private final System2 system2 = new AlwaysIncreasingSystem2();
- private final UuidFactory uuidFactory = new SequenceUuidFactory();
- private final TypeValidations typeValidations = new TypeValidations(singletonList(new StringTypeValidation()));
- private final DbSession dbSession = db.getSession();
- private final DbSession batchDbSession = db.getDbClient().openSession(true);
- private final ServerRuleFinder ruleFinder = new DefaultRuleFinder(db.getDbClient());
- private final ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
- private final BuiltInQProfileInsertImpl underTest = new BuiltInQProfileInsertImpl(db.getDbClient(), ruleFinder, system2, uuidFactory, typeValidations, activeRuleIndexer);
-
- @After
- public void tearDown() {
- batchDbSession.close();
- }
-
- @Test
- public void insert_active_rules_and_changelog() {
- RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
- RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo");
-
- newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
- newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
- newQp.done();
-
- BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"), rule1, rule2);
- call(builtIn);
-
- verifyTableSize("rules_profiles", 1);
- verifyTableSize("org_qprofiles", 1);
- verifyTableSize("active_rules", 2);
- verifyTableSize("active_rule_parameters", 0);
- verifyTableSize("qprofile_changes", 2);
- verifyTableSize("default_qprofiles", 0);
-
- QProfileDto profile = verifyProfileInDb(builtIn);
- verifyActiveRuleInDb(profile, rule1, Severity.CRITICAL);
- verifyActiveRuleInDb(profile, rule2, Severity.MAJOR);
- }
-
- @Test
- public void insert_default_qp() {
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- context.createBuiltInQualityProfile("the name", "xoo")
- .setDefault(true)
- .done();
-
- BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
- call(builtIn);
-
- verifyTableSize("rules_profiles", 1);
- verifyTableSize("org_qprofiles", 1);
- verifyTableSize("active_rules", 0);
- verifyTableSize("active_rule_parameters", 0);
- verifyTableSize("qprofile_changes", 0);
- verifyTableSize("default_qprofiles", 1);
-
- verifyProfileInDb(builtIn);
- }
-
- @Test
- public void insert_active_rules_with_params() {
- RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
- RuleParamDto param1 = db.rules().insertRuleParam(rule1, p -> p.setType(PropertyType.STRING.name()));
- RuleParamDto param2 = db.rules().insertRuleParam(rule1, p -> p.setType(PropertyType.STRING.name()));
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo");
-
- newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
- newQp.done();
-
- BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"), rule1);
- call(builtIn);
-
- verifyTableSize("rules_profiles", 1);
- verifyTableSize("org_qprofiles", 1);
- verifyTableSize("active_rules", 1);
- verifyTableSize("active_rule_parameters", 2);
- verifyTableSize("qprofile_changes", 1);
-
- QProfileDto profile = verifyProfileInDb(builtIn);
- verifyActiveRuleInDb(profile, rule1, Severity.CRITICAL, param1, param2);
- }
-
- @Test
- public void flag_profile_as_default_if_declared_as_default_by_api() {
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
- newQp.done();
-
- BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
-
- call(builtIn);
-
- QProfileDto profile = verifyProfileInDb(builtIn);
- QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, "xoo");
- assertThat(defaultProfile.getKee()).isEqualTo(profile.getKee());
- }
-
- @Test
- public void existing_default_profile_must_not_be_changed() {
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
- newQp.done();
- BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
-
- QProfileDto currentDefault = db.qualityProfiles().insert(p -> p.setLanguage("xoo"));
- db.qualityProfiles().setAsDefault(currentDefault);
-
- call(builtIn);
-
- QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, "xoo");
- assertThat(defaultProfile.getKee()).isEqualTo(currentDefault.getKee());
- verifyTableSize("rules_profiles", 2);
- }
-
- @Test
- public void dont_flag_profile_as_default_if_not_declared_as_default_by_api() {
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(false);
- newQp.done();
- BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
-
- call(builtIn);
-
- QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, "xoo");
- assertThat(defaultProfile).isNull();
- }
-
- // TODO test lot of active_rules, params, orgas
-
- private void verifyActiveRuleInDb(QProfileDto profile, RuleDefinitionDto rule, String expectedSeverity, RuleParamDto... paramDtos) {
- ActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByKey(dbSession, ActiveRuleKey.of(profile, rule.getKey())).get();
- assertThat(activeRule.getUuid()).isNotNull();
- assertThat(activeRule.getInheritance()).isNull();
- assertThat(activeRule.doesOverride()).isFalse();
- assertThat(activeRule.getRuleUuid()).isEqualTo(rule.getUuid());
- assertThat(activeRule.getProfileUuid()).isEqualTo(profile.getRulesProfileUuid());
- assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity);
- assertThat(activeRule.getCreatedAt()).isPositive();
- assertThat(activeRule.getUpdatedAt()).isPositive();
-
- List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(dbSession, activeRule.getUuid());
- assertThat(params).extracting(ActiveRuleParamDto::getKey).containsOnly(Arrays.stream(paramDtos).map(RuleParamDto::getName).toArray(String[]::new));
-
- QProfileChangeQuery changeQuery = new QProfileChangeQuery(profile.getKee());
- QProfileChangeDto change = db.getDbClient().qProfileChangeDao().selectByQuery(dbSession, changeQuery).stream()
- .filter(c -> c.getDataAsMap().get("ruleUuid").equals(rule.getUuid()))
- .findFirst()
- .get();
- assertThat(change.getChangeType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED.name());
- assertThat(change.getCreatedAt()).isPositive();
- assertThat(change.getUuid()).isNotEmpty();
- assertThat(change.getUserUuid()).isNull();
- assertThat(change.getRulesProfileUuid()).isEqualTo(profile.getRulesProfileUuid());
- assertThat(change.getDataAsMap()).containsEntry("severity", expectedSeverity);
- }
-
- private QProfileDto verifyProfileInDb(BuiltInQProfile builtIn) {
- QProfileDto profileOnOrg1 = db.getDbClient().qualityProfileDao().selectByNameAndLanguage(dbSession, builtIn.getName(), builtIn.getLanguage());
- assertThat(profileOnOrg1.getLanguage()).isEqualTo(builtIn.getLanguage());
- assertThat(profileOnOrg1.getName()).isEqualTo(builtIn.getName());
- assertThat(profileOnOrg1.getParentKee()).isNull();
- assertThat(profileOnOrg1.getLastUsed()).isNull();
- assertThat(profileOnOrg1.getUserUpdatedAt()).isNull();
- assertThat(profileOnOrg1.getRulesUpdatedAt()).isNotEmpty();
- assertThat(profileOnOrg1.getKee()).isNotEqualTo(profileOnOrg1.getRulesProfileUuid());
- assertThat(profileOnOrg1.getRulesProfileUuid()).isNotNull();
- return profileOnOrg1;
- }
-
- private void verifyTableSize(String table, int expectedSize) {
- assertThat(db.countRowsOfTable(dbSession, table)).as("table " + table).isEqualTo(expectedSize);
- }
-
- private void call(BuiltInQProfile builtIn) {
- underTest.create(dbSession, builtIn);
- dbSession.commit();
- batchDbSession.commit();
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.server.language.LanguageTesting;
-import org.sonar.server.rule.DefaultRuleFinder;
-import org.sonar.server.rule.ServerRuleFinder;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.mockito.Mockito.mock;
-import static org.sonar.db.rule.RuleTesting.EXTERNAL_XOO;
-import static org.sonar.server.qualityprofile.BuiltInQProfile.ActiveRule;
-
-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 DbTester db = DbTester.create();
-
- private final DbClient dbClient = db.getDbClient();
- private final ServerRuleFinder ruleFinder = new DefaultRuleFinder(dbClient);
-
- @Test
- public void create_qprofile_with_rule() {
- RuleDefinitionDto rule1 = db.rules().insert();
- RuleDefinitionDto rule2 = db.rules().insert();
- db.rules().insert();
- DummyProfileDefinition definition = new DummyProfileDefinition("foo", "foo", false, asList(rule1.getKey(), rule2.getKey()));
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definition);
-
- underTest.initialize();
-
- assertThat(underTest.get())
- .extracting(BuiltInQProfile::getName)
- .containsExactlyInAnyOrder("foo");
- assertThat(underTest.get().get(0).getActiveRules())
- .extracting(ActiveRule::getRuleUuid, ActiveRule::getRuleKey)
- .containsExactlyInAnyOrder(
- tuple(rule1.getUuid(), rule1.getKey()),
- tuple(rule2.getUuid(), rule2.getKey()));
- }
-
- @Test
- public void make_single_profile_of_a_language_default_even_if_not_flagged_as_so() {
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
- new DummyProfileDefinition("foo", "foo1", false));
-
- underTest.initialize();
-
- assertThat(underTest.get())
- .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault)
- .containsExactly(tuple(FOO_LANGUAGE.getKey(), true));
- }
-
- @Test
- public void make_single_profile_of_a_language_default_even_if_flagged_as_so() {
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
- new DummyProfileDefinition("foo", "foo1", true));
-
- underTest.initialize();
-
- assertThat(underTest.get())
- .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault)
- .containsExactly(tuple(FOO_LANGUAGE.getKey(), true));
- }
-
- @Test
- public void make_first_profile_of_a_language_default_when_none_flagged_as_so() {
- DummyProfileDefinition[] definitions = new DummyProfileDefinition[] {new DummyProfileDefinition("foo", "foo1", false), new DummyProfileDefinition("foo", "foo2", false)};
- String firstName = definitions[0].getName();
- String secondName = definitions[1].getName();
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definitions);
-
- underTest.initialize();
-
- assertThat(underTest.get())
- .extracting(BuiltInQProfile::getName, BuiltInQProfile::isDefault)
- .containsExactlyInAnyOrder(tuple(firstName, true), tuple(secondName, false));
- }
-
- @Test
- public void create_profile_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() {
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
- dbClient, ruleFinder, 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();
-
- assertThat(underTest.get())
- .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage()))
- .filteredOn(BuiltInQProfile::isDefault)
- .extracting(BuiltInQProfile::getName)
- .containsExactly(SONAR_WAY_QP_NAME);
- }
-
- @Test
- public void do_not_create_Sonar_Way_as_default_if_other_profile_is_defined_as_default() {
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
- dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
- new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", true));
-
- underTest.initialize();
-
- assertThat(underTest.get())
- .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage()))
- .filteredOn(BuiltInQProfile::isDefault)
- .extracting(BuiltInQProfile::getName)
- .containsExactly("goo");
- }
-
- @Test
- public void match_Sonar_Way_default_with_case_sensitivity() {
- String sonarWayInOtherCase = SONAR_WAY_QP_NAME.toUpperCase();
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
- dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
- new DummyProfileDefinition("foo", "goo", false), new DummyProfileDefinition("foo", sonarWayInOtherCase, false));
-
- underTest.initialize();
-
- assertThat(underTest.get())
- .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage()))
- .filteredOn(BuiltInQProfile::isDefault)
- .extracting(BuiltInQProfile::getName)
- .containsExactly("goo");
- }
-
- @Test
- public void create_no_BuiltInQProfile_when_all_definitions_apply_to_non_defined_languages() {
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages(),
- new DummyProfileDefinition("foo", "P1", false));
-
- underTest.initialize();
-
- assertThat(underTest.get()).isEmpty();
- }
-
- @Test
- public void create_qprofile_with_deprecated_rule() {
- RuleDefinitionDto rule1 = db.rules().insert();
- db.rules().insertDeprecatedKey(d -> d.setRuleUuid(rule1.getUuid()).setOldRepositoryKey("oldRepo").setOldRuleKey("oldKey"));
- RuleDefinitionDto rule2 = db.rules().insert();
- DummyProfileDefinition definition = new DummyProfileDefinition("foo", "foo", false,
- asList(RuleKey.of("oldRepo", "oldKey"), rule2.getKey()));
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definition);
-
- underTest.initialize();
-
- assertThat(underTest.get())
- .extracting(BuiltInQProfile::getName)
- .containsExactlyInAnyOrder("foo");
- assertThat(underTest.get().get(0).getActiveRules())
- .extracting(ActiveRule::getRuleUuid, ActiveRule::getRuleKey)
- .containsExactlyInAnyOrder(
- tuple(rule1.getUuid(), rule1.getKey()),
- tuple(rule2.getUuid(), rule2.getKey()));
- }
-
- @Test
- public void fail_with_ISE_when_rule_does_not_exist() {
- DummyProfileDefinition[] definitions = new DummyProfileDefinition[] {new DummyProfileDefinition("foo", "foo", false, singletonList(EXTERNAL_XOO))};
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definitions);
-
-
- }
-
- @Test
- public void fail_with_ISE_when_two_profiles_with_different_name_are_default_for_the_same_language() {
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
- new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo2", true));
-
- assertThatThrownBy(underTest::initialize)
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Several Quality profiles are flagged as default for the language foo: [foo1, foo2]");
- }
-
- @Test
- public void get_throws_ISE_if_called_before_initialize() {
- BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages());
-
- assertThatThrownBy(underTest::get)
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("initialize must be called first");
- }
-
- @Test
- public void initialize_throws_ISE_if_called_twice() {
- BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages());
- underTest.initialize();
-
- assertThatThrownBy(underTest::initialize)
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("initialize must be called only once");
- }
-
- @Test
- public void initialize_throws_ISE_if_language_has_no_builtin_qp() {
- BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages(FOO_LANGUAGE));
-
- assertThatThrownBy(underTest::initialize)
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("The following languages have no built-in quality profiles: foo");
- }
-
- private static final class DummyProfileDefinition implements BuiltInQualityProfilesDefinition {
- private final String language;
- private final String name;
- private final boolean defaultProfile;
- private final List<RuleKey> activeRuleKeys;
-
- private DummyProfileDefinition(String language, String name, boolean defaultProfile, List<RuleKey> activeRuleKeys) {
- this.language = language;
- this.name = name;
- this.defaultProfile = defaultProfile;
- this.activeRuleKeys = activeRuleKeys;
- }
-
- private DummyProfileDefinition(String language, String name, boolean defaultProfile) {
- this(language, name, defaultProfile, emptyList());
- }
-
- @Override
- public void define(Context context) {
- NewBuiltInQualityProfile builtInQualityProfile = context.createBuiltInQualityProfile(name, language);
- activeRuleKeys.forEach(activeRuleKey -> builtInQualityProfile.activateRule(activeRuleKey.repository(), activeRuleKey.rule()));
- builtInQualityProfile.setDefault(defaultProfile);
- builtInQualityProfile.done();
- }
-
- String getName() {
- return name;
- }
-
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.assertj.core.groups.Tuple;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.OrgActiveRuleDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.qualityprofile.RulesProfileDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.util.IntegerTypeValidation;
-import org.sonar.server.util.StringTypeValidation;
-import org.sonar.server.util.TypeValidations;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.sonar.api.rules.RulePriority.BLOCKER;
-import static org.sonar.api.rules.RulePriority.CRITICAL;
-import static org.sonar.api.rules.RulePriority.MAJOR;
-import static org.sonar.api.rules.RulePriority.MINOR;
-import static org.sonar.db.qualityprofile.QualityProfileTesting.newRuleProfileDto;
-import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED;
-
-public class BuiltInQProfileUpdateImplTest {
-
- private static final long NOW = 1_000;
- private static final long PAST = NOW - 100;
-
- @Rule
- public BuiltInQProfileRepositoryRule builtInProfileRepository = new BuiltInQProfileRepositoryRule();
- @Rule
- public DbTester db = DbTester.create();
- @Rule
- public UserSessionRule userSession = UserSessionRule.standalone();
- private System2 system2 = new TestSystem2().setNow(NOW);
- private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
- private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation()));
- private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
- private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession);
-
- private BuiltInQProfileUpdateImpl underTest = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivator, activeRuleIndexer,
- qualityProfileChangeEventService);
-
- private RulesProfileDto persistedProfile;
-
- @Before
- public void setUp() {
- persistedProfile = newRuleProfileDto(rp -> rp
- .setIsBuiltIn(true)
- .setLanguage("xoo")
- .setRulesUpdatedAt(null));
- db.getDbClient().qualityProfileDao().insert(db.getSession(), persistedProfile);
- db.commit();
- }
-
- @Test
- public void activate_new_rules() {
- RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
- RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
- newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
- newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2);
-
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
-
- List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThat(activeRules).hasSize(2);
- assertThatRuleIsNewlyActivated(activeRules, rule1, CRITICAL);
- assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR);
- assertThatProfileIsMarkedAsUpdated(persistedProfile);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- @Test
- public void already_activated_rule_is_updated_in_case_of_differences() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule);
-
- activateRuleInDb(persistedProfile, rule, BLOCKER);
-
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
-
- List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThat(activeRules).hasSize(1);
- assertThatRuleIsUpdated(activeRules, rule, CRITICAL);
- assertThatProfileIsMarkedAsUpdated(persistedProfile);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- @Test
- public void already_activated_rule_is_not_touched_if_no_differences() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule);
-
- activateRuleInDb(persistedProfile, rule, CRITICAL);
-
- underTest.update(db.getSession(), builtIn, persistedProfile);
-
- List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThat(activeRules).hasSize(1);
- assertThatRuleIsUntouched(activeRules, rule, CRITICAL);
- assertThatProfileIsNotMarkedAsUpdated(persistedProfile);
- verifyNoInteractions(qualityProfileChangeEventService);
- }
-
- @Test
- public void deactivate_rule_that_is_not_in_built_in_definition_anymore() {
- RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
- RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
- newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2);
-
- // built-in definition contains only rule2
- // so rule1 must be deactivated
- activateRuleInDb(persistedProfile, rule1, CRITICAL);
-
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
-
- List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThat(activeRules).hasSize(1);
- assertThatRuleIsDeactivated(activeRules, rule1);
- assertThatProfileIsMarkedAsUpdated(persistedProfile);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- @Test
- public void activate_deactivate_and_update_three_rules_at_the_same_time() {
- RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
- RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
- RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("xoo"));
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
- newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
- newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2);
-
- // rule1 must be updated (blocker to critical)
- // rule2 must be activated
- // rule3 must be deactivated
- activateRuleInDb(persistedProfile, rule1, BLOCKER);
- activateRuleInDb(persistedProfile, rule3, BLOCKER);
-
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
-
- List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThat(activeRules).hasSize(2);
- assertThatRuleIsUpdated(activeRules, rule1, CRITICAL);
- assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR);
- assertThatRuleIsDeactivated(activeRules, rule3);
- assertThatProfileIsMarkedAsUpdated(persistedProfile);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- // SONAR-10473
- @Test
- public void activate_rule_on_built_in_profile_resets_severity_to_default_if_not_overridden() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setSeverity(Severity.MAJOR).setLanguage("xoo"));
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule);
- underTest.update(db.getSession(), builtIn, persistedProfile);
-
- List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThatRuleIsNewlyActivated(activeRules, rule, MAJOR);
-
- // emulate an upgrade of analyzer that changes the default severity of the rule
- rule.setSeverity(Severity.MINOR);
- db.rules().update(rule);
-
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
- activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThatRuleIsNewlyActivated(activeRules, rule, MINOR);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- @Test
- public void activate_rule_on_built_in_profile_resets_params_to_default_if_not_overridden() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
- RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", rule.getLanguage());
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(newQp.language(), newQp.name()), rule);
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
-
- List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThat(activeRules).hasSize(1);
- assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "10"));
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
-
- // emulate an upgrade of analyzer that changes the default value of parameter min
- ruleParam.setDefaultValue("20");
- db.getDbClient().ruleDao().updateRuleParam(db.getSession(), rule, ruleParam);
-
- changes = underTest.update(db.getSession(), builtIn, persistedProfile);
- activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
- assertThat(activeRules).hasSize(1);
- assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "20"));
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- @Test
- public void propagate_activation_to_descendant_profiles() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
-
- QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
- QProfileDto childProfile = createChildProfile(profile);
- QProfileDto grandchildProfile = createChildProfile(childProfile);
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
-
- assertThat(changes).hasSize(3);
- assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, emptyMap());
- assertThatRuleIsActivated(childProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap());
- assertThatRuleIsActivated(grandchildProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap());
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- // SONAR-14559
- @Test
- public void propagate_rule_update_to_descendant_active_rule() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
-
- QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
- activateRuleInDb(RulesProfileDto.from(parentProfile), rule, RulePriority.valueOf(Severity.MINOR), null);
-
- QProfileDto childProfile = createChildProfile(parentProfile);
- activateRuleInDb(RulesProfileDto.from(childProfile), rule, RulePriority.valueOf(Severity.MINOR), INHERITED);
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(parentProfile.getName(), parentProfile.getLanguage());
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(parentProfile.getLanguage(), parentProfile.getName()), rule);
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(parentProfile));
-
- assertThat(changes).hasSize(2);
-
- List<ActiveRuleDto> parentActiveRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), RulesProfileDto.from(parentProfile));
- assertThatRuleIsUpdated(parentActiveRules, rule, RulePriority.BLOCKER, null);
-
- List<ActiveRuleDto> childActiveRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), RulesProfileDto.from(childProfile));
- assertThatRuleIsUpdated(childActiveRules, rule, RulePriority.BLOCKER, INHERITED);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- @Test
- public void propagate_rule_param_update_to_descendant_active_rule_params() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
- RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
-
- QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
- ActiveRuleDto parentActiveRuleDto = activateRuleInDb(RulesProfileDto.from(parentProfile), rule,
- RulePriority.valueOf(Severity.MINOR), null);
- activateRuleParamInDb(parentActiveRuleDto, ruleParam, "20");
-
- QProfileDto childProfile = createChildProfile(parentProfile);
- ActiveRuleDto childActiveRuleDto = activateRuleInDb(RulesProfileDto.from(childProfile), rule,
- RulePriority.valueOf(Severity.MINOR), INHERITED);
- activateRuleParamInDb(childActiveRuleDto, ruleParam, "20");
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(parentProfile.getName(), parentProfile.getLanguage());
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
- newQp.done();
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(parentProfile.getLanguage(), parentProfile.getName()), rule);
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(parentProfile));
-
- assertThat(changes).hasSize(2);
-
- assertThatRuleHasParams(db, parentActiveRuleDto, tuple("min", "10"));
- assertThatRuleHasParams(db, childActiveRuleDto, tuple("min", "10"));
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
- }
-
- @Test
- public void do_not_load_descendants_if_no_changes() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
-
- QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
- QProfileDto childProfile = createChildProfile(profile);
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
- newQp.done();
-
- // first run
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
- assertThat(changes).hasSize(2).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
-
- // second run, without any input changes
- RuleActivator ruleActivatorWithoutDescendants = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession) {
- @Override
- DescendantProfilesSupplier createDescendantProfilesSupplier(DbSession dbSession) {
- return (profiles, ruleIds) -> {
- throw new IllegalStateException("BOOM - descendants should not be loaded");
- };
- }
- };
- changes = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivatorWithoutDescendants, activeRuleIndexer, qualityProfileChangeEventService)
- .update(db.getSession(), builtIn, RulesProfileDto.from(profile));
- assertThat(changes).isEmpty();
- verifyNoMoreInteractions(qualityProfileChangeEventService);
- }
-
- @Test
- public void propagate_deactivation_to_descendant_profiles() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
-
- QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
- QProfileDto childProfile = createChildProfile(profile);
- QProfileDto grandChildProfile = createChildProfile(childProfile);
-
- BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
- newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
- newQp.done();
-
- // first run to activate the rule
- BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
- List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
- assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
-
- // second run to deactivate the rule
- context = new BuiltInQualityProfilesDefinition.Context();
- NewBuiltInQualityProfile updatedQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
- updatedQp.done();
- builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
- changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
- assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.DEACTIVATED);
- verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
-
- assertThatRuleIsDeactivated(profile, rule);
- assertThatRuleIsDeactivated(childProfile, rule);
- assertThatRuleIsDeactivated(grandChildProfile, rule);
- }
-
- private QProfileDto createChildProfile(QProfileDto parent) {
- return db.qualityProfiles().insert(p -> p
- .setLanguage(parent.getLanguage())
- .setParentKee(parent.getKee())
- .setName("Child of " + parent.getName()))
- .setIsBuiltIn(false);
- }
-
- private void assertThatRuleIsActivated(QProfileDto profile, RuleDefinitionDto rule, @Nullable List<ActiveRuleChange> changes,
- String expectedSeverity, @Nullable ActiveRuleInheritance expectedInheritance, Map<String, String> expectedParams) {
- OrgActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), profile)
- .stream()
- .filter(ar -> ar.getRuleKey().equals(rule.getKey()))
- .findFirst()
- .orElseThrow(IllegalStateException::new);
-
- assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity);
- assertThat(activeRule.getInheritance()).isEqualTo(expectedInheritance != null ? expectedInheritance.name() : null);
-
- List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(db.getSession(), activeRule.getUuid());
- assertThat(params).hasSize(expectedParams.size());
-
- if (changes != null) {
- ActiveRuleChange change = changes.stream()
- .filter(c -> c.getActiveRule().getUuid().equals(activeRule.getUuid()))
- .findFirst().orElseThrow(IllegalStateException::new);
- assertThat(change.getInheritance()).isEqualTo(expectedInheritance);
- assertThat(change.getSeverity()).isEqualTo(expectedSeverity);
- assertThat(change.getType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED);
- }
- }
-
- private static void assertThatRuleHasParams(DbTester db, ActiveRuleDto activeRule, Tuple... expectedParams) {
- assertThat(db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(db.getSession(), activeRule.getUuid()))
- .extracting(ActiveRuleParamDto::getKey, ActiveRuleParamDto::getValue)
- .containsExactlyInAnyOrder(expectedParams);
- }
-
- private static void assertThatRuleIsNewlyActivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
- ActiveRuleDto activeRule = findRule(activeRules, rule).get();
-
- assertThat(activeRule.getInheritance()).isNull();
- assertThat(activeRule.getSeverityString()).isEqualTo(severity.name());
- assertThat(activeRule.getCreatedAt()).isEqualTo(NOW);
- assertThat(activeRule.getUpdatedAt()).isEqualTo(NOW);
- }
-
- private static void assertThatRuleIsUpdated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance expectedInheritance) {
- ActiveRuleDto activeRule = findRule(activeRules, rule).get();
-
- if (expectedInheritance != null) {
- assertThat(activeRule.getInheritance()).isEqualTo(expectedInheritance.name());
- } else {
- assertThat(activeRule.getInheritance()).isNull();
- }
- assertThat(activeRule.getSeverityString()).isEqualTo(severity.name());
- assertThat(activeRule.getCreatedAt()).isEqualTo(PAST);
- assertThat(activeRule.getUpdatedAt()).isEqualTo(NOW);
- }
-
- private static void assertThatRuleIsUpdated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
- assertThatRuleIsUpdated(activeRules, rule, severity, null);
- }
-
- private static void assertThatRuleIsUpdated(ActiveRuleDto activeRules, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance expectedInheritance) {
- assertThatRuleIsUpdated(singletonList(activeRules), rule, severity, expectedInheritance);
- }
-
- private static void assertThatRuleIsUntouched(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
- ActiveRuleDto activeRule = findRule(activeRules, rule).get();
-
- assertThat(activeRule.getInheritance()).isNull();
- assertThat(activeRule.getSeverityString()).isEqualTo(severity.name());
- assertThat(activeRule.getCreatedAt()).isEqualTo(PAST);
- assertThat(activeRule.getUpdatedAt()).isEqualTo(PAST);
- }
-
- private void assertThatRuleIsDeactivated(QProfileDto profile, RuleDefinitionDto rule) {
- Collection<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRulesAndRuleProfileUuids(
- db.getSession(), singletonList(rule.getUuid()), singletonList(profile.getRulesProfileUuid()));
- assertThat(activeRules).isEmpty();
- }
-
- private static void assertThatRuleIsDeactivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule) {
- assertThat(findRule(activeRules, rule)).isEmpty();
- }
-
- private void assertThatProfileIsMarkedAsUpdated(RulesProfileDto dto) {
- RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession())
- .stream()
- .filter(p -> p.getUuid().equals(dto.getUuid()))
- .findFirst()
- .get();
- assertThat(reloaded.getRulesUpdatedAt()).isNotEmpty();
- }
-
- private void assertThatProfileIsNotMarkedAsUpdated(RulesProfileDto dto) {
- RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession())
- .stream()
- .filter(p -> p.getUuid().equals(dto.getUuid()))
- .findFirst()
- .get();
- assertThat(reloaded.getRulesUpdatedAt()).isNull();
- }
-
- private static Optional<ActiveRuleDto> findRule(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule) {
- return activeRules.stream()
- .filter(ar -> ar.getRuleKey().equals(rule.getKey()))
- .findFirst();
- }
-
- private ActiveRuleDto activateRuleInDb(RulesProfileDto profile, RuleDefinitionDto rule, RulePriority severity) {
- return activateRuleInDb(profile, rule, severity, null);
- }
-
- private ActiveRuleDto activateRuleInDb(RulesProfileDto ruleProfile, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance inheritance) {
- ActiveRuleDto dto = new ActiveRuleDto()
- .setKey(ActiveRuleKey.of(ruleProfile, RuleKey.of(rule.getRepositoryKey(), rule.getRuleKey())))
- .setProfileUuid(ruleProfile.getUuid())
- .setSeverity(severity.name())
- .setRuleUuid(rule.getUuid())
- .setInheritance(inheritance != null ? inheritance.name() : null)
- .setCreatedAt(PAST)
- .setUpdatedAt(PAST);
- db.getDbClient().activeRuleDao().insert(db.getSession(), dto);
- db.commit();
- return dto;
- }
-
- private void activateRuleParamInDb(ActiveRuleDto activeRuleDto, RuleParamDto ruleParamDto, String value) {
- ActiveRuleParamDto dto = new ActiveRuleParamDto()
- .setActiveRuleUuid(activeRuleDto.getUuid())
- .setRulesParameterUuid(ruleParamDto.getUuid())
- .setKey(ruleParamDto.getName())
- .setValue(value);
- db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRuleDto, dto);
- db.commit();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualityprofile;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-import org.assertj.core.groups.Tuple;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.core.util.Uuids;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.server.notification.NotificationManager;
-import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile;
-
-import static java.util.Arrays.asList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.sonar.core.config.CorePropertyDefinitions.DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES;
-import static org.sonar.server.language.LanguageTesting.newLanguage;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED;
-
-public class BuiltInQualityProfilesUpdateListenerTest {
-
- private NotificationManager notificationManager = mock(NotificationManager.class);
- private MapSettings settings = new MapSettings();
-
- @Test
- public void add_profile_to_notification_for_added_rules() {
- enableNotificationInGlobalSettings();
- Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
- Languages languages = new Languages();
- Tuple expectedTuple = addProfile(profiles, languages, ACTIVATED);
-
- BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
- underTest.onChange(profiles, 0, 1);
-
- ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
- verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
- verifyNoMoreInteractions(notificationManager);
- assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
- .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules)
- .containsExactlyInAnyOrder(expectedTuple);
- }
-
- @Test
- public void add_profile_to_notification_for_updated_rules() {
- enableNotificationInGlobalSettings();
- Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
- Languages languages = new Languages();
- Tuple expectedTuple = addProfile(profiles, languages, UPDATED);
-
- BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
- underTest.onChange(profiles, 0, 1);
-
- ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
- verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
- verifyNoMoreInteractions(notificationManager);
- assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
- .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getUpdatedRules)
- .containsExactlyInAnyOrder(expectedTuple);
- }
-
- @Test
- public void add_profile_to_notification_for_removed_rules() {
- enableNotificationInGlobalSettings();
- Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
- Languages languages = new Languages();
- Tuple expectedTuple = addProfile(profiles, languages, DEACTIVATED);
-
- BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
- underTest.onChange(profiles, 0, 1);
-
- ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
- verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
- verifyNoMoreInteractions(notificationManager);
- assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
- .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getRemovedRules)
- .containsExactlyInAnyOrder(expectedTuple);
- }
-
- @Test
- public void add_multiple_profiles_to_notification() {
- enableNotificationInGlobalSettings();
- Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
- Languages languages = new Languages();
- Tuple expectedTuple1 = addProfile(profiles, languages, ACTIVATED);
- Tuple expectedTuple2 = addProfile(profiles, languages, ACTIVATED);
-
- BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
- underTest.onChange(profiles, 0, 1);
-
- ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
- verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
- verifyNoMoreInteractions(notificationManager);
- assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
- .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules)
- .containsExactlyInAnyOrder(expectedTuple1, expectedTuple2);
- }
-
- @Test
- public void add_start_and_end_dates_to_notification() {
- enableNotificationInGlobalSettings();
- Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
- Languages languages = new Languages();
- addProfile(profiles, languages, ACTIVATED);
- long startDate = 10_000_000_000L;
- long endDate = 15_000_000_000L;
-
- BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
- underTest.onChange(profiles, startDate, endDate);
-
- ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
- verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
- verifyNoMoreInteractions(notificationManager);
- assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
- .extracting(Profile::getStartDate, Profile::getEndDate)
- .containsExactlyInAnyOrder(tuple(startDate, endDate));
- }
-
- @Test
- public void avoid_notification_if_configured_in_settings() {
- settings.setProperty(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES, true);
- Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
- Languages languages = new Languages();
- addProfile(profiles, languages, ACTIVATED);
-
- BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
- underTest.onChange(profiles, 0, 1);
-
- verifyZeroInteractions(notificationManager);
- }
-
- private Tuple addProfile(Multimap<QProfileName, ActiveRuleChange> profiles, Languages languages, ActiveRuleChange.Type type) {
- String profileName = randomLowerCaseText();
- String ruleUuid1 = Uuids.createFast();
- String ruleUuid2 = Uuids.createFast();
- Language language = newLanguage(randomLowerCaseText(), randomLowerCaseText());
- languages.add(language);
- profiles.putAll(new QProfileName(language.getKey(), profileName),
- asList(new ActiveRuleChange(
- type,
- ActiveRuleKey.parse("qp:repo:rule1"), new RuleDefinitionDto().setUuid(ruleUuid1)),
- new ActiveRuleChange(type, ActiveRuleKey.parse("qp:repo:rule2"), new RuleDefinitionDto().setUuid(ruleUuid2))));
- return tuple(profileName, language.getKey(), language.getName(), 2);
- }
-
- private static String randomLowerCaseText() {
- return randomAlphanumeric(20).toLowerCase();
- }
-
- private void enableNotificationInGlobalSettings() {
- settings.setProperty(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES, false);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.ImmutableMap;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.qualityprofile.RulesProfileDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.qualityprofile.DescendantProfilesSupplier.Result;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.util.IntegerTypeValidation;
-import org.sonar.server.util.StringTypeValidation;
-import org.sonar.server.util.TypeValidations;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertThrows;
-import static org.mockito.Mockito.mock;
-import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED;
-import static org.sonar.server.qualityprofile.ActiveRuleInheritance.OVERRIDES;
-
-/**
- * Class org.sonar.server.qualityprofile.RuleActivator is mostly covered in
- * org.sonar.server.qualityprofile.BuiltInQProfileUpdateImplTest
- */
-public class RuleActivatorTest {
- @Rule
- public final DbTester db = DbTester.create();
-
- @Rule
- public final UserSessionRule userSession = UserSessionRule.standalone();
-
- private static final long NOW = 1_000;
- private static final long PAST = NOW - 100;
- private final System2 system2 = new TestSystem2().setNow(NOW);
- private final TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation()));
-
- private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
- private final RuleActivator underTest = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession);
-
- @Test
- public void reset_overridden_active_rule() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
- RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
-
- QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
- ActiveRuleDto parentActiveRuleDto = activateRuleInDb(RulesProfileDto.from(parentProfile), rule,
- RulePriority.valueOf(Severity.BLOCKER), null);
- ActiveRuleParamDto parentActiveRuleParam = activateRuleParamInDb(parentActiveRuleDto, ruleParam, "10");
-
- QProfileDto childProfile = createChildProfile(parentProfile);
- ActiveRuleDto childActiveRuleDto = activateRuleInDb(RulesProfileDto.from(childProfile), rule,
- RulePriority.valueOf(Severity.MINOR), OVERRIDES);
- ActiveRuleParamDto childActiveRuleParam = activateRuleParamInDb(childActiveRuleDto, ruleParam, "15");
-
- DbSession session = db.getSession();
- RuleActivation resetRequest = RuleActivation.createReset(rule.getUuid());
- RuleActivationContext context = new RuleActivationContext.Builder()
- .setProfiles(asList(parentProfile, childProfile))
- .setBaseProfile(RulesProfileDto.from(childProfile))
- .setDate(NOW)
- .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
- .setRules(singletonList(rule))
- .setRuleParams(singletonList(ruleParam))
- .setActiveRules(asList(parentActiveRuleDto, childActiveRuleDto))
- .setActiveRuleParams(asList(parentActiveRuleParam, childActiveRuleParam))
- .build();
-
- List<ActiveRuleChange> result = underTest.activate(session, resetRequest, context);
-
- assertThat(result).hasSize(1);
- assertThat(result.get(0).getParameters()).containsEntry("min", "10");
- assertThat(result.get(0).getSeverity()).isEqualTo(Severity.BLOCKER);
- assertThat(result.get(0).getInheritance()).isEqualTo(ActiveRuleInheritance.INHERITED);
- }
-
- @Test
- public void request_new_severity_and_param_for_child_rule() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
- RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
-
- QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
- ActiveRuleDto parentActiveRuleDto = activateRuleInDb(RulesProfileDto.from(parentProfile), rule,
- RulePriority.valueOf(Severity.BLOCKER), null);
- ActiveRuleParamDto parentActiveRuleParam = activateRuleParamInDb(parentActiveRuleDto, ruleParam, "10");
-
- QProfileDto childProfile = createChildProfile(parentProfile);
- ActiveRuleDto childActiveRuleDto = activateRuleInDb(RulesProfileDto.from(childProfile), rule,
- RulePriority.valueOf(Severity.BLOCKER), INHERITED);
- ActiveRuleParamDto childActiveRuleParam = activateRuleParamInDb(childActiveRuleDto, ruleParam, "10");
-
- DbSession session = db.getSession();
- RuleActivation resetRequest = RuleActivation.create(rule.getUuid(), Severity.MINOR, ImmutableMap.of("min", "15"));
- RuleActivationContext context = new RuleActivationContext.Builder()
- .setProfiles(asList(parentProfile, childProfile))
- .setBaseProfile(RulesProfileDto.from(childProfile))
- .setDate(NOW)
- .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
- .setRules(singletonList(rule))
- .setRuleParams(singletonList(ruleParam))
- .setActiveRules(asList(parentActiveRuleDto, childActiveRuleDto))
- .setActiveRuleParams(asList(parentActiveRuleParam, childActiveRuleParam))
- .build();
-
- List<ActiveRuleChange> result = underTest.activate(session, resetRequest, context);
-
- assertThat(result).hasSize(1);
- assertThat(result.get(0).getParameters()).containsEntry("min", "15");
- assertThat(result.get(0).getSeverity()).isEqualTo(Severity.MINOR);
- assertThat(result.get(0).getInheritance()).isEqualTo(OVERRIDES);
- }
-
- @Test
- public void set_severity_and_param_for_child_rule_when_activating() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
- RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
-
- QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
-
- QProfileDto childProfile = createChildProfile(parentProfile);
-
- DbSession session = db.getSession();
- RuleActivation resetRequest = RuleActivation.create(rule.getUuid());
- RuleActivationContext context = new RuleActivationContext.Builder()
- .setProfiles(asList(parentProfile, childProfile))
- .setBaseProfile(RulesProfileDto.from(childProfile))
- .setDate(NOW)
- .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
- .setRules(singletonList(rule))
- .setRuleParams(singletonList(ruleParam))
- .setActiveRules(emptyList())
- .setActiveRuleParams(emptyList())
- .build();
-
- List<ActiveRuleChange> result = underTest.activate(session, resetRequest, context);
-
- assertThat(result).hasSize(1);
- assertThat(result.get(0).getParameters()).containsEntry("min", "10");
- assertThat(result.get(0).getSeverity()).isEqualTo(Severity.BLOCKER);
- assertThat(result.get(0).getInheritance()).isNull();
- }
-
- @Test
- public void fail_if_rule_language_doesnt_match_qp() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")
- .setRepositoryKey("repo")
- .setRuleKey("rule")
- .setSeverity(Severity.BLOCKER));
- QProfileDto qp = db.qualityProfiles().insert(p -> p.setLanguage("xoo2").setKee("qp").setIsBuiltIn(true));
-
- DbSession session = db.getSession();
- RuleActivation resetRequest = RuleActivation.create(rule.getUuid());
- RuleActivationContext context = new RuleActivationContext.Builder()
- .setProfiles(singletonList(qp))
- .setBaseProfile(RulesProfileDto.from(qp))
- .setDate(NOW)
- .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
- .setRules(singletonList(rule))
- .setRuleParams(emptyList())
- .setActiveRules(emptyList())
- .setActiveRuleParams(emptyList())
- .build();
-
-
- assertThrows("xoo rule repo:rule cannot be activated on xoo2 profile qp", BadRequestException.class,
- () -> underTest.activate(session, resetRequest, context));
- }
-
-
- private ActiveRuleDto activateRuleInDb(RulesProfileDto ruleProfile, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance inheritance) {
- ActiveRuleDto dto = new ActiveRuleDto()
- .setKey(ActiveRuleKey.of(ruleProfile, RuleKey.of(rule.getRepositoryKey(), rule.getRuleKey())))
- .setProfileUuid(ruleProfile.getUuid())
- .setSeverity(severity.name())
- .setRuleUuid(rule.getUuid())
- .setInheritance(inheritance != null ? inheritance.name() : null)
- .setCreatedAt(PAST)
- .setUpdatedAt(PAST);
- db.getDbClient().activeRuleDao().insert(db.getSession(), dto);
- db.commit();
- return dto;
- }
-
- private ActiveRuleParamDto activateRuleParamInDb(ActiveRuleDto activeRuleDto, RuleParamDto ruleParamDto, String value) {
- ActiveRuleParamDto dto = new ActiveRuleParamDto()
- .setActiveRuleUuid(activeRuleDto.getUuid())
- .setRulesParameterUuid(ruleParamDto.getUuid())
- .setKey(ruleParamDto.getName())
- .setValue(value);
- db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRuleDto, dto);
- db.commit();
- return dto;
- }
-
- private QProfileDto createChildProfile(QProfileDto parent) {
- return db.qualityProfiles().insert(p -> p
- .setLanguage(parent.getLanguage())
- .setParentKee(parent.getKee())
- .setName("Child of " + parent.getName()))
- .setIsBuiltIn(false);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.Preconditions;
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import org.junit.rules.ExternalResource;
-import org.sonar.api.resources.Language;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.rule.RuleDefinitionDto;
-
-import static com.google.common.base.Preconditions.checkState;
-
-public class BuiltInQProfileRepositoryRule extends ExternalResource implements BuiltInQProfileRepository {
- private boolean initializeCalled = false;
- private List<BuiltInQProfile> profiles = new ArrayList<>();
-
- @Override
- protected void before() {
- this.initializeCalled = false;
- this.profiles.clear();
- }
-
- @Override
- public void initialize() {
- checkState(!initializeCalled, "initialize must be called only once");
- this.initializeCalled = true;
- }
-
- @Override
- public List<BuiltInQProfile> get() {
- checkState(initializeCalled, "initialize must be called first");
-
- return ImmutableList.copyOf(profiles);
- }
-
- public boolean isInitialized() {
- return initializeCalled;
- }
-
- public BuiltInQProfile add(Language language, String profileName) {
- return add(language, profileName, false);
- }
-
- public BuiltInQProfile add(Language language, String profileName, boolean isDefault) {
- return add(language, profileName, isDefault, new BuiltInQProfile.ActiveRule[0]);
- }
-
- public BuiltInQProfile add(Language language, String profileName, boolean isDefault, BuiltInQProfile.ActiveRule... rules) {
- BuiltInQProfile builtIn = create(language, profileName, isDefault, rules);
- profiles.add(builtIn);
- return builtIn;
- }
-
- public BuiltInQProfile create(Language language, String profileName, boolean isDefault, BuiltInQProfile.ActiveRule... rules) {
- BuiltInQProfile.Builder builder = new BuiltInQProfile.Builder()
- .setLanguage(language.getKey())
- .setName(profileName)
- .setDeclaredDefault(isDefault);
- Arrays.stream(rules).forEach(builder::addRule);
- return builder.build();
- }
-
- public BuiltInQProfile create(BuiltInQualityProfilesDefinition.BuiltInQualityProfile api, RuleDefinitionDto... rules) {
- BuiltInQProfile.Builder builder = new BuiltInQProfile.Builder()
- .setLanguage(api.language())
- .setName(api.name())
- .setDeclaredDefault(api.isDefault());
- Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = Arrays.stream(rules)
- .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getKey));
- api.rules().forEach(rule -> {
- RuleKey ruleKey = RuleKey.of(rule.repoKey(), rule.ruleKey());
- RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey);
- Preconditions.checkState(ruleDefinition != null, "Rule '%s' not found", ruleKey);
- builder.addRule(new BuiltInQProfile.ActiveRule(ruleDefinition.getUuid(), rule));
- });
- return builder
- .build();
- }
-}
dependencies {
compile 'javax.servlet:javax.servlet-api'
- compile project(':server:sonar-webserver-auth')
+ compile 'org.json:json'
+
compile project(':server:sonar-webserver-ws')
- compile project(':server:sonar-webserver-webapi')
+ compile project(':server:sonar-webserver-auth')
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
private static final Logger LOG = Loggers.get(ServerPushClient.class);
private static final int DEFAULT_HEARTBEAT_PERIOD = 60;
-
-
-
- private static final byte[] CRLF = new byte[] {'\r', '\n'};
- private static final byte[] DATA_END = new byte[] {'\n', '\n'};
- private static final byte[] DATA_FIELD = "data: ".getBytes(StandardCharsets.UTF_8);
-
-
-
-
-
protected final AsyncContext asyncContext;
private final ScheduledExecutorService executorService;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.qualityprofile;
+
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.util.RuleActivationListener;
+import org.sonar.core.util.RuleSetChangeEvent;
+import org.sonar.process.cluster.hz.HazelcastMember;
+
+@ServerSide
+public class DistributedRuleActivatorEventsDistributor implements RuleActivatorEventsDistributor {
+
+ private HazelcastMember hazelcastMember;
+
+ public DistributedRuleActivatorEventsDistributor(HazelcastMember hazelcastMember) {
+ this.hazelcastMember = hazelcastMember;
+ }
+
+ @Override
+ public void subscribe(RuleActivationListener listener) {
+ hazelcastMember.subscribeRuleActivationTopic(listener);
+ }
+
+ @Override
+ public void pushEvent(RuleSetChangeEvent event) {
+ hazelcastMember.publishEvent(event);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.qualityprofile;
+
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+
+public interface QualityProfileChangeEventService {
+
+ void publishRuleActivationToSonarLintClients(ProjectDto project, @Nullable QProfileDto activatedProfile, @Nullable QProfileDto deactivatedProfile);
+
+ void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.qualityprofile;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.util.ParamChange;
+import org.sonar.core.util.RuleChange;
+import org.sonar.core.util.RuleSetChangeEvent;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.ProjectQprofileAssociationDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.rule.index.RuleIndex;
+import org.sonar.server.rule.index.RuleQuery;
+
+@ServerSide
+public class QualityProfileChangeEventServiceImpl implements QualityProfileChangeEventService {
+
+ private final DbClient dbClient;
+ private final RuleIndex ruleIndex;
+ private final RuleActivatorEventsDistributor eventsDistributor;
+
+ public QualityProfileChangeEventServiceImpl(DbClient dbClient, RuleIndex ruleIndex, RuleActivatorEventsDistributor eventsDistributor) {
+ this.dbClient = dbClient;
+ this.ruleIndex = ruleIndex;
+ this.eventsDistributor = eventsDistributor;
+ }
+
+ @Override
+ public void publishRuleActivationToSonarLintClients(ProjectDto project, @Nullable QProfileDto activatedProfile, @Nullable QProfileDto deactivatedProfile) {
+ List<RuleChange> activatedRules = new ArrayList<>();
+ List<RuleChange> deactivatedRules = new ArrayList<>();
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+
+ if (activatedProfile != null) {
+ RuleQuery query = new RuleQuery().setQProfile(activatedProfile).setActivation(true).setIncludeExternal(true);
+ Iterator<String> searchIdResult = ruleIndex.searchAll(query);
+ List<String> uuids = new ArrayList<>();
+ while (searchIdResult.hasNext()) {
+ uuids.add(searchIdResult.next());
+ }
+
+ List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, uuids);
+ Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, uuids)
+ .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
+
+ for (RuleDto ruleDto : ruleDtos) {
+ RuleChange ruleChange = toRuleChange(ruleDto, paramsByRuleUuid);
+ activatedRules.add(ruleChange);
+ }
+ }
+
+ if (deactivatedProfile != null) {
+ RuleQuery query = new RuleQuery().setQProfile(deactivatedProfile).setActivation(true).setIncludeExternal(true);
+ // .setLanguages() ?
+ Iterator<String> searchIdResult = ruleIndex.searchAll(query);
+ List<String> uuids = new ArrayList<>();
+ while (searchIdResult.hasNext()) {
+ uuids.add(searchIdResult.next());
+ }
+
+ List<RuleDto> ruleDtos = dbClient.ruleDao().selectByUuids(dbSession, uuids);
+ Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, uuids)
+ .stream().collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
+
+ for (RuleDto ruleDto : ruleDtos) {
+ RuleChange ruleChange = toRuleChange(ruleDto, paramsByRuleUuid);
+ deactivatedRules.add(ruleChange);
+ }
+ }
+
+ }
+
+ if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
+ return;
+ }
+
+ RuleSetChangeEvent event = new RuleSetChangeEvent(new String[]{project.getKey()}, activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new RuleChange[0]));
+ eventsDistributor.pushEvent(event);
+ }
+
+ @NotNull
+ private RuleChange toRuleChange(RuleDto ruleDto, Map<String, List<ActiveRuleParamDto>> paramsByRuleUuid) {
+ RuleChange ruleChange = new RuleChange();
+ ruleChange.setKey(ruleDto.getRuleKey());
+ ruleChange.setLanguage(ruleDto.getLanguage());
+ ruleChange.setSeverity(ruleDto.getSeverityString());
+
+ List<ParamChange> paramChanges = new ArrayList<>();
+ List<ActiveRuleParamDto> activeRuleParamDtos = paramsByRuleUuid.getOrDefault(ruleDto.getUuid(), new ArrayList<>());
+ for (ActiveRuleParamDto activeRuleParam : activeRuleParamDtos) {
+ paramChanges.add(new ParamChange(activeRuleParam.getKey(), activeRuleParam.getValue()));
+ }
+ ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
+
+ String templateUuid = ruleDto.getTemplateUuid();
+ if (templateUuid != null && !"".equals(templateUuid)) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
+ .orElseThrow(() -> new IllegalStateException(String.format("unknow Template Rule '%s'", templateUuid)));
+ ruleChange.setTemplateKey(templateRule.getRuleKey());
+ }
+ }
+
+ return ruleChange;
+ }
+
+ public void distributeRuleChangeEvent(Collection<QProfileDto> profiles, List<ActiveRuleChange> activeRuleChanges, String language) {
+ if (activeRuleChanges.isEmpty()) {
+ return;
+ }
+
+ Set<RuleChange> activatedRules = new HashSet<>();
+ Set<RuleChange> deactivatedRules = new HashSet<>();
+
+ for (ActiveRuleChange arc : activeRuleChanges) {
+ if (arc.getActiveRule() == null) {
+ continue;
+ }
+
+ RuleChange ruleChange = new RuleChange();
+ ruleChange.setKey(arc.getActiveRule().getRuleKey().rule());
+ ruleChange.setSeverity(arc.getSeverity());
+ ruleChange.setLanguage(language);
+
+ Optional<String> templateKey = templateKey(arc);
+ templateKey.ifPresent(ruleChange::setTemplateKey);
+
+ // params
+ List<ParamChange> paramChanges = new ArrayList<>();
+ for (Map.Entry<String, String> entry : arc.getParameters().entrySet()) {
+ paramChanges.add(new ParamChange(entry.getKey(), entry.getValue()));
+ }
+ ruleChange.setParams(paramChanges.toArray(new ParamChange[0]));
+
+ switch (arc.getType()) {
+ case ACTIVATED:
+ case UPDATED:
+ activatedRules.add(ruleChange);
+ break;
+ case DEACTIVATED:
+ deactivatedRules.add(ruleChange);
+ break;
+ }
+ }
+
+ Set<String> projectKeys = getProjectKeys(profiles);
+
+ if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
+ return;
+ }
+
+ RuleSetChangeEvent event = new RuleSetChangeEvent(projectKeys.toArray(new String[0]), activatedRules.toArray(new RuleChange[0]), deactivatedRules.toArray(new RuleChange[0]));
+ eventsDistributor.pushEvent(event);
+ }
+
+ private Optional<String> templateKey(ActiveRuleChange arc) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ String ruleUuid = arc.getRuleUuid();
+ RuleDto rule = dbClient.ruleDao().selectByUuid(ruleUuid, dbSession).orElseThrow(() -> new IllegalStateException("unknow rule"));
+ String templateUuid = rule.getTemplateUuid();
+ if (templateUuid != null && !"".equals(templateUuid)) {
+ RuleDto templateRule = dbClient.ruleDao().selectByUuid(templateUuid, dbSession)
+ .orElseThrow(() -> new IllegalStateException(String.format("unknow Template Rule '%s'", templateUuid)));
+ return Optional.of(templateRule.getRuleKey());
+ }
+ }
+ return Optional.empty();
+ }
+
+ private Set<String> getProjectKeys(Collection<QProfileDto> profiles) {
+ Set<String> projectKeys = new HashSet<>();
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ for (QProfileDto profileDto : profiles) {
+ List<ProjectQprofileAssociationDto> associationDtos = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profileDto, null);
+ for (ProjectQprofileAssociationDto associationDto : associationDtos) {
+ projectKeys.add(associationDto.getProjectKey());
+ }
+ }
+ return projectKeys;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.qualityprofile;
+
+import org.sonar.core.util.RuleActivationListener;
+import org.sonar.core.util.RuleSetChangeEvent;
+
+public interface RuleActivatorEventsDistributor {
+
+ void subscribe(RuleActivationListener listener);
+
+ void pushEvent(RuleSetChangeEvent event);
+}
\ No newline at end of file
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.pushapi.qualityprofile;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.util.RuleActivationListener;
+import org.sonar.core.util.RuleSetChangeEvent;
+
+@ServerSide
+public class StandaloneRuleActivatorEventsDistributor implements RuleActivatorEventsDistributor {
+
+ private List<RuleActivationListener> listeners = new ArrayList<>();
+
+ @Override
+ public void subscribe(RuleActivationListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void pushEvent(RuleSetChangeEvent event) {
+ listeners.forEach(l -> l.listen(event));
+ }
+}
import org.sonar.core.util.RuleActivationListener;
import org.sonar.core.util.RuleChange;
import org.sonar.core.util.RuleSetChangeEvent;
-import org.sonar.server.qualityprofile.RuleActivatorEventsDistributor;
+import org.sonar.server.pushapi.qualityprofile.RuleActivatorEventsDistributor;
import static java.util.Arrays.asList;
@Override
public void listen(RuleSetChangeEvent ruleChangeEvent) {
LOG.info("Generating a RuleSetChangeEvent");
- // TODO filter on languages here as well
- broadcastMessage(ruleChangeEvent, f -> f.getClientProjectKeys().isEmpty() || !Collections.disjoint(f.getClientProjectKeys(), asList(ruleChangeEvent.getProjects())));
+ broadcastMessage(ruleChangeEvent, getFilterForEvent(ruleChangeEvent));
+ }
+
+ private static Predicate<SonarLintClient> getFilterForEvent(RuleSetChangeEvent ruleChangeEvent) {
+ List<String> affectedProjects = asList(ruleChangeEvent.getProjects());
+ return f -> !Collections.disjoint(f.getClientProjectKeys(), affectedProjects) && f.getLanguages().contains(ruleChangeEvent.getLanguage());
}
- public void broadcastMessage(RuleSetChangeEvent message, Predicate<SonarLintClient> filter) {
- String jsonString = getJSONString(message);
- clients.stream().filter(filter).forEach(c -> {
+ public void broadcastMessage(RuleSetChangeEvent event, Predicate<SonarLintClient> projectsFilter) {
+ clients.stream().filter(projectsFilter).forEach(c -> {
try {
+ String jsonString = getJSONString(event);
c.writeAndFlush(jsonString);
} catch (IOException e) {
LOG.error("Unable to send message to a client: " + e.getMessage());
});
}
-
public String getJSONString(RuleSetChangeEvent ruleSetChangeEvent) {
JSONObject result = new JSONObject();
result.put("event", ruleSetChangeEvent.getEvent());
return ruleJson;
}
- private JSONObject toJson(ParamChange paramChange) {
+ private static JSONObject toJson(ParamChange paramChange) {
JSONObject param = new JSONObject();
param.put("key", paramChange.getKey());
param.put("value", paramChange.getValue());
if (!isServerSideEventsRequest(servletRequest)) {
servletResponse.stream().setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
- return; // TODO fixme this is not closing the connexion properly
+ return;
}
setHeadersForResponse(servletResponse);
*/
package org.sonar.server.pushapi.sonarlint;
+import java.io.IOException;
import java.util.Set;
import javax.servlet.AsyncContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
import org.junit.Before;
import org.junit.Test;
-import org.sonar.server.qualityprofile.StandaloneRuleActivatorEventsDistributor;
+import org.mockito.ArgumentCaptor;
+import org.sonar.core.util.RuleChange;
+import org.sonar.core.util.RuleSetChangeEvent;
+import org.sonar.server.pushapi.qualityprofile.StandaloneRuleActivatorEventsDistributor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
public class SonarLintClientsRegistryTest {
assertThat(underTest.countConnectedClients()).isEqualTo(10);
}
+ @Test
+ public void listen_givenOneClientInterestedInJavaEvents_sendOneJavaEvent() throws IOException {
+ Set<String> javaLanguageKey = Set.of("java");
+ ServletResponse response = mock(ServletResponse.class);
+ when(defaultAsyncContext.getResponse()).thenReturn(response);
+ ServletOutputStream outputStream = mock(ServletOutputStream.class);
+ when(response.getOutputStream()).thenReturn(outputStream);
+ SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, javaLanguageKey);
+
+ underTest.registerClient(sonarLintClient);
+
+ RuleChange javaRule = createRuleChange("java");
+
+ RuleChange[] activatedRules = {};
+ RuleChange[] deactivatedRules = {javaRule};
+ RuleSetChangeEvent ruleChangeEvent = new RuleSetChangeEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules);
+ underTest.listen(ruleChangeEvent);
+
+ ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
+ verify(outputStream).write(captor.capture());
+ String message = new String(captor.getValue());
+ assertThat(message).contains("java");
+ }
+
+ @Test
+ public void listen_givenOneClientInterestedInJsEventsAndJavaEventGenerated_sendZeroEvents() throws IOException {
+ Set<String> jsLanguageKey = Set.of("js");
+ ServletResponse response = mock(ServletResponse.class);
+ when(defaultAsyncContext.getResponse()).thenReturn(response);
+ ServletOutputStream outputStream = mock(ServletOutputStream.class);
+ when(response.getOutputStream()).thenReturn(outputStream);
+ SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, jsLanguageKey);
+
+ underTest.registerClient(sonarLintClient);
+
+ RuleChange javaRuleChange = createRuleChange("java");
+
+ RuleChange[] activatedRules = {};
+ RuleChange[] deactivatedRules = {javaRuleChange};
+ RuleSetChangeEvent ruleChangeEvent = new RuleSetChangeEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules);
+ underTest.listen(ruleChangeEvent);
+
+ verifyNoInteractions(outputStream);
+ }
+
+ private RuleChange createRuleChange(String language) {
+ RuleChange javaRule = new RuleChange();
+ javaRule.setLanguage(language);
+
+ return javaRule;
+ }
+
}
compile project(':server:sonar-webserver-auth')
compile project(':server:sonar-webserver-es')
compile project(':server:sonar-webserver-ws')
+ compile project(':server:sonar-webserver-pushapi')
compile project(':server:sonar-alm-client')
compile project(':sonar-scanner-protocol')
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.rule.DeprecatedRuleKeyDto;
import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
import org.sonar.server.rule.NewCustomRule;
import org.sonar.server.rule.RuleCreator;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
@ServerSide
public class QProfileCopier {
import org.sonar.db.DbSession;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
/**
* Create, delete and set as default profile.
import org.sonar.db.qualityprofile.DefaultQProfileDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import static com.google.common.base.Preconditions.checkArgument;
import org.sonar.db.qualityprofile.ActiveRuleDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.RuleActivationContext;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import static com.google.common.base.Preconditions.checkArgument;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.RuleActivationContext;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleQuery;
import org.sonar.db.qualityprofile.OrgActiveRuleDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.RuleActivationContext;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import static org.sonar.server.exceptions.BadRequestException.checkRequest;
private final ActiveRuleIndexer activeRuleIndexer;
private final QualityProfileChangeEventService qualityProfileChangeEventService;
- public QProfileTreeImpl(DbClient db, RuleActivator ruleActivator, System2 system2, ActiveRuleIndexer activeRuleIndexer, QualityProfileChangeEventService qualityProfileChangeEventService) {
+ public QProfileTreeImpl(DbClient db, RuleActivator ruleActivator, System2 system2, ActiveRuleIndexer activeRuleIndexer,
+ QualityProfileChangeEventService qualityProfileChangeEventService) {
this.db = db;
this.ruleActivator = ruleActivator;
this.system2 = system2;
import org.sonar.db.qualityprofile.DefaultQProfileDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfile;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsert;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileRepository;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdate;
+import org.sonar.server.qualityprofile.builtin.BuiltInQualityProfilesUpdateListener;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
import static java.lang.String.format;
import static java.util.stream.Collectors.toMap;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import org.sonar.api.notifications.Notification;
+
+public class BuiltInQPChangeNotification extends Notification {
+ static final String TYPE = "built-in-quality-profiles";
+
+ public BuiltInQPChangeNotification() {
+ super(TYPE);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.IntStream;
+import org.sonar.api.notifications.Notification;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.Integer.parseInt;
+import static java.lang.Long.parseLong;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+public class BuiltInQPChangeNotificationBuilder {
+
+ private static final String NUMBER_OF_PROFILES = "numberOfProfiles";
+ private static final String PROFILE_NAME = ".profileName";
+ private static final String LANGUAGE_KEY = ".languageKey";
+ private static final String LANGUAGE_NAME = ".languageName";
+ private static final String NEW_RULES = ".newRules";
+ private static final String UPDATED_RULES = ".updatedRules";
+ private static final String REMOVED_RULES = ".removedRules";
+ private static final String START_DATE = ".startDate";
+ private static final String END_DATE = ".endDate";
+
+ private final List<Profile> profiles = new ArrayList<>();
+
+ public BuiltInQPChangeNotificationBuilder addProfile(Profile profile) {
+ profiles.add(profile);
+ return this;
+ }
+
+ public BuiltInQPChangeNotification build() {
+ BuiltInQPChangeNotification notification = new BuiltInQPChangeNotification();
+ notification.setFieldValue(NUMBER_OF_PROFILES, String.valueOf(profiles.size()));
+ AtomicInteger count = new AtomicInteger();
+ profiles.forEach(profile -> {
+ int index = count.getAndIncrement();
+ notification.setFieldValue(index + PROFILE_NAME, profile.getProfileName());
+ notification.setFieldValue(index + LANGUAGE_KEY, profile.getLanguageKey());
+ notification.setFieldValue(index + LANGUAGE_NAME, profile.getLanguageName());
+ notification.setFieldValue(index + NEW_RULES, String.valueOf(profile.getNewRules()));
+ notification.setFieldValue(index + UPDATED_RULES, String.valueOf(profile.getUpdatedRules()));
+ notification.setFieldValue(index + REMOVED_RULES, String.valueOf(profile.getRemovedRules()));
+ notification.setFieldValue(index + START_DATE, String.valueOf(profile.getStartDate()));
+ notification.setFieldValue(index + END_DATE, String.valueOf(profile.getEndDate()));
+ });
+ return notification;
+ }
+
+ public static BuiltInQPChangeNotificationBuilder parse(Notification notification) {
+ checkState(BuiltInQPChangeNotification.TYPE.equals(notification.getType()),
+ "Expected notification of type %s but got %s", BuiltInQPChangeNotification.TYPE, notification.getType());
+ BuiltInQPChangeNotificationBuilder notif = new BuiltInQPChangeNotificationBuilder();
+ String numberOfProfilesText = notification.getFieldValue(NUMBER_OF_PROFILES);
+ checkState(numberOfProfilesText != null, "Could not read the built-in quality profile notification");
+ Integer numberOfProfiles = Integer.valueOf(numberOfProfilesText);
+ IntStream.rangeClosed(0, numberOfProfiles - 1)
+ .mapToObj(index -> Profile.newBuilder()
+ .setProfileName(getNonNullFieldValue(notification, index + PROFILE_NAME))
+ .setLanguageKey(getNonNullFieldValue(notification, index + LANGUAGE_KEY))
+ .setLanguageName(getNonNullFieldValue(notification, index + LANGUAGE_NAME))
+ .setNewRules(parseInt(getNonNullFieldValue(notification, index + NEW_RULES)))
+ .setUpdatedRules(parseInt(getNonNullFieldValue(notification, index + UPDATED_RULES)))
+ .setRemovedRules(parseInt(getNonNullFieldValue(notification, index + REMOVED_RULES)))
+ .setStartDate(parseLong(getNonNullFieldValue(notification, index + START_DATE)))
+ .setEndDate(parseLong(getNonNullFieldValue(notification, index + END_DATE)))
+ .build())
+ .forEach(notif::addProfile);
+ return notif;
+ }
+
+ private static String getNonNullFieldValue(Notification notification, String key) {
+ String value = notification.getFieldValue(key);
+ return requireNonNull(value, format("Notification field '%s' is null", key));
+ }
+
+ public List<Profile> getProfiles() {
+ return profiles;
+ }
+
+ public static class Profile {
+ private final String profileName;
+ private final String languageKey;
+ private final String languageName;
+ private final int newRules;
+ private final int updatedRules;
+ private final int removedRules;
+ private final long startDate;
+ private final long endDate;
+
+ public Profile(Builder builder) {
+ this.profileName = builder.profileName;
+ this.languageKey = builder.languageKey;
+ this.languageName = builder.languageName;
+ this.newRules = builder.newRules;
+ this.updatedRules = builder.updatedRules;
+ this.removedRules = builder.removedRules;
+ this.startDate = builder.startDate;
+ this.endDate = builder.endDate;
+ }
+
+ public String getProfileName() {
+ return profileName;
+ }
+
+ public String getLanguageKey() {
+ return languageKey;
+ }
+
+ public String getLanguageName() {
+ return languageName;
+ }
+
+ public int getNewRules() {
+ return newRules;
+ }
+
+ public int getUpdatedRules() {
+ return updatedRules;
+ }
+
+ public int getRemovedRules() {
+ return removedRules;
+ }
+
+ public long getStartDate() {
+ return startDate;
+ }
+
+ public long getEndDate() {
+ return endDate;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String profileName;
+ private String languageKey;
+ private String languageName;
+ private int newRules;
+ private int updatedRules;
+ private int removedRules;
+ private long startDate;
+ private long endDate;
+
+ private Builder() {
+ }
+
+ public Builder setLanguageKey(String languageKey) {
+ this.languageKey = requireNonNull(languageKey, "languageKEy should not be null");
+ return this;
+ }
+
+ public Builder setLanguageName(String languageName) {
+ this.languageName = requireNonNull(languageName, "languageName should not be null");
+ return this;
+ }
+
+ public Builder setProfileName(String profileName) {
+ this.profileName = requireNonNull(profileName, "profileName should not be null");
+ return this;
+ }
+
+ public Builder setNewRules(int newRules) {
+ checkState(newRules >= 0, "newRules should not be negative");
+ this.newRules = newRules;
+ return this;
+ }
+
+ public Builder setUpdatedRules(int updatedRules) {
+ checkState(updatedRules >= 0, "updatedRules should not be negative");
+ this.updatedRules = updatedRules;
+ return this;
+ }
+
+ public Builder setRemovedRules(int removedRules) {
+ checkState(removedRules >= 0, "removedRules should not be negative");
+ this.removedRules = removedRules;
+ return this;
+ }
+
+ public Builder setStartDate(long startDate) {
+ this.startDate = startDate;
+ return this;
+ }
+
+ public Builder setEndDate(long endDate) {
+ this.endDate = endDate;
+ return this;
+ }
+
+ public Profile build() {
+ return new Profile(this);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.notification.EmailNotificationHandler;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.email.EmailNotificationChannel;
+import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
+
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+
+public class BuiltInQPChangeNotificationHandler extends EmailNotificationHandler<BuiltInQPChangeNotification> {
+ private final DbClient dbClient;
+
+ public BuiltInQPChangeNotificationHandler(DbClient dbClient, EmailNotificationChannel emailNotificationChannel) {
+ super(emailNotificationChannel);
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Class<BuiltInQPChangeNotification> getNotificationClass() {
+ return BuiltInQPChangeNotification.class;
+ }
+
+ @Override
+ public Set<EmailDeliveryRequest> toEmailDeliveryRequests(Collection<BuiltInQPChangeNotification> notifications) {
+ try (DbSession session = dbClient.openSession(false)) {
+ return dbClient.authorizationDao()
+ .selectQualityProfileAdministratorLogins(session)
+ .stream()
+ .flatMap(t -> notifications.stream().map(notification -> new EmailDeliveryRequest(t.getEmail(), notification)))
+ .collect(toSet());
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Comparator;
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.platform.Server;
+import org.sonar.server.issue.notification.EmailMessage;
+import org.sonar.server.issue.notification.EmailTemplate;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.sonar.api.utils.DateUtils.formatDate;
+import static org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationBuilder.Profile;
+import static org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationBuilder.parse;
+
+public class BuiltInQPChangeNotificationTemplate implements EmailTemplate {
+
+ private final Server server;
+
+ public BuiltInQPChangeNotificationTemplate(Server server) {
+ this.server = server;
+ }
+
+ @Override
+ @CheckForNull
+ public EmailMessage format(Notification notification) {
+ if (!BuiltInQPChangeNotification.TYPE.equals(notification.getType())) {
+ return null;
+ }
+
+ BuiltInQPChangeNotificationBuilder profilesNotification = parse(notification);
+ StringBuilder message = new StringBuilder("The following built-in profiles have been updated:\n\n");
+ profilesNotification.getProfiles().stream()
+ .sorted(Comparator.comparing(Profile::getLanguageName).thenComparing(Profile::getProfileName))
+ .forEach(profile -> {
+ message.append("\"")
+ .append(profile.getProfileName())
+ .append("\" - ")
+ .append(profile.getLanguageName())
+ .append(": ")
+ .append(server.getPublicRootUrl()).append("/profiles/changelog?language=")
+ .append(profile.getLanguageKey())
+ .append("&name=")
+ .append(encode(profile.getProfileName()))
+ .append("&since=")
+ .append(formatDate(new Date(profile.getStartDate())))
+ .append("&to=")
+ .append(formatDate(new Date(profile.getEndDate())))
+ .append("\n");
+ int newRules = profile.getNewRules();
+ if (newRules > 0) {
+ message.append(" ").append(newRules).append(" new rule")
+ .append(plural(newRules))
+ .append('\n');
+ }
+ int updatedRules = profile.getUpdatedRules();
+ if (updatedRules > 0) {
+ message.append(" ").append(updatedRules).append(" rule")
+ .append(updatedRules > 1 ? "s have been updated" : " has been updated")
+ .append("\n");
+ }
+ int removedRules = profile.getRemovedRules();
+ if (removedRules > 0) {
+ message.append(" ").append(removedRules).append(" rule")
+ .append(plural(removedRules))
+ .append(" removed\n");
+ }
+ message.append("\n");
+ });
+
+ message.append("This is a good time to review your quality profiles and update them to benefit from the latest evolutions: ");
+ message.append(server.getPublicRootUrl()).append("/profiles");
+
+ // And finally return the email that will be sent
+ return new EmailMessage()
+ .setMessageId(BuiltInQPChangeNotification.TYPE)
+ .setSubject("Built-in quality profiles have been updated")
+ .setPlainTextMessage(message.toString());
+ }
+
+ private static String plural(int count) {
+ return count > 1 ? "s" : "";
+ }
+
+ public String encode(String text) {
+ try {
+ return URLEncoder.encode(text, UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException(String.format("Cannot encode %s", text), e);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.collect.ImmutableList;
+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.rule.RuleKey;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+
+import static org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
+import static org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.OverriddenParam;
+
+/**
+ * Represent a Quality Profile as computed from {@link BuiltInQualityProfilesDefinition} provided by installed plugins.
+ */
+@Immutable
+public final class BuiltInQProfile {
+ private final QProfileName qProfileName;
+ private final boolean isDefault;
+ private final List<ActiveRule> activeRules;
+
+ private BuiltInQProfile(Builder builder) {
+ this.qProfileName = new QProfileName(builder.language, builder.name);
+ this.isDefault = builder.declaredDefault || builder.computedDefault;
+ this.activeRules = ImmutableList.copyOf(builder.activeRules);
+ }
+
+ public String getName() {
+ return qProfileName.getName();
+ }
+
+ public String getLanguage() {
+ return qProfileName.getLanguage();
+ }
+
+ public QProfileName getQProfileName() {
+ return qProfileName;
+ }
+
+ public boolean isDefault() {
+ return isDefault;
+ }
+
+ public List<ActiveRule> getActiveRules() {
+ return activeRules;
+ }
+
+ public static final class ActiveRule {
+ private final String ruleUuid;
+ private final RuleKey ruleKey;
+ private final String severity;
+ private final List<OverriddenParam> params;
+
+ public ActiveRule(String ruleUuid, BuiltInActiveRule builtIn) {
+ this(ruleUuid, RuleKey.of(builtIn.repoKey(), builtIn.ruleKey()), builtIn.overriddenSeverity(), builtIn.overriddenParams());
+ }
+
+ ActiveRule(String ruleUuid, RuleKey ruleKey, @Nullable String severity, List<OverriddenParam> params) {
+ this.ruleUuid = ruleUuid;
+ this.ruleKey = ruleKey;
+ this.severity = severity;
+ this.params = params;
+ }
+
+ public String getRuleUuid() {
+ return ruleUuid;
+ }
+
+ public RuleKey getRuleKey() {
+ return ruleKey;
+ }
+
+ @CheckForNull
+ public String getSeverity() {
+ return severity;
+ }
+
+ public List<OverriddenParam> getParams() {
+ return params;
+ }
+ }
+
+ static final class Builder {
+ private String language;
+ private String name;
+ private boolean declaredDefault;
+ private boolean computedDefault;
+ private final List<ActiveRule> activeRules = new ArrayList<>();
+
+ public Builder setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
+ Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ Builder setDeclaredDefault(boolean declaredDefault) {
+ this.declaredDefault = declaredDefault;
+ return this;
+ }
+
+ boolean isDeclaredDefault() {
+ return declaredDefault;
+ }
+
+ Builder setComputedDefault(boolean flag) {
+ computedDefault = flag;
+ return this;
+ }
+
+ Builder addRule(ActiveRule activeRule) {
+ this.activeRules.add(activeRule);
+ return this;
+ }
+
+ BuiltInQProfile build() {
+ return new BuiltInQProfile(this);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import org.sonar.db.DbSession;
+
+public interface BuiltInQProfileInsert {
+ /**
+ * Persist a new built-in profile
+ * Db sessions are committed and Elasticsearch indices are updated
+ */
+ void create(DbSession batchSession, BuiltInQProfile builtInQProfile);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.base.Splitter;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.rule.RuleParamType;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.DefaultQProfileDto;
+import org.sonar.db.qualityprofile.OrgQProfileDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.ServerRuleFinder;
+import org.sonar.server.util.TypeValidations;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.emptySet;
+import static java.util.Objects.requireNonNull;
+
+public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert {
+ private final DbClient dbClient;
+ private final ServerRuleFinder ruleFinder;
+ private final System2 system2;
+ private final UuidFactory uuidFactory;
+ private final TypeValidations typeValidations;
+ private final ActiveRuleIndexer activeRuleIndexer;
+ private RuleRepository ruleRepository;
+
+ public BuiltInQProfileInsertImpl(DbClient dbClient, ServerRuleFinder ruleFinder, System2 system2, UuidFactory uuidFactory,
+ TypeValidations typeValidations, ActiveRuleIndexer activeRuleIndexer) {
+ this.dbClient = dbClient;
+ this.ruleFinder = ruleFinder;
+ this.system2 = system2;
+ this.uuidFactory = uuidFactory;
+ this.typeValidations = typeValidations;
+ this.activeRuleIndexer = activeRuleIndexer;
+ }
+
+ @Override
+ public void create(DbSession batchDbSession, BuiltInQProfile builtInQProfile) {
+ initRuleRepository(batchDbSession);
+
+ Date now = new Date(system2.now());
+ RulesProfileDto ruleProfile = insertRulesProfile(batchDbSession, builtInQProfile, now);
+
+ List<ActiveRuleChange> changes = builtInQProfile.getActiveRules().stream()
+ .map(activeRule -> insertActiveRule(batchDbSession, ruleProfile, activeRule, now.getTime()))
+ .collect(MoreCollectors.toList());
+
+ changes.forEach(change -> dbClient.qProfileChangeDao().insert(batchDbSession, change.toDto(null)));
+
+ createDefaultAndOrgQProfiles(batchDbSession, builtInQProfile, ruleProfile);
+
+ activeRuleIndexer.commitAndIndex(batchDbSession, changes);
+ }
+
+
+ private void createDefaultAndOrgQProfiles(DbSession batchDbSession, BuiltInQProfile builtIn, RulesProfileDto rulesProfileDto) {
+ Optional<String> qProfileUuid = dbClient.defaultQProfileDao().selectDefaultQProfileUuid(batchDbSession, builtIn.getLanguage());
+
+ OrgQProfileDto dto = new OrgQProfileDto()
+ .setRulesProfileUuid(rulesProfileDto.getUuid())
+ .setUuid(uuidFactory.create());
+
+ if (builtIn.isDefault() && qProfileUuid.isEmpty()) {
+ DefaultQProfileDto defaultQProfileDto = new DefaultQProfileDto()
+ .setQProfileUuid(dto.getUuid())
+ .setLanguage(builtIn.getLanguage());
+ dbClient.defaultQProfileDao().insert(batchDbSession, defaultQProfileDto);
+ }
+
+ dbClient.qualityProfileDao().insert(batchDbSession, dto);
+ }
+
+ private void initRuleRepository(DbSession dbSession) {
+ if (ruleRepository == null) {
+ ruleRepository = new RuleRepository(dbClient, dbSession, ruleFinder);
+ }
+ }
+
+ private RulesProfileDto insertRulesProfile(DbSession dbSession, BuiltInQProfile builtIn, Date now) {
+ RulesProfileDto dto = new RulesProfileDto()
+ .setUuid(uuidFactory.create())
+ .setName(builtIn.getName())
+ .setLanguage(builtIn.getLanguage())
+ .setIsBuiltIn(true)
+ .setRulesUpdatedAtAsDate(now);
+ dbClient.qualityProfileDao().insert(dbSession, dto);
+ return dto;
+ }
+
+ private ActiveRuleChange insertActiveRule(DbSession batchDbSession, RulesProfileDto rulesProfileDto, BuiltInQProfile.ActiveRule activeRule, long now) {
+ RuleKey ruleKey = activeRule.getRuleKey();
+ RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey)
+ .orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey));
+
+ ActiveRuleDto dto = new ActiveRuleDto();
+ dto.setProfileUuid(rulesProfileDto.getUuid());
+ dto.setRuleUuid(ruleDefinitionDto.getUuid());
+ dto.setKey(ActiveRuleKey.of(rulesProfileDto, ruleDefinitionDto.getKey()));
+ dto.setSeverity(firstNonNull(activeRule.getSeverity(), ruleDefinitionDto.getSeverityString()));
+ dto.setUpdatedAt(now);
+ dto.setCreatedAt(now);
+ dbClient.activeRuleDao().insert(batchDbSession, dto);
+
+ List<ActiveRuleParamDto> paramDtos = insertActiveRuleParams(batchDbSession, activeRule, dto);
+
+ ActiveRuleChange change = new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, dto, ruleDefinitionDto);
+ change.setSeverity(dto.getSeverityString());
+ paramDtos.forEach(paramDto -> change.setParameter(paramDto.getKey(), paramDto.getValue()));
+ return change;
+ }
+
+ private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, BuiltInQProfile.ActiveRule activeRule, ActiveRuleDto activeRuleDto) {
+ Map<String, String> valuesByParamKey = activeRule.getParams().stream()
+ .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
+ List<ActiveRuleParamDto> rules = ruleRepository.getRuleParams(activeRule.getRuleKey()).stream()
+ .map(param -> createParamDto(param, Optional.ofNullable(valuesByParamKey.get(param.getName())).orElse(param.getDefaultValue())))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ rules.forEach(paramDto -> dbClient.activeRuleDao().insertParam(session, activeRuleDto, paramDto));
+ return rules;
+ }
+
+ @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;
+ }
+
+ private String validateParam(RuleParamDto ruleParam, String value) {
+ RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType());
+ if (ruleParamType.multiple()) {
+ List<String> values = newArrayList(Splitter.on(",").split(value));
+ typeValidations.validate(values, ruleParamType.type(), ruleParamType.values());
+ } else {
+ typeValidations.validate(value, ruleParamType.type(), ruleParamType.values());
+ }
+ return value;
+ }
+
+ private static class RuleRepository {
+ private final Map<RuleKey, Set<RuleParamDto>> params;
+ private final ServerRuleFinder ruleFinder;
+
+ private RuleRepository(DbClient dbClient, DbSession session, ServerRuleFinder ruleFinder) {
+ this.ruleFinder = ruleFinder;
+ this.params = new HashMap<>();
+
+ for (RuleParamDto ruleParam : dbClient.ruleDao().selectAllRuleParams(session)) {
+ Optional<RuleKey> ruleKey = ruleFinder.findDtoByUuid(ruleParam.getRuleUuid())
+ .map(r -> RuleKey.of(r.getRepositoryKey(), r.getRuleKey()));
+
+ if (ruleKey.isPresent()) {
+ params.computeIfAbsent(ruleKey.get(), r -> new HashSet<>()).add(ruleParam);
+ }
+ }
+ }
+
+ private Optional<RuleDefinitionDto> getDefinition(RuleKey ruleKey) {
+ return ruleFinder.findDtoByKey(requireNonNull(ruleKey, "RuleKey can't be null"));
+ }
+
+ private Set<RuleParamDto> getRuleParams(RuleKey ruleKey) {
+ return params.getOrDefault(requireNonNull(ruleKey, "RuleKey can't be null"), emptySet());
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+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
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.List;
+
+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 list
+ *
+ * @throws IllegalStateException if {@link #initialize()} has not been called
+ */
+ List<BuiltInQProfile> get();
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.rule.ServerRuleFinder;
+
+import static com.google.common.base.Preconditions.checkState;
+
+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 DbClient dbClient;
+ private final ServerRuleFinder ruleFinder;
+ private final Languages languages;
+ private final List<BuiltInQualityProfilesDefinition> definitions;
+ private List<BuiltInQProfile> qProfiles;
+
+ /**
+ * Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all
+ */
+ public BuiltInQProfileRepositoryImpl(DbClient dbClient, ServerRuleFinder ruleFinder, Languages languages) {
+ this(dbClient, ruleFinder, languages, new BuiltInQualityProfilesDefinition[0]);
+ }
+
+ public BuiltInQProfileRepositoryImpl(DbClient dbClient, ServerRuleFinder ruleFinder, Languages languages, BuiltInQualityProfilesDefinition... definitions) {
+ this.dbClient = dbClient;
+ this.ruleFinder = ruleFinder;
+ this.languages = languages;
+ this.definitions = ImmutableList.copyOf(definitions);
+ }
+
+ @Override
+ public void initialize() {
+ checkState(qProfiles == null, "initialize must be called only once");
+
+ Profiler profiler = Profiler.create(LOGGER).startInfo("Load quality profiles");
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ for (BuiltInQualityProfilesDefinition definition : definitions) {
+ definition.define(context);
+ }
+ Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage = validateAndClean(context);
+ this.qProfiles = toFlatList(rulesProfilesByLanguage);
+ ensureAllLanguagesHaveAtLeastOneBuiltInQP();
+ profiler.stopDebug();
+ }
+
+ @Override
+ public List<BuiltInQProfile> get() {
+ checkState(qProfiles != null, "initialize must be called first");
+
+ return qProfiles;
+ }
+
+ private void ensureAllLanguagesHaveAtLeastOneBuiltInQP() {
+ Set<String> languagesWithBuiltInQProfiles = qProfiles.stream().map(BuiltInQProfile::getLanguage).collect(Collectors.toSet());
+ Set<String> languagesWithoutBuiltInQProfiles = Arrays.stream(languages.all())
+ .map(Language::getKey)
+ .filter(key -> !languagesWithBuiltInQProfiles.contains(key))
+ .collect(Collectors.toSet());
+
+ checkState(languagesWithoutBuiltInQProfiles.isEmpty(), "The following languages have no built-in quality profiles: %s",
+ String.join("", languagesWithoutBuiltInQProfiles));
+ }
+
+ private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) {
+ Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = context.profilesByLanguageAndName();
+ profilesByLanguageAndName.entrySet()
+ .removeIf(entry -> {
+ String language = entry.getKey();
+ if (languages.get(language) == null) {
+ LOGGER.info("Language {} is not installed, related quality profiles are ignored", language);
+ return true;
+ }
+ return false;
+ });
+
+ return profilesByLanguageAndName;
+ }
+
+ private List<BuiltInQProfile> toFlatList(Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage) {
+ if (rulesProfilesByLanguage.isEmpty()) {
+ return Collections.emptyList();
+ }
+ Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = loadRuleDefinitionsByRuleKey();
+ Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = rulesProfilesByLanguage
+ .entrySet()
+ .stream()
+ .collect(MoreCollectors.uniqueIndex(
+ Map.Entry::getKey,
+ rulesProfilesByLanguageAndName -> toQualityProfileBuilders(rulesProfilesByLanguageAndName, rulesByRuleKey)));
+ return buildersByLanguage
+ .entrySet()
+ .stream()
+ .filter(BuiltInQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
+ .map(entry -> toQualityProfiles(entry.getValue()))
+ .flatMap(Collection::stream)
+ .collect(MoreCollectors.toList());
+ }
+
+ private Map<RuleKey, RuleDefinitionDto> loadRuleDefinitionsByRuleKey() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Collection<RuleDefinitionDto> ruleDefinitions = ruleFinder.findAll();
+ Multimap<String, DeprecatedRuleKeyDto> deprecatedRuleKeysByRuleId = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
+ .collect(MoreCollectors.index(DeprecatedRuleKeyDto::getRuleUuid));
+ Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = new HashMap<>();
+ for (RuleDefinitionDto ruleDefinition : ruleDefinitions) {
+ rulesByRuleKey.put(ruleDefinition.getKey(), ruleDefinition);
+ deprecatedRuleKeysByRuleId.get(ruleDefinition.getUuid()).forEach(t -> rulesByRuleKey.put(RuleKey.of(t.getOldRepositoryKey(), t.getOldRuleKey()), ruleDefinition));
+ }
+ return rulesByRuleKey;
+ }
+ }
+
+ /**
+ * Creates {@link BuiltInQProfile.Builder} for each unique quality profile name for a given language.
+ * Builders will have the following properties populated:
+ * <ul>
+ * <li>{@link BuiltInQProfile.Builder#language language}: key of the method's parameter</li>
+ * <li>{@link BuiltInQProfile.Builder#name name}: {@link RulesProfile#getName()}</li>
+ * <li>{@link BuiltInQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile
+ * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li>
+ * <li>{@link BuiltInQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all
+ * RulesProfile with a given name</li>
+ * </ul>
+ */
+ private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguageAndName,
+ Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
+ String language = rulesProfilesByLanguageAndName.getKey();
+ // use a LinkedHashMap to keep order of insertion of RulesProfiles
+ Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>();
+ for (BuiltInQualityProfile builtInProfile : rulesProfilesByLanguageAndName.getValue().values()) {
+ qualityProfileBuildersByName.compute(
+ builtInProfile.name(),
+ (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, builtInProfile, rulesByRuleKey));
+ }
+ 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<String, List<BuiltInQProfile.Builder>> entry) {
+ Set<String> 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, BuiltInQualityProfile builtInProfile,
+ Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) {
+ BuiltInQProfile.Builder builder = createOrReuseBuilder(existingBuilder, language, builtInProfile);
+ builder.setDeclaredDefault(builtInProfile.isDefault());
+ builtInProfile.rules().forEach(builtInActiveRule -> {
+ RuleKey ruleKey = RuleKey.of(builtInActiveRule.repoKey(), builtInActiveRule.ruleKey());
+ RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey);
+ checkState(ruleDefinition != null, "Rule with key '%s' not found", ruleKey);
+ builder.addRule(new BuiltInQProfile.ActiveRule(ruleDefinition.getUuid(), ruleDefinition.getKey(),
+ builtInActiveRule.overriddenSeverity(), builtInActiveRule.overriddenParams()));
+ });
+ return builder;
+ }
+
+ private static BuiltInQProfile.Builder createOrReuseBuilder(@Nullable BuiltInQProfile.Builder existingBuilder, String language, BuiltInQualityProfile builtInProfile) {
+ if (existingBuilder == null) {
+ return new BuiltInQProfile.Builder()
+ .setLanguage(language)
+ .setName(builtInProfile.name());
+ }
+ return existingBuilder;
+ }
+
+ private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) {
+ if (builders.stream().noneMatch(BuiltInQProfile.Builder::isDeclaredDefault)) {
+ Optional<BuiltInQProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst();
+ if (sonarWayProfile.isPresent()) {
+ sonarWayProfile.get().setComputedDefault(true);
+ } else {
+ builders.iterator().next().setComputedDefault(true);
+ }
+ }
+ return builders.stream()
+ .map(BuiltInQProfile.Builder::build)
+ .collect(MoreCollectors.toList(builders.size()));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.List;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+
+public interface BuiltInQProfileUpdate {
+ /**
+ * Persist an existing built-in profile.
+ * Db session is committed and Elasticsearch indices are updated.
+ */
+ List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInQProfile, RulesProfileDto ruleProfile);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.RuleActivation;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+
+public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate {
+
+ private final DbClient dbClient;
+ private final RuleActivator ruleActivator;
+ private final ActiveRuleIndexer activeRuleIndexer;
+ private final QualityProfileChangeEventService qualityProfileChangeEventService;
+
+ public BuiltInQProfileUpdateImpl(DbClient dbClient, RuleActivator ruleActivator, ActiveRuleIndexer activeRuleIndexer,
+ QualityProfileChangeEventService qualityProfileChangeEventService) {
+ this.dbClient = dbClient;
+ this.ruleActivator = ruleActivator;
+ this.activeRuleIndexer = activeRuleIndexer;
+ this.qualityProfileChangeEventService = qualityProfileChangeEventService;
+ }
+
+ public List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInDefinition, RulesProfileDto initialRuleProfile) {
+ // Keep reference to all the activated rules before update
+ Set<String> deactivatedRuleUuids = dbClient.activeRuleDao().selectByRuleProfile(dbSession, initialRuleProfile)
+ .stream()
+ .map(ActiveRuleDto::getRuleUuid)
+ .collect(MoreCollectors.toHashSet());
+
+ // all rules, including those which are removed from built-in profile
+ Set<String> ruleUuids = Stream.concat(
+ deactivatedRuleUuids.stream(),
+ builtInDefinition.getActiveRules().stream().map(BuiltInQProfile.ActiveRule::getRuleUuid))
+ .collect(toSet());
+
+ Collection<RuleActivation> activations = new ArrayList<>();
+ for (BuiltInQProfile.ActiveRule ar : builtInDefinition.getActiveRules()) {
+ RuleActivation activation = convert(ar);
+ activations.add(activation);
+ deactivatedRuleUuids.remove(activation.getRuleUuid());
+ }
+
+ RuleActivationContext context = ruleActivator.createContextForBuiltInProfile(dbSession, initialRuleProfile, ruleUuids);
+ List<ActiveRuleChange> changes = new ArrayList<>();
+
+ changes.addAll(ruleActivator.activate(dbSession, activations, context));
+
+ // these rules are no longer part of the built-in profile
+ deactivatedRuleUuids.forEach(ruleUuid -> changes.addAll(ruleActivator.deactivate(dbSession, context, ruleUuid, false)));
+
+ if (!changes.isEmpty()) {
+ qualityProfileChangeEventService.distributeRuleChangeEvent(context.getProfiles(), changes, initialRuleProfile.getLanguage());
+ }
+
+ activeRuleIndexer.commitAndIndex(dbSession, changes);
+ return changes;
+ }
+
+ private static RuleActivation convert(BuiltInQProfile.ActiveRule ar) {
+ Map<String, String> params = ar.getParams().stream()
+ .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue));
+ return RuleActivation.create(ar.getRuleUuid(), ar.getSeverity(), params);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationBuilder.Profile;
+
+import static org.sonar.core.config.CorePropertyDefinitions.DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED;
+
+public class BuiltInQualityProfilesUpdateListener {
+
+ private final NotificationManager notificationManager;
+ private final Languages languages;
+ private final Configuration config;
+
+ public BuiltInQualityProfilesUpdateListener(NotificationManager notificationManager, Languages languages, Configuration config) {
+ this.notificationManager = notificationManager;
+ this.languages = languages;
+ this.config = config;
+ }
+
+ public void onChange(Multimap<QProfileName, ActiveRuleChange> changedProfiles, long startDate, long endDate) {
+ if (config.getBoolean(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES).orElse(false)) {
+ return;
+ }
+
+ BuiltInQPChangeNotificationBuilder builder = new BuiltInQPChangeNotificationBuilder();
+ changedProfiles.keySet().stream()
+ .map(changedProfile -> {
+ String profileName = changedProfile.getName();
+ Language language = languages.get(changedProfile.getLanguage());
+ Collection<ActiveRuleChange> activeRuleChanges = changedProfiles.get(changedProfile);
+ int newRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(ACTIVATED::equals).count();
+ int updatedRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(UPDATED::equals).count();
+ int removedRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(DEACTIVATED::equals).count();
+ return Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(language.getKey())
+ .setLanguageName(language.getName())
+ .setNewRules(newRules)
+ .setUpdatedRules(updatedRules)
+ .setRemovedRules(removedRules)
+ .setStartDate(startDate)
+ .setEndDate(endDate)
+ .build();
+ })
+ .forEach(builder::addProfile);
+
+ notificationManager.scheduleForSending(builder.build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.Collection;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+
+@FunctionalInterface
+public interface DescendantProfilesSupplier {
+
+ Result get(Collection<QProfileDto> profiles, Collection<String> ruleUuids);
+
+ final class Result {
+ private final Collection<QProfileDto> profiles;
+ private final Collection<ActiveRuleDto> activeRules;
+ private final Collection<ActiveRuleParamDto> activeRuleParams;
+
+ public Result(Collection<QProfileDto> profiles, Collection<ActiveRuleDto> activeRules, Collection<ActiveRuleParamDto> activeRuleParams) {
+ this.profiles = profiles;
+ this.activeRules = activeRules;
+ this.activeRuleParams = activeRuleParams;
+ }
+
+ public Collection<QProfileDto> getProfiles() {
+ return profiles;
+ }
+
+ public Collection<ActiveRuleDto> getActiveRules() {
+ return activeRules;
+ }
+
+ public Collection<ActiveRuleParamDto> getActiveRuleParams() {
+ return activeRuleParams;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import javax.annotation.Nullable;
+
+public class QProfileName {
+ private final String lang;
+ private final String name;
+
+ public QProfileName(String lang, String name) {
+ this.lang = lang;
+ this.name = name;
+ }
+
+ public String getLanguage() {
+ return lang;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public static QProfileName createFor(String lang, String name){
+ return new QProfileName(lang, name);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ QProfileName that = (QProfileName) o;
+ if (!lang.equals(that.lang)) {
+ return false;
+ }
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = lang.hashCode();
+ result = 31 * result + name.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s/%s", lang, name);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.qualityprofile.RuleActivation;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.core.util.stream.MoreCollectors.index;
+import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+
+/**
+ * Cache of the data required to activate/deactivate
+ * multiple rules on a Quality profile, including
+ * the rule definitions, the rule parameters, the tree
+ * of profiles hierarchy and its related active rules.
+ */
+public class RuleActivationContext {
+
+ private final long date;
+
+ // The profile that is initially targeted by the operation
+ private final RulesProfileDto baseRulesProfile;
+
+ private final Map<String, QProfileDto> profilesByUuid = new HashMap<>();
+ private final ListMultimap<String, QProfileDto> profilesByParentUuid = ArrayListMultimap.create();
+
+ // The rules/active rules involved in the group of activations/de-activations
+ private final Map<String, RuleWrapper> rulesByUuid = new HashMap<>();
+ private final Map<ActiveRuleKey, ActiveRuleWrapper> activeRulesByKey = new HashMap<>();
+
+ // Cursors used to move in the rules and in the tree of profiles.
+
+ private RulesProfileDto currentRulesProfile;
+ // Cardinality is zero-to-many when cursor is on a built-in rules profile,
+ // otherwise it's always one, and only one (cursor on descendants or on non-built-in base profile).
+ private Collection<QProfileDto> currentProfiles;
+ private RuleWrapper currentRule;
+ private ActiveRuleWrapper currentActiveRule;
+ private ActiveRuleWrapper currentParentActiveRule;
+
+ private boolean descendantsLoaded = false;
+ private final DescendantProfilesSupplier descendantProfilesSupplier;
+
+ private RuleActivationContext(Builder builder) {
+ this.date = builder.date;
+ this.descendantProfilesSupplier = builder.descendantProfilesSupplier;
+
+ ListMultimap<String, RuleParamDto> paramsByRuleId = builder.ruleParams.stream().collect(index(RuleParamDto::getRuleUuid));
+ for (RuleDefinitionDto rule : builder.rules) {
+ RuleWrapper wrapper = new RuleWrapper(rule, paramsByRuleId.get(rule.getUuid()));
+ rulesByUuid.put(rule.getUuid(), wrapper);
+ }
+
+ this.baseRulesProfile = builder.baseRulesProfile;
+ register(builder.profiles);
+ register(builder.activeRules, builder.activeRuleParams);
+ }
+
+ private void register(Collection<QProfileDto> profiles) {
+ for (QProfileDto profile : profiles) {
+ profilesByUuid.put(profile.getKee(), profile);
+ if (profile.getParentKee() != null) {
+ profilesByParentUuid.put(profile.getParentKee(), profile);
+ }
+ }
+ }
+
+ private void register(Collection<ActiveRuleDto> activeRules, Collection<ActiveRuleParamDto> activeRuleParams) {
+ ListMultimap<String, ActiveRuleParamDto> paramsByActiveRuleUuid = activeRuleParams.stream().collect(index(ActiveRuleParamDto::getActiveRuleUuid));
+ for (ActiveRuleDto activeRule : activeRules) {
+ ActiveRuleWrapper wrapper = new ActiveRuleWrapper(activeRule, paramsByActiveRuleUuid.get(activeRule.getUuid()));
+ this.activeRulesByKey.put(activeRule.getKey(), wrapper);
+ }
+ }
+
+ long getDate() {
+ return date;
+ }
+
+ /**
+ * The rule currently selected.
+ */
+ public RuleWrapper getRule() {
+ checkState(currentRule != null, "Rule has not been set yet");
+ return currentRule;
+ }
+
+ @CheckForNull
+ String getRequestedParamValue(RuleActivation request, String key) {
+ if (currentRule.rule.isCustomRule()) {
+ return null;
+ }
+ return request.getParameter(key);
+ }
+
+ boolean hasRequestedParamValue(RuleActivation request, String key) {
+ return request.hasParameter(key);
+ }
+
+ /**
+ * The rules profile being selected.
+ */
+ RulesProfileDto getRulesProfile() {
+ checkState(currentRulesProfile != null, "Rule profile has not been set yet");
+ return currentRulesProfile;
+ }
+
+ /**
+ * The active rule related to the selected profile and rule.
+ * @return null if the selected rule is not activated on the selected profile.
+ * @see #getRulesProfile()
+ * @see #getRule()
+ */
+ @CheckForNull
+ ActiveRuleWrapper getActiveRule() {
+ return currentActiveRule;
+ }
+
+ /**
+ * The active rule related to the rule and the parent of the selected profile.
+ * @return null if the selected rule is not activated on the parent profile.
+ * @see #getRule()
+ */
+ @CheckForNull
+ ActiveRuleWrapper getParentActiveRule() {
+ return currentParentActiveRule;
+ }
+
+ /**
+ * Whether the profile cursor is on the base profile or not.
+ */
+ boolean isCascading() {
+ return currentRulesProfile != null && !currentRulesProfile.getUuid().equals(baseRulesProfile.getUuid());
+ }
+
+ /**
+ * The profiles being selected. Can be zero or many if {@link #getRulesProfile()} is built-in.
+ * Else the collection always contains a single profile.
+ */
+ Collection<QProfileDto> getProfiles() {
+ checkState(currentProfiles != null, "Profiles have not been set yet");
+ return currentProfiles;
+ }
+
+ /**
+ * The children of {@link #getProfiles()}
+ */
+ Collection<QProfileDto> getChildProfiles() {
+ loadDescendants();
+ return getProfiles().stream()
+ .flatMap(p -> profilesByParentUuid.get(p.getKee()).stream())
+ .collect(Collectors.toList());
+ }
+
+ private void loadDescendants() {
+ if (descendantsLoaded) {
+ return;
+ }
+ Collection<QProfileDto> baseProfiles = profilesByUuid.values().stream()
+ .filter(p -> p.getRulesProfileUuid().equals(baseRulesProfile.getUuid()))
+ .collect(toArrayList(profilesByUuid.size()));
+ DescendantProfilesSupplier.Result result = descendantProfilesSupplier.get(baseProfiles, rulesByUuid.keySet());
+ register(result.getProfiles());
+ register(result.getActiveRules(), result.getActiveRuleParams());
+ descendantsLoaded = true;
+ }
+
+ /**
+ * Move the cursor to the given rule and back to the base profile.
+ */
+ public void reset(String ruleUuid) {
+ doSwitch(this.baseRulesProfile, ruleUuid);
+ }
+
+ /**
+ * Moves cursor to a child profile
+ */
+ void selectChild(QProfileDto to) {
+ checkState(!to.isBuiltIn());
+ QProfileDto qp = requireNonNull(this.profilesByUuid.get(to.getKee()), () -> "No profile with uuid " + to.getKee());
+
+ RulesProfileDto ruleProfile = RulesProfileDto.from(qp);
+ doSwitch(ruleProfile, getRule().get().getUuid());
+ }
+
+ private void doSwitch(RulesProfileDto ruleProfile, String ruleUuid) {
+ this.currentRule = rulesByUuid.get(ruleUuid);
+ checkRequest(this.currentRule != null, "Rule with UUID %s not found", ruleUuid);
+ RuleKey ruleKey = currentRule.get().getKey();
+
+ this.currentRulesProfile = ruleProfile;
+ this.currentProfiles = profilesByUuid.values().stream()
+ .filter(p -> p.getRulesProfileUuid().equals(ruleProfile.getUuid()))
+ .collect(Collectors.toList());
+ this.currentActiveRule = this.activeRulesByKey.get(ActiveRuleKey.of(ruleProfile, ruleKey));
+ this.currentParentActiveRule = this.currentProfiles.stream()
+ .map(QProfileDto::getParentKee)
+ .filter(Objects::nonNull)
+ .map(profilesByUuid::get)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .map(profile -> activeRulesByKey.get(ActiveRuleKey.of(profile, ruleKey)))
+ .orElse(null);
+ }
+
+ static final class Builder {
+ private long date = System.currentTimeMillis();
+ private RulesProfileDto baseRulesProfile;
+ private Collection<RuleDefinitionDto> rules;
+ private Collection<RuleParamDto> ruleParams;
+ private Collection<QProfileDto> profiles;
+ private Collection<ActiveRuleDto> activeRules;
+ private Collection<ActiveRuleParamDto> activeRuleParams;
+ private DescendantProfilesSupplier descendantProfilesSupplier;
+
+ Builder setDate(long l) {
+ this.date = l;
+ return this;
+ }
+
+ Builder setBaseProfile(RulesProfileDto p) {
+ this.baseRulesProfile = p;
+ return this;
+ }
+
+ Builder setRules(Collection<RuleDefinitionDto> rules) {
+ this.rules = rules;
+ return this;
+ }
+
+ Builder setRuleParams(Collection<RuleParamDto> ruleParams) {
+ this.ruleParams = ruleParams;
+ return this;
+ }
+
+ /**
+ * All the profiles involved in the activation workflow, including the
+ * parent profile, even if it's not updated.
+ */
+ Builder setProfiles(Collection<QProfileDto> profiles) {
+ this.profiles = profiles;
+ return this;
+ }
+
+ Builder setActiveRules(Collection<ActiveRuleDto> activeRules) {
+ this.activeRules = activeRules;
+ return this;
+ }
+
+ Builder setActiveRuleParams(Collection<ActiveRuleParamDto> activeRuleParams) {
+ this.activeRuleParams = activeRuleParams;
+ return this;
+ }
+
+ Builder setDescendantProfilesSupplier(DescendantProfilesSupplier d) {
+ this.descendantProfilesSupplier = d;
+ return this;
+ }
+
+ RuleActivationContext build() {
+ checkArgument(date > 0, "date is not set");
+ requireNonNull(baseRulesProfile, "baseRulesProfile is null");
+ requireNonNull(rules, "rules is null");
+ requireNonNull(ruleParams, "ruleParams is null");
+ requireNonNull(profiles, "profiles is null");
+ requireNonNull(activeRules, "activeRules is null");
+ requireNonNull(activeRuleParams, "activeRuleParams is null");
+ requireNonNull(descendantProfilesSupplier, "descendantProfilesSupplier is null");
+ return new RuleActivationContext(this);
+ }
+ }
+
+ public static final class RuleWrapper {
+ private final RuleDefinitionDto rule;
+ private final Map<String, RuleParamDto> paramsByKey;
+
+ private RuleWrapper(RuleDefinitionDto rule, Collection<RuleParamDto> params) {
+ this.rule = rule;
+ this.paramsByKey = params.stream().collect(uniqueIndex(RuleParamDto::getName));
+ }
+
+ public RuleDefinitionDto get() {
+ return rule;
+ }
+
+ Collection<RuleParamDto> getParams() {
+ return paramsByKey.values();
+ }
+
+ @CheckForNull
+ RuleParamDto getParam(String key) {
+ return paramsByKey.get(key);
+ }
+
+ @CheckForNull
+ String getParamDefaultValue(String key) {
+ RuleParamDto param = getParam(key);
+ return param != null ? param.getDefaultValue() : null;
+ }
+ }
+
+ static final class ActiveRuleWrapper {
+ private final ActiveRuleDto activeRule;
+ private final Map<String, ActiveRuleParamDto> paramsByKey;
+
+ private ActiveRuleWrapper(ActiveRuleDto activeRule, Collection<ActiveRuleParamDto> params) {
+ this.activeRule = activeRule;
+ this.paramsByKey = params.stream().collect(uniqueIndex(ActiveRuleParamDto::getKey));
+ }
+
+ ActiveRuleDto get() {
+ return activeRule;
+ }
+
+ Collection<ActiveRuleParamDto> getParams() {
+ return paramsByKey.values();
+ }
+
+ @CheckForNull
+ ActiveRuleParamDto getParam(String key) {
+ return paramsByKey.get(key);
+ }
+
+ @CheckForNull
+ String getParamValue(String key) {
+ ActiveRuleParamDto param = paramsByKey.get(key);
+ return param != null ? param.getValue() : null;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.base.Splitter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.rule.RuleParamType;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleDao;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.OrgQProfileDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.ActiveRuleInheritance;
+import org.sonar.server.qualityprofile.RuleActivation;
+import org.sonar.server.qualityprofile.builtin.RuleActivationContext.ActiveRuleWrapper;
+import org.sonar.server.qualityprofile.builtin.RuleActivationContext.RuleWrapper;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.util.TypeValidations;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.stream.Collectors.toList;
+import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+
+/**
+ * Activation and deactivation of rules in Quality profiles
+ */
+@ServerSide
+public class RuleActivator {
+
+ private final System2 system2;
+ private final DbClient db;
+ private final TypeValidations typeValidations;
+ private final UserSession userSession;
+
+ public RuleActivator(System2 system2, DbClient db, TypeValidations typeValidations, UserSession userSession) {
+ this.system2 = system2;
+ this.db = db;
+ this.typeValidations = typeValidations;
+ this.userSession = userSession;
+ }
+
+
+ public List<ActiveRuleChange> activate(DbSession dbSession, Collection<RuleActivation> activations, RuleActivationContext context) {
+ return activations.stream().map(a -> activate(dbSession, a, context))
+ .flatMap(List::stream)
+ .collect(toList());
+ }
+
+ public List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
+ context.reset(activation.getRuleUuid());
+ return doActivate(dbSession, activation, context);
+ }
+
+ private List<ActiveRuleChange> doActivate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
+ RuleDefinitionDto rule = context.getRule().get();
+ checkRequest(RuleStatus.REMOVED != rule.getStatus(), "Rule was removed: %s", rule.getKey());
+ checkRequest(!rule.isTemplate(), "Rule template can't be activated on a Quality profile: %s", rule.getKey());
+ checkRequest(context.getRulesProfile().getLanguage().equals(rule.getLanguage()),
+ "%s rule %s cannot be activated on %s profile %s", rule.getLanguage(), rule.getKey(), context.getRulesProfile().getLanguage(), context.getRulesProfile().getName());
+ List<ActiveRuleChange> changes = new ArrayList<>();
+ ActiveRuleChange change;
+ boolean stopCascading = false;
+
+ ActiveRuleWrapper activeRule = context.getActiveRule();
+ ActiveRuleKey activeRuleKey = ActiveRuleKey.of(context.getRulesProfile(), rule.getKey());
+ if (activeRule == null) {
+ if (activation.isReset()) {
+ // ignore reset when rule is not activated
+ return changes;
+ }
+ change = handleNewRuleActivation(activation, context, rule, activeRuleKey);
+ } else {
+ // already activated
+ if (context.isCascading() && activeRule.get().doesOverride()) {
+ // propagating to descendants, but child profile already overrides rule -> stop propagation
+ return changes;
+ }
+ change = new ActiveRuleChange(ActiveRuleChange.Type.UPDATED, activeRuleKey, rule);
+ stopCascading = handleUpdatedRuleActivation(activation, context, change, stopCascading, activeRule);
+
+ if (isSame(change, activeRule)) {
+ change = null;
+ stopCascading = true;
+ }
+ }
+
+ if (change != null) {
+ changes.add(change);
+ persist(change, context, dbSession);
+ }
+
+ if (!changes.isEmpty()) {
+ updateProfileDates(dbSession, context);
+ }
+
+ if (!stopCascading) {
+ changes.addAll(propagateActivationToDescendants(dbSession, activation, context));
+ }
+
+ return changes;
+ }
+
+ private boolean handleUpdatedRuleActivation(RuleActivation activation, RuleActivationContext context, ActiveRuleChange change,
+ boolean stopCascading, ActiveRuleWrapper activeRule) {
+ if (context.isCascading() && activeRule.get().getInheritance() == null) {
+ // activate on child, then on parent -> mark child as overriding parent
+ change.setInheritance(ActiveRuleInheritance.OVERRIDES);
+ change.setSeverity(activeRule.get().getSeverityString());
+ for (ActiveRuleParamDto activeParam : activeRule.getParams()) {
+ change.setParameter(activeParam.getKey(), activeParam.getValue());
+ }
+ stopCascading = true;
+ } else {
+ applySeverityAndParamToChange(activation, context, change);
+ if (!context.isCascading() && context.getParentActiveRule() != null) {
+ // override rule which is already declared on parents
+ change.setInheritance(isSameAsParent(change, context) ? ActiveRuleInheritance.INHERITED : ActiveRuleInheritance.OVERRIDES);
+ }
+ }
+ return stopCascading;
+ }
+
+ private ActiveRuleChange handleNewRuleActivation(RuleActivation activation, RuleActivationContext context, RuleDefinitionDto rule, ActiveRuleKey activeRuleKey) {
+ ActiveRuleChange change = new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, activeRuleKey, rule);
+ applySeverityAndParamToChange(activation, context, change);
+ if (context.isCascading() || isSameAsParent(change, context)) {
+ change.setInheritance(ActiveRuleInheritance.INHERITED);
+ }
+ return change;
+ }
+
+ private void updateProfileDates(DbSession dbSession, RuleActivationContext context) {
+ RulesProfileDto ruleProfile = context.getRulesProfile();
+ ruleProfile.setRulesUpdatedAtAsDate(new Date(context.getDate()));
+ db.qualityProfileDao().update(dbSession, ruleProfile);
+
+ if (userSession.isLoggedIn()) {
+ context.getProfiles().forEach(p -> db.qualityProfileDao().update(dbSession, OrgQProfileDto.from(p).setUserUpdatedAt(context.getDate())));
+ }
+ }
+
+ /**
+ * Update severity and params
+ */
+ private void applySeverityAndParamToChange(RuleActivation request, RuleActivationContext context, ActiveRuleChange change) {
+ RuleWrapper rule = context.getRule();
+ ActiveRuleWrapper activeRule = context.getActiveRule();
+ ActiveRuleWrapper parentActiveRule = context.getParentActiveRule();
+
+ if (request.isReset()) {
+ applySeverityAndParamsWhenResetRequested(change, rule, parentActiveRule);
+ } else if (context.getRulesProfile().isBuiltIn()) {
+ applySeverityAndParamsWhenBuiltInProfile(request, context, change, rule);
+ } else {
+ applySeverityAndParamsWhenNonBuiltInProfile(request, context, change, rule, activeRule, parentActiveRule);
+ }
+ }
+
+ private void applySeverityAndParamsWhenResetRequested(ActiveRuleChange change, RuleWrapper rule, @Nullable ActiveRuleWrapper parentActiveRule) {
+ String severity = firstNonNull(
+ parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null,
+ rule.get().getSeverityString());
+ change.setSeverity(severity);
+
+ for (RuleParamDto ruleParamDto : rule.getParams()) {
+ String paramKey = ruleParamDto.getName();
+ // load params from parent profile, else from default values
+ String paramValue = firstNonNull(
+ parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null,
+ rule.getParamDefaultValue(paramKey));
+
+ change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
+ }
+ }
+
+ private void applySeverityAndParamsWhenBuiltInProfile(RuleActivation request, RuleActivationContext context, ActiveRuleChange change,
+ RuleWrapper rule) {
+ // for builtin quality profiles, the severity from profile, when null use the default severity of the rule
+ String severity = firstNonNull(request.getSeverity(), rule.get().getSeverityString());
+ change.setSeverity(severity);
+
+ for (RuleParamDto ruleParamDto : rule.getParams()) {
+ String paramKey = ruleParamDto.getName();
+ // use the value defined in the profile definition, else the rule default value
+ String paramValue = firstNonNull(
+ context.getRequestedParamValue(request, paramKey),
+ rule.getParamDefaultValue(paramKey));
+ change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
+ }
+ }
+
+ /**
+ * 1. apply requested severity and param
+ * 2. if rule activated and overridden - apply user value
+ * 3. apply parent value
+ * 4. apply defaults
+ */
+ private void applySeverityAndParamsWhenNonBuiltInProfile(RuleActivation request, RuleActivationContext context, ActiveRuleChange change,
+ RuleWrapper rule, @Nullable ActiveRuleWrapper activeRule, @Nullable ActiveRuleWrapper parentActiveRule) {
+ String severity = getSeverityForNonBuiltInProfile(request, rule, activeRule, parentActiveRule);
+ change.setSeverity(severity);
+
+ for (RuleParamDto ruleParamDto : rule.getParams()) {
+ String paramKey = ruleParamDto.getName();
+ String parentValue = parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null;
+ String paramValue;
+ if (context.hasRequestedParamValue(request, paramKey)) {
+ // If the request contains the parameter then we're using either value from request, or parent value, or default value
+ paramValue = firstNonNull(
+ context.getRequestedParamValue(request, paramKey),
+ parentValue,
+ rule.getParamDefaultValue(paramKey));
+ } else if (activeRule != null) {
+ // If the request doesn't contain the parameter, then we're using either user value from db, or parent value if rule inherited, or default
+ // value
+ paramValue = firstNonNull(
+ activeRule.get().doesOverride() ? activeRule.getParamValue(paramKey) : null,
+ parentValue == null ? activeRule.getParamValue(paramKey) : parentValue,
+ rule.getParamDefaultValue(paramKey));
+ } else {
+ paramValue = firstNonNull(
+ parentValue,
+ rule.getParamDefaultValue(paramKey));
+ }
+ change.setParameter(paramKey, validateParam(ruleParamDto, paramValue));
+ }
+ }
+
+ private static String getSeverityForNonBuiltInProfile(RuleActivation request, RuleWrapper rule, @Nullable ActiveRuleWrapper activeRule,
+ @Nullable ActiveRuleWrapper parentActiveRule) {
+ String severity;
+ if (activeRule != null) {
+ ActiveRuleDto activeRuleDto = activeRule.get();
+ // load severity from request, else keep existing one (if overridden), else from parent if rule inherited, else from default
+ severity = firstNonNull(
+ request.getSeverity(),
+ activeRuleDto.doesOverride() ? activeRuleDto.getSeverityString() : null,
+ parentActiveRule != null ? parentActiveRule.get().getSeverityString() : activeRuleDto.getSeverityString(),
+ rule.get().getSeverityString());
+ } else {
+ // load severity from request, else from parent, else from default
+ severity = firstNonNull(
+ request.getSeverity(),
+ parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null,
+ rule.get().getSeverityString());
+ }
+ return severity;
+ }
+
+ private List<ActiveRuleChange> propagateActivationToDescendants(DbSession dbSession, RuleActivation activation, RuleActivationContext context) {
+ List<ActiveRuleChange> changes = new ArrayList<>();
+
+ // get all inherited profiles
+ context.getChildProfiles().forEach(child -> {
+ context.selectChild(child);
+ changes.addAll(doActivate(dbSession, activation, context));
+ });
+ return changes;
+ }
+
+ private void persist(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
+ ActiveRuleDto activeRule = null;
+ if (change.getType() == ActiveRuleChange.Type.ACTIVATED) {
+ activeRule = doInsert(change, context, dbSession);
+ } else if (change.getType() == ActiveRuleChange.Type.DEACTIVATED) {
+ ActiveRuleDao dao = db.activeRuleDao();
+ activeRule = dao.delete(dbSession, change.getKey()).orElse(null);
+
+ } else if (change.getType() == ActiveRuleChange.Type.UPDATED) {
+ activeRule = doUpdate(change, context, dbSession);
+ }
+ change.setActiveRule(activeRule);
+ db.qProfileChangeDao().insert(dbSession, change.toDto(userSession.getUuid()));
+ }
+
+ private ActiveRuleDto doInsert(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
+ ActiveRuleDao dao = db.activeRuleDao();
+ RuleWrapper rule = context.getRule();
+
+ ActiveRuleDto activeRule = new ActiveRuleDto();
+ activeRule.setProfileUuid(context.getRulesProfile().getUuid());
+ activeRule.setRuleUuid(rule.get().getUuid());
+ activeRule.setKey(ActiveRuleKey.of(context.getRulesProfile(), rule.get().getKey()));
+ String severity = change.getSeverity();
+ if (severity != null) {
+ activeRule.setSeverity(severity);
+ }
+ ActiveRuleInheritance inheritance = change.getInheritance();
+ if (inheritance != null) {
+ activeRule.setInheritance(inheritance.name());
+ }
+ activeRule.setUpdatedAt(system2.now());
+ activeRule.setCreatedAt(system2.now());
+ dao.insert(dbSession, activeRule);
+ for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
+ if (param.getValue() != null) {
+ ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(rule.getParam(param.getKey()));
+ paramDto.setValue(param.getValue());
+ dao.insertParam(dbSession, activeRule, paramDto);
+ }
+ }
+ return activeRule;
+ }
+
+ private ActiveRuleDto doUpdate(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) {
+ ActiveRuleWrapper activeRule = context.getActiveRule();
+ if (activeRule == null) {
+ return null;
+ }
+ ActiveRuleDao dao = db.activeRuleDao();
+ String severity = change.getSeverity();
+ if (severity != null) {
+ activeRule.get().setSeverity(severity);
+ }
+ ActiveRuleInheritance inheritance = change.getInheritance();
+ if (inheritance != null) {
+ activeRule.get().setInheritance(inheritance.name());
+ }
+ activeRule.get().setUpdatedAt(system2.now());
+ dao.update(dbSession, activeRule.get());
+
+ for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
+ ActiveRuleParamDto activeRuleParamDto = activeRule.getParam(param.getKey());
+ if (activeRuleParamDto == null) {
+ // did not exist
+ if (param.getValue() != null) {
+ activeRuleParamDto = ActiveRuleParamDto.createFor(context.getRule().getParam(param.getKey()));
+ activeRuleParamDto.setValue(param.getValue());
+ dao.insertParam(dbSession, activeRule.get(), activeRuleParamDto);
+ }
+ } else {
+ if (param.getValue() != null) {
+ activeRuleParamDto.setValue(param.getValue());
+ dao.updateParam(dbSession, activeRuleParamDto);
+ } else {
+ dao.deleteParam(dbSession, activeRuleParamDto);
+ }
+ }
+ }
+ return activeRule.get();
+ }
+
+ public List<ActiveRuleChange> deactivate(DbSession dbSession, RuleActivationContext context, String ruleUuid, boolean force) {
+ context.reset(ruleUuid);
+ return doDeactivate(dbSession, context, force);
+ }
+
+ private List<ActiveRuleChange> doDeactivate(DbSession dbSession, RuleActivationContext context, boolean force) {
+ List<ActiveRuleChange> changes = new ArrayList<>();
+ ActiveRuleWrapper activeRule = context.getActiveRule();
+ if (activeRule == null) {
+ return changes;
+ }
+
+ ActiveRuleChange change;
+ checkRequest(force || context.isCascading() || activeRule.get().getInheritance() == null, "Cannot deactivate inherited rule '%s'", context.getRule().get().getKey());
+ change = new ActiveRuleChange(ActiveRuleChange.Type.DEACTIVATED, activeRule.get(), context.getRule().get());
+ changes.add(change);
+ persist(change, context, dbSession);
+
+ // get all inherited profiles (they are not built-in by design)
+ context.getChildProfiles().forEach(child -> {
+ context.selectChild(child);
+ changes.addAll(doDeactivate(dbSession, context, force));
+ });
+
+ if (!changes.isEmpty()) {
+ updateProfileDates(dbSession, context);
+ }
+
+ return changes;
+ }
+
+ @CheckForNull
+ private String validateParam(RuleParamDto ruleParam, @Nullable String value) {
+ if (value != null) {
+ RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType());
+ if (ruleParamType.multiple()) {
+ List<String> values = Splitter.on(",").splitToList(value);
+ typeValidations.validate(values, ruleParamType.type(), ruleParamType.values());
+ } else {
+ typeValidations.validate(value, ruleParamType.type(), ruleParamType.values());
+ }
+ }
+ return value;
+ }
+
+ public RuleActivationContext createContextForBuiltInProfile(DbSession dbSession, RulesProfileDto builtInProfile, Collection<String> ruleUuids) {
+ checkArgument(builtInProfile.isBuiltIn(), "Rules profile with UUID %s is not built-in", builtInProfile.getUuid());
+
+ RuleActivationContext.Builder builder = new RuleActivationContext.Builder();
+ builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession));
+
+ // load rules
+ completeWithRules(dbSession, builder, ruleUuids);
+
+ // load org profiles. Their parents are null by nature.
+ List<QProfileDto> profiles = db.qualityProfileDao().selectQProfilesByRuleProfile(dbSession, builtInProfile);
+ builder.setProfiles(profiles);
+ builder.setBaseProfile(builtInProfile);
+
+ // load active rules
+ Collection<String> ruleProfileUuids = Stream
+ .concat(Stream.of(builtInProfile.getUuid()), profiles.stream().map(QProfileDto::getRulesProfileUuid))
+ .collect(MoreCollectors.toHashSet(profiles.size() + 1));
+ completeWithActiveRules(dbSession, builder, ruleUuids, ruleProfileUuids);
+ return builder.build();
+ }
+
+ public RuleActivationContext createContextForUserProfile(DbSession dbSession, QProfileDto profile, Collection<String> ruleUuids) {
+ checkArgument(!profile.isBuiltIn(), "Profile with UUID %s is built-in", profile.getKee());
+ RuleActivationContext.Builder builder = new RuleActivationContext.Builder();
+ builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession));
+
+ // load rules
+ completeWithRules(dbSession, builder, ruleUuids);
+
+ // load profiles
+ List<QProfileDto> profiles = new ArrayList<>();
+ profiles.add(profile);
+ if (profile.getParentKee() != null) {
+ profiles.add(db.qualityProfileDao().selectByUuid(dbSession, profile.getParentKee()));
+ }
+ builder.setProfiles(profiles);
+ builder.setBaseProfile(RulesProfileDto.from(profile));
+
+ // load active rules
+ Collection<String> ruleProfileUuids = profiles.stream()
+ .map(QProfileDto::getRulesProfileUuid)
+ .collect(MoreCollectors.toHashSet(profiles.size()));
+ completeWithActiveRules(dbSession, builder, ruleUuids, ruleProfileUuids);
+
+ return builder.build();
+ }
+
+ DescendantProfilesSupplier createDescendantProfilesSupplier(DbSession dbSession) {
+ return (parents, ruleUuids) -> {
+ Collection<QProfileDto> profiles = db.qualityProfileDao().selectDescendants(dbSession, parents);
+ Set<String> ruleProfileUuids = profiles.stream()
+ .map(QProfileDto::getRulesProfileUuid)
+ .collect(MoreCollectors.toHashSet());
+ Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleUuids, ruleProfileUuids);
+ List<String> activeRuleUuids = activeRules.stream().map(ActiveRuleDto::getUuid).collect(MoreCollectors.toArrayList(activeRules.size()));
+ List<ActiveRuleParamDto> activeRuleParams = db.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids);
+ return new DescendantProfilesSupplier.Result(profiles, activeRules, activeRuleParams);
+ };
+ }
+
+ private void completeWithRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<String> ruleUuids) {
+ List<RuleDefinitionDto> rules = db.ruleDao().selectDefinitionByUuids(dbSession, ruleUuids);
+ builder.setRules(rules);
+ builder.setRuleParams(db.ruleDao().selectRuleParamsByRuleUuids(dbSession, ruleUuids));
+ }
+
+ private void completeWithActiveRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<String> ruleUuids, Collection<String> ruleProfileUuids) {
+ Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleUuids, ruleProfileUuids);
+ builder.setActiveRules(activeRules);
+ List<String> activeRuleUuids = activeRules.stream().map(ActiveRuleDto::getUuid).collect(MoreCollectors.toArrayList(activeRules.size()));
+ builder.setActiveRuleParams(db.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, activeRuleUuids));
+ }
+
+ private static boolean isSame(ActiveRuleChange change, ActiveRuleWrapper activeRule) {
+ ActiveRuleInheritance inheritance = change.getInheritance();
+ if (inheritance != null && !inheritance.name().equals(activeRule.get().getInheritance())) {
+ return false;
+ }
+ String severity = change.getSeverity();
+ if (severity != null && !severity.equals(activeRule.get().getSeverityString())) {
+ return false;
+ }
+ for (Map.Entry<String, String> changeParam : change.getParameters().entrySet()) {
+ String activeParamValue = activeRule.getParamValue(changeParam.getKey());
+ if (changeParam.getValue() == null && activeParamValue != null) {
+ return false;
+ }
+ if (changeParam.getValue() != null && (activeParamValue == null || !StringUtils.equals(changeParam.getValue(), activeParamValue))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * True if trying to override an inherited rule but with exactly the same values
+ */
+ private static boolean isSameAsParent(ActiveRuleChange change, RuleActivationContext context) {
+ ActiveRuleWrapper parentActiveRule = context.getParentActiveRule();
+ if (parentActiveRule == null) {
+ return false;
+ }
+ if (!StringUtils.equals(change.getSeverity(), parentActiveRule.get().getSeverityString())) {
+ return false;
+ }
+ for (Map.Entry<String, String> entry : change.getParameters().entrySet()) {
+ if (entry.getValue() != null && !entry.getValue().equals(parentActiveRule.getParamValue(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @CheckForNull
+ private static String firstNonNull(String... strings) {
+ for (String s : strings) {
+ if (s != null) {
+ return s;
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.qualityprofile.builtin;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
*/
package org.sonar.server.qualityprofile.ws;
-import java.util.Optional;
import org.sonar.api.resources.Languages;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.db.project.ProjectDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.user.UserSession;
-import static java.util.Optional.empty;
-import static java.util.Optional.of;
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_ADD_PROJECT;
checkPermissions(dbSession, profile, project);
QProfileDto currentProfile = dbClient.qualityProfileDao().selectAssociatedToProjectAndLanguage(dbSession, project, profile.getLanguage());
- Optional<QProfileDto> deactivatedProfile = empty();
+ QProfileDto deactivatedProfile = null;
if (currentProfile == null) {
QProfileDto defaultProfile = dbClient.qualityProfileDao().selectDefaultProfile(dbSession, profile.getLanguage());
if (defaultProfile != null) {
- deactivatedProfile = of(defaultProfile);
+ deactivatedProfile = defaultProfile;
}
// project uses the default profile
dbClient.qualityProfileDao().insertProjectProfileAssociation(dbSession, project, profile);
dbSession.commit();
} else if (!profile.getKee().equals(currentProfile.getKee())) {
- deactivatedProfile = of(currentProfile);
+ deactivatedProfile = currentProfile;
dbClient.qualityProfileDao().updateProjectProfileAssociation(dbSession, project, profile.getKee(), currentProfile.getKee());
dbSession.commit();
}
- Optional<QProfileDto> activatedProfile = of(profile);
- qualityProfileChangeEventService.publishRuleActivationToSonarLintClients(project, activatedProfile, deactivatedProfile);
+ qualityProfileChangeEventService.publishRuleActivationToSonarLintClients(project, profile, deactivatedProfile);
}
response.noContent();
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonar.server.qualityprofile.QProfileFactory;
-import org.sonar.server.qualityprofile.QProfileName;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
import org.sonar.server.qualityprofile.QProfileResult;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.user.UserSession;
*/
package org.sonar.server.qualityprofile.ws;
-import java.util.Optional;
import org.sonar.api.resources.Languages;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.db.project.ProjectDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.user.UserSession;
-import static java.util.Optional.empty;
-import static java.util.Optional.of;
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_REMOVE_PROJECT;
dbClient.qualityProfileDao().deleteProjectProfileAssociation(dbSession, project, profile);
dbSession.commit();
- Optional<QProfileDto> deactivatedProfile = of(profile);
- Optional<QProfileDto> activatedProfile = empty();
+ QProfileDto activatedProfile = null;
// publish change for rules in the default quality profile
QProfileDto defaultProfile = dbClient.qualityProfileDao().selectDefaultProfile(dbSession, profile.getLanguage());
if (defaultProfile != null) {
- activatedProfile = of(defaultProfile);
+ activatedProfile = defaultProfile;
}
- qualityProfileChangeEventService.publishRuleActivationToSonarLintClients(project, activatedProfile, deactivatedProfile);
+ qualityProfileChangeEventService.publishRuleActivationToSonarLintClients(project, activatedProfile, profile);
response.noContent();
}
import org.sonar.db.qualityprofile.QualityProfileTesting;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
import org.sonar.server.rule.RuleCreator;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.rule.RuleTesting;
import org.sonar.server.es.EsTester;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.QProfileComparison.ActiveRuleDiff;
import org.sonar.server.qualityprofile.QProfileComparison.QProfileComparisonResult;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.db.DbTester;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.QualityProfileTesting;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import static java.util.Arrays.asList;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.QualityProfileTesting;
import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.util.IntegerTypeValidation;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.es.EsTester;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.util.IntegerTypeValidation;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.RulesProfileDto;
import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfile;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsert;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsertImpl;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileRepositoryRule;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdate;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdateImpl;
+import org.sonar.server.qualityprofile.builtin.BuiltInQualityProfilesUpdateListener;
+import org.sonar.server.qualityprofile.builtin.QProfileName;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.rule.ServerRuleFinder;
import org.sonar.db.qualityprofile.RulesProfileDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfile;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsert;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileRepositoryRule;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdate;
+import org.sonar.server.qualityprofile.builtin.BuiltInQualityProfilesUpdateListener;
import org.sonar.server.tester.UserSessionRule;
import static java.lang.String.format;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.IntStream;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.EmailSubscriberDto;
+import org.sonar.db.permission.AuthorizationDao;
+import org.sonar.server.notification.email.EmailNotificationChannel;
+
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toSet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class BuiltInQPChangeNotificationHandlerTest {
+ private DbClient dbClient = mock(DbClient.class);
+ private DbSession dbSession = mock(DbSession.class);
+ private AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
+ private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
+
+ private BuiltInQPChangeNotificationHandler underTest = new BuiltInQPChangeNotificationHandler(dbClient, emailNotificationChannel);
+
+ @Before
+ public void wire_mocks() {
+ when(dbClient.openSession(false)).thenReturn(dbSession);
+ when(dbClient.authorizationDao()).thenReturn(authorizationDao);
+ }
+
+ @Test
+ public void getMetadata_returns_empty() {
+ assertThat(underTest.getMetadata()).isEmpty();
+ }
+
+ @Test
+ public void getNotificationClass_is_BuiltInQPChangeNotification() {
+ assertThat(underTest.getNotificationClass()).isEqualTo(BuiltInQPChangeNotification.class);
+ }
+
+ @Test
+ public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() {
+ when(emailNotificationChannel.isActivated()).thenReturn(false);
+ Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+ .mapToObj(i -> mock(BuiltInQPChangeNotification.class))
+ .collect(toSet());
+
+ int deliver = underTest.deliver(notifications);
+
+ assertThat(deliver).isZero();
+ verify(emailNotificationChannel).isActivated();
+ verifyNoMoreInteractions(emailNotificationChannel);
+ verifyZeroInteractions(dbClient);
+ notifications.forEach(Mockito::verifyZeroInteractions);
+ }
+
+ @Test
+ public void deliver_has_no_effect_if_there_is_no_global_administer_email_subscriber() {
+ when(emailNotificationChannel.isActivated()).thenReturn(true);
+ Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+ .mapToObj(i -> mock(BuiltInQPChangeNotification.class))
+ .collect(toSet());
+ when(authorizationDao.selectQualityProfileAdministratorLogins(dbSession))
+ .thenReturn(emptySet());
+
+ int deliver = underTest.deliver(notifications);
+
+ assertThat(deliver).isZero();
+ verify(emailNotificationChannel).isActivated();
+ verifyNoMoreInteractions(emailNotificationChannel);
+ verify(dbClient).openSession(false);
+ verify(dbClient).authorizationDao();
+ verifyNoMoreInteractions(dbClient);
+ verify(authorizationDao).selectQualityProfileAdministratorLogins(dbSession);
+ verifyNoMoreInteractions(authorizationDao);
+ notifications.forEach(Mockito::verifyZeroInteractions);
+ }
+
+ @Test
+ public void deliver_create_emailRequest_for_each_notification_and_for_each_global_administer_email_subscriber() {
+ when(emailNotificationChannel.isActivated()).thenReturn(true);
+ Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+ .mapToObj(i -> mock(BuiltInQPChangeNotification.class))
+ .collect(toSet());
+ Set<EmailSubscriberDto> emailSubscribers = IntStream.range(0, 1 + new Random().nextInt(10))
+ .mapToObj(i -> EmailSubscriberDto.create("login_" + i, true, "login_" + i + "@foo"))
+ .collect(toSet());
+ when(authorizationDao.selectQualityProfileAdministratorLogins(dbSession))
+ .thenReturn(emailSubscribers);
+ Set<EmailNotificationChannel.EmailDeliveryRequest> expectedRequests = notifications.stream()
+ .flatMap(notification -> emailSubscribers.stream().map(subscriber -> new EmailNotificationChannel.EmailDeliveryRequest(subscriber.getEmail(), notification)))
+ .collect(toSet());
+ int deliveries = new Random().nextInt(expectedRequests.size());
+ when(emailNotificationChannel.deliverAll(expectedRequests)).thenReturn(deliveries);
+
+ int deliver = underTest.deliver(notifications);
+
+ assertThat(deliver).isEqualTo(deliveries);
+ verify(emailNotificationChannel).isActivated();
+ verify(emailNotificationChannel).deliverAll(expectedRequests);
+ verifyNoMoreInteractions(emailNotificationChannel);
+ verify(dbClient).openSession(false);
+ verify(dbClient).authorizationDao();
+ verifyNoMoreInteractions(dbClient);
+ verify(authorizationDao).selectQualityProfileAdministratorLogins(dbSession);
+ verifyNoMoreInteractions(authorizationDao);
+ notifications.forEach(Mockito::verifyZeroInteractions);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.Date;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.platform.Server;
+import org.sonar.server.issue.notification.EmailMessage;
+import org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationBuilder.Profile;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.utils.DateUtils.formatDate;
+
+public class BuiltInQPChangeNotificationTemplateTest {
+
+ private Server server = mock(Server.class);
+
+ private BuiltInQPChangeNotificationTemplate underTest = new BuiltInQPChangeNotificationTemplate(server);
+
+ @Before
+ public void setUp() {
+ when(server.getPublicRootUrl()).thenReturn("http://" + randomAlphanumeric(10));
+ }
+
+ @Test
+ public void notification_contains_a_subject() {
+ String profileName = newProfileName();
+ String languageKey = newLanguageKey();
+ String languageName = newLanguageName();
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setNewRules(2)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertThat(emailMessage.getSubject()).isEqualTo("Built-in quality profiles have been updated");
+ }
+
+ @Test
+ public void notification_contains_count_of_new_rules() {
+ String profileName = newProfileName();
+ String languageKey = newLanguageKey();
+ String languageName = newLanguageName();
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setNewRules(2)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertMessage(emailMessage, "\n 2 new rules\n");
+ }
+
+ @Test
+ public void notification_contains_count_of_updated_rules() {
+ String profileName = newProfileName();
+ String languageKey = newLanguageKey();
+ String languageName = newLanguageName();
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setUpdatedRules(2)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertMessage(emailMessage, "\n 2 rules have been updated\n");
+ }
+
+ @Test
+ public void notification_contains_count_of_removed_rules() {
+ String profileName = newProfileName();
+ String languageKey = newLanguageKey();
+ String languageName = newLanguageName();
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setRemovedRules(2)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertMessage(emailMessage, "\n 2 rules removed\n");
+ }
+
+ @Test
+ public void notification_supports_grammar_for_single_rule_added_removed_or_updated() {
+ String profileName = newProfileName();
+ String languageKey = newLanguageKey();
+ String languageName = newLanguageName();
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setNewRules(1)
+ .setUpdatedRules(1)
+ .setRemovedRules(1)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertThat(emailMessage.getMessage())
+ .contains("\n 1 new rule\n")
+ .contains("\n 1 rule has been updated\n")
+ .contains("\n 1 rule removed\n");
+ }
+
+ @Test
+ public void notification_contains_list_of_new_updated_and_removed_rules() {
+ String profileName = newProfileName();
+ String languageKey = newLanguageKey();
+ String languageName = newLanguageName();
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setNewRules(2)
+ .setUpdatedRules(3)
+ .setRemovedRules(4)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertMessage(emailMessage,
+ "\n" +
+ " 2 new rules\n" +
+ " 3 rules have been updated\n" +
+ " 4 rules removed\n");
+ }
+
+ @Test
+ public void notification_contains_many_profiles() {
+ String profileName1 = "profile1_" + randomAlphanumeric(20);
+ String languageKey1 = "langkey1_" + randomAlphanumeric(20);
+ String languageName1 = "langName1_" + randomAlphanumeric(20);
+ String profileName2 = "profile2_" + randomAlphanumeric(20);
+ String languageKey2 = "langkey2_" + randomAlphanumeric(20);
+ String languageName2 = "langName2_" + randomAlphanumeric(20);
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName1)
+ .setLanguageKey(languageKey1)
+ .setLanguageName(languageName1)
+ .setNewRules(2)
+ .build())
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName2)
+ .setLanguageKey(languageKey2)
+ .setLanguageName(languageName2)
+ .setNewRules(13)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertThat(emailMessage.getMessage()).containsSubsequence("The following built-in profiles have been updated:\n",
+ profileTitleText(profileName1, languageKey1, languageName1),
+ " 2 new rules\n",
+ profileTitleText(profileName2, languageKey2, languageName2),
+ " 13 new rules\n",
+ "This is a good time to review your quality profiles and update them to benefit from the latest evolutions: " + server.getPublicRootUrl() + "/profiles");
+ }
+
+ @Test
+ public void notification_contains_profiles_sorted_by_language_then_by_profile_name() {
+ String languageKey1 = "langkey1_" + randomAlphanumeric(20);
+ String languageName1 = "langName1_" + randomAlphanumeric(20);
+ String languageKey2 = "langKey2_" + randomAlphanumeric(20);
+ String languageName2 = "langName2_" + randomAlphanumeric(20);
+ String profileName1 = "profile1_" + randomAlphanumeric(20);
+ String profileName2 = "profile2_" + randomAlphanumeric(20);
+ String profileName3 = "profile3_" + randomAlphanumeric(20);
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder().setProfileName(profileName3).setLanguageKey(languageKey2).setLanguageName(languageName2).build())
+ .addProfile(Profile.newBuilder().setProfileName(profileName2).setLanguageKey(languageKey1).setLanguageName(languageName1).build())
+ .addProfile(Profile.newBuilder().setProfileName(profileName1).setLanguageKey(languageKey2).setLanguageName(languageName2).build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertThat(emailMessage.getMessage()).containsSubsequence(
+ "\"" + profileName2 + "\" - " + languageName1,
+ "\"" + profileName1 + "\" - " + languageName2,
+ "\"" + profileName3 + "\" - " + languageName2);
+ }
+
+ @Test
+ public void notification_contains_encoded_profile_name() {
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName("Sonar Way")
+ .setLanguageKey("java")
+ .setLanguageName(newLanguageName())
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertThat(emailMessage.getMessage()).contains(server.getPublicRootUrl() + "/profiles/changelog?language=java&name=Sonar+Way");
+ }
+
+ @Test
+ public void notification_contains_from_and_to_date() {
+ String profileName = newProfileName();
+ String languageKey = newLanguageKey();
+ String languageName = newLanguageName();
+ long startDate = 1_000_000_000_000L;
+ long endDate = startDate + 1_100_000_000_000L;
+ BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setStartDate(startDate)
+ .setEndDate(endDate)
+ .build());
+
+ EmailMessage emailMessage = underTest.format(notification.build());
+
+ assertMessage(emailMessage,
+ profileTitleText(profileName, languageKey, languageName, formatDate(new Date(startDate)), formatDate(new Date(endDate))));
+ }
+
+ private void assertMessage(EmailMessage emailMessage, String expectedProfileDetails) {
+ assertThat(emailMessage.getMessage())
+ .containsSubsequence(
+ "The following built-in profiles have been updated:\n\n",
+ expectedProfileDetails,
+ "\nThis is a good time to review your quality profiles and update them to benefit from the latest evolutions: " + server.getPublicRootUrl() + "/profiles");
+ }
+
+ private String profileTitleText(String profileName, String languageKey, String languageName) {
+ return "\"" + profileName + "\" - " + languageName + ": " + server.getPublicRootUrl() + "/profiles/changelog?language=" + languageKey + "&name=" + profileName;
+ }
+
+ private String profileTitleText(String profileName, String languageKey, String languageName, String startDate, String endDate) {
+ return "\"" + profileName + "\" - " + languageName + ": " + server.getPublicRootUrl() + "/profiles/changelog?language=" + languageKey + "&name=" + profileName +
+ "&since=" + startDate + "&to=" + endDate + "\n";
+ }
+
+ private static String newProfileName() {
+ return "profileName_" + randomAlphanumeric(20);
+ }
+
+ private static String newLanguageName() {
+ return "languageName_" + randomAlphanumeric(20);
+ }
+
+ private static String newLanguageKey() {
+ return "languageKey_" + randomAlphanumeric(20);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.Random;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationBuilder.Profile;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class BuiltInQPChangeNotificationTest {
+
+ private static final Random RANDOM = new Random();
+
+
+ @Test
+ public void serialize_and_parse_no_profile() {
+ Notification notification = new BuiltInQPChangeNotificationBuilder().build();
+
+ BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
+
+ assertThat(result.getProfiles()).isEmpty();
+ }
+
+ @Test
+ public void serialize_and_parse_single_profile() {
+ String profileName = randomAlphanumeric(20);
+ String languageKey = randomAlphanumeric(20);
+ String languageName = randomAlphanumeric(20);
+ int newRules = RANDOM.nextInt(5000);
+ int updatedRules = RANDOM.nextInt(5000);
+ int removedRules = RANDOM.nextInt(5000);
+ long startDate = RANDOM.nextInt(5000);
+ long endDate = startDate + RANDOM.nextInt(5000);
+
+ BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setNewRules(newRules)
+ .setUpdatedRules(updatedRules)
+ .setRemovedRules(removedRules)
+ .setStartDate(startDate)
+ .setEndDate(endDate)
+ .build())
+ .build();
+ BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
+
+ assertThat(result.getProfiles())
+ .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules, Profile::getUpdatedRules, Profile::getRemovedRules,
+ Profile::getStartDate, Profile::getEndDate)
+ .containsExactlyInAnyOrder(tuple(profileName, languageKey, languageName, newRules, updatedRules, removedRules, startDate, endDate));
+ }
+
+ @Test
+ public void serialize_and_parse_multiple_profiles() {
+ String profileName1 = randomAlphanumeric(20);
+ String languageKey1 = randomAlphanumeric(20);
+ String languageName1 = randomAlphanumeric(20);
+ String profileName2 = randomAlphanumeric(20);
+ String languageKey2 = randomAlphanumeric(20);
+ String languageName2 = randomAlphanumeric(20);
+
+ BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName1)
+ .setLanguageKey(languageKey1)
+ .setLanguageName(languageName1)
+ .build())
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName2)
+ .setLanguageKey(languageKey2)
+ .setLanguageName(languageName2)
+ .build())
+ .build();
+ BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
+
+ assertThat(result.getProfiles()).extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName)
+ .containsExactlyInAnyOrder(tuple(profileName1, languageKey1, languageName1), tuple(profileName2, languageKey2, languageName2));
+ }
+
+ @Test
+ public void serialize_and_parse_max_values() {
+ String profileName = randomAlphanumeric(20);
+ String languageKey = randomAlphanumeric(20);
+ String languageName = randomAlphanumeric(20);
+ int newRules = Integer.MAX_VALUE;
+ int updatedRules = Integer.MAX_VALUE;
+ int removedRules = Integer.MAX_VALUE;
+ long startDate = Long.MAX_VALUE;
+ long endDate = Long.MAX_VALUE;
+
+ BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder()
+ .addProfile(Profile.newBuilder()
+ .setProfileName(profileName)
+ .setLanguageKey(languageKey)
+ .setLanguageName(languageName)
+ .setNewRules(newRules)
+ .setUpdatedRules(updatedRules)
+ .setRemovedRules(removedRules)
+ .setStartDate(startDate)
+ .setEndDate(endDate)
+ .build())
+ .build();
+ BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification);
+
+ assertThat(result.getProfiles())
+ .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules, Profile::getUpdatedRules, Profile::getRemovedRules,
+ Profile::getStartDate, Profile::getEndDate)
+ .containsExactlyInAnyOrder(tuple(profileName, languageKey, languageName, newRules, updatedRules, removedRules, startDate, endDate));
+ }
+
+ @Test
+ public void fail_with_ISE_when_parsing_empty_notification() {
+ assertThatThrownBy(() -> BuiltInQPChangeNotificationBuilder.parse(new Notification(BuiltInQPChangeNotification.TYPE)))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Could not read the built-in quality profile notification");
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.PropertyType;
+import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QProfileChangeDto;
+import org.sonar.db.qualityprofile.QProfileChangeQuery;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.rule.ServerRuleFinder;
+import org.sonar.server.util.StringTypeValidation;
+import org.sonar.server.util.TypeValidations;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class BuiltInQProfileInsertImplTest {
+
+ @Rule
+ public BuiltInQProfileRepositoryRule builtInQProfileRepository = new BuiltInQProfileRepositoryRule();
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private final System2 system2 = new AlwaysIncreasingSystem2();
+ private final UuidFactory uuidFactory = new SequenceUuidFactory();
+ private final TypeValidations typeValidations = new TypeValidations(singletonList(new StringTypeValidation()));
+ private final DbSession dbSession = db.getSession();
+ private final DbSession batchDbSession = db.getDbClient().openSession(true);
+ private final ServerRuleFinder ruleFinder = new DefaultRuleFinder(db.getDbClient());
+ private final ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
+ private final BuiltInQProfileInsertImpl underTest = new BuiltInQProfileInsertImpl(db.getDbClient(), ruleFinder, system2, uuidFactory, typeValidations, activeRuleIndexer);
+
+ @After
+ public void tearDown() {
+ batchDbSession.close();
+ }
+
+ @Test
+ public void insert_active_rules_and_changelog() {
+ RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo");
+
+ newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+ newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+ newQp.done();
+
+ BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"), rule1, rule2);
+ call(builtIn);
+
+ verifyTableSize("rules_profiles", 1);
+ verifyTableSize("org_qprofiles", 1);
+ verifyTableSize("active_rules", 2);
+ verifyTableSize("active_rule_parameters", 0);
+ verifyTableSize("qprofile_changes", 2);
+ verifyTableSize("default_qprofiles", 0);
+
+ QProfileDto profile = verifyProfileInDb(builtIn);
+ verifyActiveRuleInDb(profile, rule1, Severity.CRITICAL);
+ verifyActiveRuleInDb(profile, rule2, Severity.MAJOR);
+ }
+
+ @Test
+ public void insert_default_qp() {
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ context.createBuiltInQualityProfile("the name", "xoo")
+ .setDefault(true)
+ .done();
+
+ BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
+ call(builtIn);
+
+ verifyTableSize("rules_profiles", 1);
+ verifyTableSize("org_qprofiles", 1);
+ verifyTableSize("active_rules", 0);
+ verifyTableSize("active_rule_parameters", 0);
+ verifyTableSize("qprofile_changes", 0);
+ verifyTableSize("default_qprofiles", 1);
+
+ verifyProfileInDb(builtIn);
+ }
+
+ @Test
+ public void insert_active_rules_with_params() {
+ RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleParamDto param1 = db.rules().insertRuleParam(rule1, p -> p.setType(PropertyType.STRING.name()));
+ RuleParamDto param2 = db.rules().insertRuleParam(rule1, p -> p.setType(PropertyType.STRING.name()));
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo");
+
+ newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+ newQp.done();
+
+ BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"), rule1);
+ call(builtIn);
+
+ verifyTableSize("rules_profiles", 1);
+ verifyTableSize("org_qprofiles", 1);
+ verifyTableSize("active_rules", 1);
+ verifyTableSize("active_rule_parameters", 2);
+ verifyTableSize("qprofile_changes", 1);
+
+ QProfileDto profile = verifyProfileInDb(builtIn);
+ verifyActiveRuleInDb(profile, rule1, Severity.CRITICAL, param1, param2);
+ }
+
+ @Test
+ public void flag_profile_as_default_if_declared_as_default_by_api() {
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
+ newQp.done();
+
+ BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
+
+ call(builtIn);
+
+ QProfileDto profile = verifyProfileInDb(builtIn);
+ QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, "xoo");
+ assertThat(defaultProfile.getKee()).isEqualTo(profile.getKee());
+ }
+
+ @Test
+ public void existing_default_profile_must_not_be_changed() {
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true);
+ newQp.done();
+ BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
+
+ QProfileDto currentDefault = db.qualityProfiles().insert(p -> p.setLanguage("xoo"));
+ db.qualityProfiles().setAsDefault(currentDefault);
+
+ call(builtIn);
+
+ QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, "xoo");
+ assertThat(defaultProfile.getKee()).isEqualTo(currentDefault.getKee());
+ verifyTableSize("rules_profiles", 2);
+ }
+
+ @Test
+ public void dont_flag_profile_as_default_if_not_declared_as_default_by_api() {
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(false);
+ newQp.done();
+ BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"));
+
+ call(builtIn);
+
+ QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, "xoo");
+ assertThat(defaultProfile).isNull();
+ }
+
+ // TODO test lot of active_rules, params, orgas
+
+ private void verifyActiveRuleInDb(QProfileDto profile, RuleDefinitionDto rule, String expectedSeverity, RuleParamDto... paramDtos) {
+ ActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByKey(dbSession, ActiveRuleKey.of(profile, rule.getKey())).get();
+ assertThat(activeRule.getUuid()).isNotNull();
+ assertThat(activeRule.getInheritance()).isNull();
+ assertThat(activeRule.doesOverride()).isFalse();
+ assertThat(activeRule.getRuleUuid()).isEqualTo(rule.getUuid());
+ assertThat(activeRule.getProfileUuid()).isEqualTo(profile.getRulesProfileUuid());
+ assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity);
+ assertThat(activeRule.getCreatedAt()).isPositive();
+ assertThat(activeRule.getUpdatedAt()).isPositive();
+
+ List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(dbSession, activeRule.getUuid());
+ assertThat(params).extracting(ActiveRuleParamDto::getKey).containsOnly(Arrays.stream(paramDtos).map(RuleParamDto::getName).toArray(String[]::new));
+
+ QProfileChangeQuery changeQuery = new QProfileChangeQuery(profile.getKee());
+ QProfileChangeDto change = db.getDbClient().qProfileChangeDao().selectByQuery(dbSession, changeQuery).stream()
+ .filter(c -> c.getDataAsMap().get("ruleUuid").equals(rule.getUuid()))
+ .findFirst()
+ .get();
+ assertThat(change.getChangeType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED.name());
+ assertThat(change.getCreatedAt()).isPositive();
+ assertThat(change.getUuid()).isNotEmpty();
+ assertThat(change.getUserUuid()).isNull();
+ assertThat(change.getRulesProfileUuid()).isEqualTo(profile.getRulesProfileUuid());
+ assertThat(change.getDataAsMap()).containsEntry("severity", expectedSeverity);
+ }
+
+ private QProfileDto verifyProfileInDb(BuiltInQProfile builtIn) {
+ QProfileDto profileOnOrg1 = db.getDbClient().qualityProfileDao().selectByNameAndLanguage(dbSession, builtIn.getName(), builtIn.getLanguage());
+ assertThat(profileOnOrg1.getLanguage()).isEqualTo(builtIn.getLanguage());
+ assertThat(profileOnOrg1.getName()).isEqualTo(builtIn.getName());
+ assertThat(profileOnOrg1.getParentKee()).isNull();
+ assertThat(profileOnOrg1.getLastUsed()).isNull();
+ assertThat(profileOnOrg1.getUserUpdatedAt()).isNull();
+ assertThat(profileOnOrg1.getRulesUpdatedAt()).isNotEmpty();
+ assertThat(profileOnOrg1.getKee()).isNotEqualTo(profileOnOrg1.getRulesProfileUuid());
+ assertThat(profileOnOrg1.getRulesProfileUuid()).isNotNull();
+ return profileOnOrg1;
+ }
+
+ private void verifyTableSize(String table, int expectedSize) {
+ assertThat(db.countRowsOfTable(dbSession, table)).as("table " + table).isEqualTo(expectedSize);
+ }
+
+ private void call(BuiltInQProfile builtIn) {
+ underTest.create(dbSession, builtIn);
+ dbSession.commit();
+ batchDbSession.commit();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfile.ActiveRule;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.rule.ServerRuleFinder;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
+import static org.sonar.db.rule.RuleTesting.EXTERNAL_XOO;
+
+
+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 DbTester db = DbTester.create();
+
+ private final DbClient dbClient = db.getDbClient();
+ private final ServerRuleFinder ruleFinder = new DefaultRuleFinder(dbClient);
+
+ @Test
+ public void create_qprofile_with_rule() {
+ RuleDefinitionDto rule1 = db.rules().insert();
+ RuleDefinitionDto rule2 = db.rules().insert();
+ db.rules().insert();
+ DummyProfileDefinition definition = new DummyProfileDefinition("foo", "foo", false, asList(rule1.getKey(), rule2.getKey()));
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definition);
+
+ underTest.initialize();
+
+ assertThat(underTest.get())
+ .extracting(BuiltInQProfile::getName)
+ .containsExactlyInAnyOrder("foo");
+ assertThat(underTest.get().get(0).getActiveRules())
+ .extracting(ActiveRule::getRuleUuid, ActiveRule::getRuleKey)
+ .containsExactlyInAnyOrder(
+ tuple(rule1.getUuid(), rule1.getKey()),
+ tuple(rule2.getUuid(), rule2.getKey()));
+ }
+
+ @Test
+ public void make_single_profile_of_a_language_default_even_if_not_flagged_as_so() {
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "foo1", false));
+
+ underTest.initialize();
+
+ assertThat(underTest.get())
+ .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault)
+ .containsExactly(tuple(FOO_LANGUAGE.getKey(), true));
+ }
+
+ @Test
+ public void make_single_profile_of_a_language_default_even_if_flagged_as_so() {
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "foo1", true));
+
+ underTest.initialize();
+
+ assertThat(underTest.get())
+ .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault)
+ .containsExactly(tuple(FOO_LANGUAGE.getKey(), true));
+ }
+
+ @Test
+ public void make_first_profile_of_a_language_default_when_none_flagged_as_so() {
+ DummyProfileDefinition[] definitions = new DummyProfileDefinition[] {new DummyProfileDefinition("foo", "foo1", false), new DummyProfileDefinition("foo", "foo2", false)};
+ String firstName = definitions[0].getName();
+ String secondName = definitions[1].getName();
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definitions);
+
+ underTest.initialize();
+
+ assertThat(underTest.get())
+ .extracting(BuiltInQProfile::getName, BuiltInQProfile::isDefault)
+ .containsExactlyInAnyOrder(tuple(firstName, true), tuple(secondName, false));
+ }
+
+ @Test
+ public void create_profile_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() {
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
+ dbClient, ruleFinder, 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();
+
+ assertThat(underTest.get())
+ .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage()))
+ .filteredOn(BuiltInQProfile::isDefault)
+ .extracting(BuiltInQProfile::getName)
+ .containsExactly(SONAR_WAY_QP_NAME);
+ }
+
+ @Test
+ public void do_not_create_Sonar_Way_as_default_if_other_profile_is_defined_as_default() {
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
+ dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", true));
+
+ underTest.initialize();
+
+ assertThat(underTest.get())
+ .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage()))
+ .filteredOn(BuiltInQProfile::isDefault)
+ .extracting(BuiltInQProfile::getName)
+ .containsExactly("goo");
+ }
+
+ @Test
+ public void match_Sonar_Way_default_with_case_sensitivity() {
+ String sonarWayInOtherCase = SONAR_WAY_QP_NAME.toUpperCase();
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(
+ dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "goo", false), new DummyProfileDefinition("foo", sonarWayInOtherCase, false));
+
+ underTest.initialize();
+
+ assertThat(underTest.get())
+ .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage()))
+ .filteredOn(BuiltInQProfile::isDefault)
+ .extracting(BuiltInQProfile::getName)
+ .containsExactly("goo");
+ }
+
+ @Test
+ public void create_no_BuiltInQProfile_when_all_definitions_apply_to_non_defined_languages() {
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages(),
+ new DummyProfileDefinition("foo", "P1", false));
+
+ underTest.initialize();
+
+ assertThat(underTest.get()).isEmpty();
+ }
+
+ @Test
+ public void create_qprofile_with_deprecated_rule() {
+ RuleDefinitionDto rule1 = db.rules().insert();
+ db.rules().insertDeprecatedKey(d -> d.setRuleUuid(rule1.getUuid()).setOldRepositoryKey("oldRepo").setOldRuleKey("oldKey"));
+ RuleDefinitionDto rule2 = db.rules().insert();
+ DummyProfileDefinition definition = new DummyProfileDefinition("foo", "foo", false,
+ asList(RuleKey.of("oldRepo", "oldKey"), rule2.getKey()));
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definition);
+
+ underTest.initialize();
+
+ assertThat(underTest.get())
+ .extracting(BuiltInQProfile::getName)
+ .containsExactlyInAnyOrder("foo");
+ assertThat(underTest.get().get(0).getActiveRules())
+ .extracting(ActiveRule::getRuleUuid, ActiveRule::getRuleKey)
+ .containsExactlyInAnyOrder(
+ tuple(rule1.getUuid(), rule1.getKey()),
+ tuple(rule2.getUuid(), rule2.getKey()));
+ }
+
+ @Test
+ public void fail_with_ISE_when_rule_does_not_exist() {
+ DummyProfileDefinition[] definitions = new DummyProfileDefinition[] {new DummyProfileDefinition("foo", "foo", false, singletonList(EXTERNAL_XOO))};
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE), definitions);
+
+ assertThatThrownBy(underTest::initialize)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage(String.format("Rule with key '%s' not found", EXTERNAL_XOO));
+ }
+
+ @Test
+ public void fail_with_ISE_when_two_profiles_with_different_name_are_default_for_the_same_language() {
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, new Languages(FOO_LANGUAGE),
+ new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo2", true));
+
+ assertThatThrownBy(underTest::initialize)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Several Quality profiles are flagged as default for the language foo: [foo1, foo2]");
+ }
+
+ @Test
+ public void get_throws_ISE_if_called_before_initialize() {
+ BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages());
+
+ assertThatThrownBy(underTest::get)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("initialize must be called first");
+ }
+
+ @Test
+ public void initialize_throws_ISE_if_called_twice() {
+ BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages());
+ underTest.initialize();
+
+ assertThatThrownBy(underTest::initialize)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("initialize must be called only once");
+ }
+
+ @Test
+ public void initialize_throws_ISE_if_language_has_no_builtin_qp() {
+ BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), ruleFinder, new Languages(FOO_LANGUAGE));
+
+ assertThatThrownBy(underTest::initialize)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("The following languages have no built-in quality profiles: foo");
+ }
+
+ private static final class DummyProfileDefinition implements BuiltInQualityProfilesDefinition {
+ private final String language;
+ private final String name;
+ private final boolean defaultProfile;
+ private final List<RuleKey> activeRuleKeys;
+
+ private DummyProfileDefinition(String language, String name, boolean defaultProfile, List<RuleKey> activeRuleKeys) {
+ this.language = language;
+ this.name = name;
+ this.defaultProfile = defaultProfile;
+ this.activeRuleKeys = activeRuleKeys;
+ }
+
+ private DummyProfileDefinition(String language, String name, boolean defaultProfile) {
+ this(language, name, defaultProfile, emptyList());
+ }
+
+ @Override
+ public void define(Context context) {
+ NewBuiltInQualityProfile builtInQualityProfile = context.createBuiltInQualityProfile(name, language);
+ activeRuleKeys.forEach(activeRuleKey -> builtInQualityProfile.activateRule(activeRuleKey.repository(), activeRuleKey.rule()));
+ builtInQualityProfile.setDefault(defaultProfile);
+ builtInQualityProfile.done();
+ }
+
+ String getName() {
+ return name;
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.junit.rules.ExternalResource;
+import org.sonar.api.resources.Language;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.rule.RuleDefinitionDto;
+
+import static com.google.common.base.Preconditions.checkState;
+
+public class BuiltInQProfileRepositoryRule extends ExternalResource implements BuiltInQProfileRepository {
+ private boolean initializeCalled = false;
+ private List<BuiltInQProfile> profiles = new ArrayList<>();
+
+ @Override
+ protected void before() {
+ this.initializeCalled = false;
+ this.profiles.clear();
+ }
+
+ @Override
+ public void initialize() {
+ checkState(!initializeCalled, "initialize must be called only once");
+ this.initializeCalled = true;
+ }
+
+ @Override
+ public List<BuiltInQProfile> get() {
+ checkState(initializeCalled, "initialize must be called first");
+
+ return ImmutableList.copyOf(profiles);
+ }
+
+ public boolean isInitialized() {
+ return initializeCalled;
+ }
+
+ public BuiltInQProfile add(Language language, String profileName) {
+ return add(language, profileName, false);
+ }
+
+ public BuiltInQProfile add(Language language, String profileName, boolean isDefault) {
+ return add(language, profileName, isDefault, new BuiltInQProfile.ActiveRule[0]);
+ }
+
+ public BuiltInQProfile add(Language language, String profileName, boolean isDefault, BuiltInQProfile.ActiveRule... rules) {
+ BuiltInQProfile builtIn = create(language, profileName, isDefault, rules);
+ profiles.add(builtIn);
+ return builtIn;
+ }
+
+ public BuiltInQProfile create(Language language, String profileName, boolean isDefault, BuiltInQProfile.ActiveRule... rules) {
+ BuiltInQProfile.Builder builder = new BuiltInQProfile.Builder()
+ .setLanguage(language.getKey())
+ .setName(profileName)
+ .setDeclaredDefault(isDefault);
+ Arrays.stream(rules).forEach(builder::addRule);
+ return builder.build();
+ }
+
+ public BuiltInQProfile create(BuiltInQualityProfilesDefinition.BuiltInQualityProfile api, RuleDefinitionDto... rules) {
+ BuiltInQProfile.Builder builder = new BuiltInQProfile.Builder()
+ .setLanguage(api.language())
+ .setName(api.name())
+ .setDeclaredDefault(api.isDefault());
+ Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = Arrays.stream(rules)
+ .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getKey));
+ api.rules().forEach(rule -> {
+ RuleKey ruleKey = RuleKey.of(rule.repoKey(), rule.ruleKey());
+ RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey);
+ Preconditions.checkState(ruleDefinition != null, "Rule '%s' not found", ruleKey);
+ builder.addRule(new BuiltInQProfile.ActiveRule(ruleDefinition.getUuid(), rule));
+ });
+ return builder
+ .build();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.assertj.core.groups.Tuple;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.OrgActiveRuleDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.ActiveRuleInheritance;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.util.IntegerTypeValidation;
+import org.sonar.server.util.StringTypeValidation;
+import org.sonar.server.util.TypeValidations;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.sonar.api.rules.RulePriority.BLOCKER;
+import static org.sonar.api.rules.RulePriority.CRITICAL;
+import static org.sonar.api.rules.RulePriority.MAJOR;
+import static org.sonar.api.rules.RulePriority.MINOR;
+import static org.sonar.db.qualityprofile.QualityProfileTesting.newRuleProfileDto;
+import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED;
+
+public class BuiltInQProfileUpdateImplTest {
+
+ private static final long NOW = 1_000;
+ private static final long PAST = NOW - 100;
+
+ @Rule
+ public BuiltInQProfileRepositoryRule builtInProfileRepository = new BuiltInQProfileRepositoryRule();
+ @Rule
+ public DbTester db = DbTester.create();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ private System2 system2 = new TestSystem2().setNow(NOW);
+ private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
+ private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation()));
+ private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
+ private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession);
+
+ private BuiltInQProfileUpdateImpl underTest = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivator, activeRuleIndexer,
+ qualityProfileChangeEventService);
+
+ private RulesProfileDto persistedProfile;
+
+ @Before
+ public void setUp() {
+ persistedProfile = newRuleProfileDto(rp -> rp
+ .setIsBuiltIn(true)
+ .setLanguage("xoo")
+ .setRulesUpdatedAt(null));
+ db.getDbClient().qualityProfileDao().insert(db.getSession(), persistedProfile);
+ db.commit();
+ }
+
+ @Test
+ public void activate_new_rules() {
+ RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+ newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+ newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2);
+
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
+
+ List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThat(activeRules).hasSize(2);
+ assertThatRuleIsNewlyActivated(activeRules, rule1, CRITICAL);
+ assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR);
+ assertThatProfileIsMarkedAsUpdated(persistedProfile);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ @Test
+ public void already_activated_rule_is_updated_in_case_of_differences() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule);
+
+ activateRuleInDb(persistedProfile, rule, BLOCKER);
+
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
+
+ List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThat(activeRules).hasSize(1);
+ assertThatRuleIsUpdated(activeRules, rule, CRITICAL);
+ assertThatProfileIsMarkedAsUpdated(persistedProfile);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ @Test
+ public void already_activated_rule_is_not_touched_if_no_differences() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule);
+
+ activateRuleInDb(persistedProfile, rule, CRITICAL);
+
+ underTest.update(db.getSession(), builtIn, persistedProfile);
+
+ List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThat(activeRules).hasSize(1);
+ assertThatRuleIsUntouched(activeRules, rule, CRITICAL);
+ assertThatProfileIsNotMarkedAsUpdated(persistedProfile);
+ verifyNoInteractions(qualityProfileChangeEventService);
+ }
+
+ @Test
+ public void deactivate_rule_that_is_not_in_built_in_definition_anymore() {
+ RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+ newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2);
+
+ // built-in definition contains only rule2
+ // so rule1 must be deactivated
+ activateRuleInDb(persistedProfile, rule1, CRITICAL);
+
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
+
+ List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThat(activeRules).hasSize(1);
+ assertThatRuleIsDeactivated(activeRules, rule1);
+ assertThatProfileIsMarkedAsUpdated(persistedProfile);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ @Test
+ public void activate_deactivate_and_update_three_rules_at_the_same_time() {
+ RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("xoo"));
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+ newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL);
+ newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR);
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2);
+
+ // rule1 must be updated (blocker to critical)
+ // rule2 must be activated
+ // rule3 must be deactivated
+ activateRuleInDb(persistedProfile, rule1, BLOCKER);
+ activateRuleInDb(persistedProfile, rule3, BLOCKER);
+
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
+
+ List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThat(activeRules).hasSize(2);
+ assertThatRuleIsUpdated(activeRules, rule1, CRITICAL);
+ assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR);
+ assertThatRuleIsDeactivated(activeRules, rule3);
+ assertThatProfileIsMarkedAsUpdated(persistedProfile);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ // SONAR-10473
+ @Test
+ public void activate_rule_on_built_in_profile_resets_severity_to_default_if_not_overridden() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setSeverity(Severity.MAJOR).setLanguage("xoo"));
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo");
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule);
+ underTest.update(db.getSession(), builtIn, persistedProfile);
+
+ List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThatRuleIsNewlyActivated(activeRules, rule, MAJOR);
+
+ // emulate an upgrade of analyzer that changes the default severity of the rule
+ rule.setSeverity(Severity.MINOR);
+ db.rules().update(rule);
+
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
+ activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThatRuleIsNewlyActivated(activeRules, rule, MINOR);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ @Test
+ public void activate_rule_on_built_in_profile_resets_params_to_default_if_not_overridden() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", rule.getLanguage());
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(newQp.language(), newQp.name()), rule);
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, persistedProfile);
+
+ List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThat(activeRules).hasSize(1);
+ assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "10"));
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+
+ // emulate an upgrade of analyzer that changes the default value of parameter min
+ ruleParam.setDefaultValue("20");
+ db.getDbClient().ruleDao().updateRuleParam(db.getSession(), rule, ruleParam);
+
+ changes = underTest.update(db.getSession(), builtIn, persistedProfile);
+ activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile);
+ assertThat(activeRules).hasSize(1);
+ assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "20"));
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ @Test
+ public void propagate_activation_to_descendant_profiles() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
+
+ QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+ QProfileDto childProfile = createChildProfile(profile);
+ QProfileDto grandchildProfile = createChildProfile(childProfile);
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
+
+ assertThat(changes).hasSize(3);
+ assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, emptyMap());
+ assertThatRuleIsActivated(childProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap());
+ assertThatRuleIsActivated(grandchildProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap());
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ // SONAR-14559
+ @Test
+ public void propagate_rule_update_to_descendant_active_rule() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
+
+ QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+ activateRuleInDb(RulesProfileDto.from(parentProfile), rule, RulePriority.valueOf(Severity.MINOR), null);
+
+ QProfileDto childProfile = createChildProfile(parentProfile);
+ activateRuleInDb(RulesProfileDto.from(childProfile), rule, RulePriority.valueOf(Severity.MINOR), INHERITED);
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(parentProfile.getName(), parentProfile.getLanguage());
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(parentProfile.getLanguage(), parentProfile.getName()), rule);
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(parentProfile));
+
+ assertThat(changes).hasSize(2);
+
+ List<ActiveRuleDto> parentActiveRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), RulesProfileDto.from(parentProfile));
+ assertThatRuleIsUpdated(parentActiveRules, rule, RulePriority.BLOCKER, null);
+
+ List<ActiveRuleDto> childActiveRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), RulesProfileDto.from(childProfile));
+ assertThatRuleIsUpdated(childActiveRules, rule, RulePriority.BLOCKER, INHERITED);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ @Test
+ public void propagate_rule_param_update_to_descendant_active_rule_params() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
+ RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
+
+ QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+ ActiveRuleDto parentActiveRuleDto = activateRuleInDb(RulesProfileDto.from(parentProfile), rule,
+ RulePriority.valueOf(Severity.MINOR), null);
+ activateRuleParamInDb(parentActiveRuleDto, ruleParam, "20");
+
+ QProfileDto childProfile = createChildProfile(parentProfile);
+ ActiveRuleDto childActiveRuleDto = activateRuleInDb(RulesProfileDto.from(childProfile), rule,
+ RulePriority.valueOf(Severity.MINOR), INHERITED);
+ activateRuleParamInDb(childActiveRuleDto, ruleParam, "20");
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(parentProfile.getName(), parentProfile.getLanguage());
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(parentProfile.getLanguage(), parentProfile.getName()), rule);
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(parentProfile));
+
+ assertThat(changes).hasSize(2);
+
+ assertThatRuleHasParams(db, parentActiveRuleDto, tuple("min", "10"));
+ assertThatRuleHasParams(db, childActiveRuleDto, tuple("min", "10"));
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+ }
+
+ @Test
+ public void do_not_load_descendants_if_no_changes() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
+
+ QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+ QProfileDto childProfile = createChildProfile(profile);
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
+ newQp.done();
+
+ // first run
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
+ assertThat(changes).hasSize(2).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+
+ // second run, without any input changes
+ RuleActivator ruleActivatorWithoutDescendants = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession) {
+ @Override
+ DescendantProfilesSupplier createDescendantProfilesSupplier(DbSession dbSession) {
+ return (profiles, ruleIds) -> {
+ throw new IllegalStateException("BOOM - descendants should not be loaded");
+ };
+ }
+ };
+ changes = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivatorWithoutDescendants, activeRuleIndexer, qualityProfileChangeEventService)
+ .update(db.getSession(), builtIn, RulesProfileDto.from(profile));
+ assertThat(changes).isEmpty();
+ verifyNoMoreInteractions(qualityProfileChangeEventService);
+ }
+
+ @Test
+ public void propagate_deactivation_to_descendant_profiles() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
+
+ QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+ QProfileDto childProfile = createChildProfile(profile);
+ QProfileDto grandChildProfile = createChildProfile(childProfile);
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
+ newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey());
+ newQp.done();
+
+ // first run to activate the rule
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
+ assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+
+ // second run to deactivate the rule
+ context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile updatedQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
+ updatedQp.done();
+ builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule);
+ changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile));
+ assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.DEACTIVATED);
+ verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
+
+ assertThatRuleIsDeactivated(profile, rule);
+ assertThatRuleIsDeactivated(childProfile, rule);
+ assertThatRuleIsDeactivated(grandChildProfile, rule);
+ }
+
+ private QProfileDto createChildProfile(QProfileDto parent) {
+ return db.qualityProfiles().insert(p -> p
+ .setLanguage(parent.getLanguage())
+ .setParentKee(parent.getKee())
+ .setName("Child of " + parent.getName()))
+ .setIsBuiltIn(false);
+ }
+
+ private void assertThatRuleIsActivated(QProfileDto profile, RuleDefinitionDto rule, @Nullable List<ActiveRuleChange> changes,
+ String expectedSeverity, @Nullable ActiveRuleInheritance expectedInheritance, Map<String, String> expectedParams) {
+ OrgActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), profile)
+ .stream()
+ .filter(ar -> ar.getRuleKey().equals(rule.getKey()))
+ .findFirst()
+ .orElseThrow(IllegalStateException::new);
+
+ assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity);
+ assertThat(activeRule.getInheritance()).isEqualTo(expectedInheritance != null ? expectedInheritance.name() : null);
+
+ List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(db.getSession(), activeRule.getUuid());
+ assertThat(params).hasSize(expectedParams.size());
+
+ if (changes != null) {
+ ActiveRuleChange change = changes.stream()
+ .filter(c -> c.getActiveRule().getUuid().equals(activeRule.getUuid()))
+ .findFirst().orElseThrow(IllegalStateException::new);
+ assertThat(change.getInheritance()).isEqualTo(expectedInheritance);
+ assertThat(change.getSeverity()).isEqualTo(expectedSeverity);
+ assertThat(change.getType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED);
+ }
+ }
+
+ private static void assertThatRuleHasParams(DbTester db, ActiveRuleDto activeRule, Tuple... expectedParams) {
+ assertThat(db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(db.getSession(), activeRule.getUuid()))
+ .extracting(ActiveRuleParamDto::getKey, ActiveRuleParamDto::getValue)
+ .containsExactlyInAnyOrder(expectedParams);
+ }
+
+ private static void assertThatRuleIsNewlyActivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
+ ActiveRuleDto activeRule = findRule(activeRules, rule).get();
+
+ assertThat(activeRule.getInheritance()).isNull();
+ assertThat(activeRule.getSeverityString()).isEqualTo(severity.name());
+ assertThat(activeRule.getCreatedAt()).isEqualTo(NOW);
+ assertThat(activeRule.getUpdatedAt()).isEqualTo(NOW);
+ }
+
+ private static void assertThatRuleIsUpdated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance expectedInheritance) {
+ ActiveRuleDto activeRule = findRule(activeRules, rule).get();
+
+ if (expectedInheritance != null) {
+ assertThat(activeRule.getInheritance()).isEqualTo(expectedInheritance.name());
+ } else {
+ assertThat(activeRule.getInheritance()).isNull();
+ }
+ assertThat(activeRule.getSeverityString()).isEqualTo(severity.name());
+ assertThat(activeRule.getCreatedAt()).isEqualTo(PAST);
+ assertThat(activeRule.getUpdatedAt()).isEqualTo(NOW);
+ }
+
+ private static void assertThatRuleIsUpdated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
+ assertThatRuleIsUpdated(activeRules, rule, severity, null);
+ }
+
+ private static void assertThatRuleIsUpdated(ActiveRuleDto activeRules, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance expectedInheritance) {
+ assertThatRuleIsUpdated(singletonList(activeRules), rule, severity, expectedInheritance);
+ }
+
+ private static void assertThatRuleIsUntouched(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) {
+ ActiveRuleDto activeRule = findRule(activeRules, rule).get();
+
+ assertThat(activeRule.getInheritance()).isNull();
+ assertThat(activeRule.getSeverityString()).isEqualTo(severity.name());
+ assertThat(activeRule.getCreatedAt()).isEqualTo(PAST);
+ assertThat(activeRule.getUpdatedAt()).isEqualTo(PAST);
+ }
+
+ private void assertThatRuleIsDeactivated(QProfileDto profile, RuleDefinitionDto rule) {
+ Collection<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRulesAndRuleProfileUuids(
+ db.getSession(), singletonList(rule.getUuid()), singletonList(profile.getRulesProfileUuid()));
+ assertThat(activeRules).isEmpty();
+ }
+
+ private static void assertThatRuleIsDeactivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule) {
+ assertThat(findRule(activeRules, rule)).isEmpty();
+ }
+
+ private void assertThatProfileIsMarkedAsUpdated(RulesProfileDto dto) {
+ RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession())
+ .stream()
+ .filter(p -> p.getUuid().equals(dto.getUuid()))
+ .findFirst()
+ .get();
+ assertThat(reloaded.getRulesUpdatedAt()).isNotEmpty();
+ }
+
+ private void assertThatProfileIsNotMarkedAsUpdated(RulesProfileDto dto) {
+ RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession())
+ .stream()
+ .filter(p -> p.getUuid().equals(dto.getUuid()))
+ .findFirst()
+ .get();
+ assertThat(reloaded.getRulesUpdatedAt()).isNull();
+ }
+
+ private static Optional<ActiveRuleDto> findRule(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule) {
+ return activeRules.stream()
+ .filter(ar -> ar.getRuleKey().equals(rule.getKey()))
+ .findFirst();
+ }
+
+ private ActiveRuleDto activateRuleInDb(RulesProfileDto profile, RuleDefinitionDto rule, RulePriority severity) {
+ return activateRuleInDb(profile, rule, severity, null);
+ }
+
+ private ActiveRuleDto activateRuleInDb(RulesProfileDto ruleProfile, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance inheritance) {
+ ActiveRuleDto dto = new ActiveRuleDto()
+ .setKey(ActiveRuleKey.of(ruleProfile, RuleKey.of(rule.getRepositoryKey(), rule.getRuleKey())))
+ .setProfileUuid(ruleProfile.getUuid())
+ .setSeverity(severity.name())
+ .setRuleUuid(rule.getUuid())
+ .setInheritance(inheritance != null ? inheritance.name() : null)
+ .setCreatedAt(PAST)
+ .setUpdatedAt(PAST);
+ db.getDbClient().activeRuleDao().insert(db.getSession(), dto);
+ db.commit();
+ return dto;
+ }
+
+ private void activateRuleParamInDb(ActiveRuleDto activeRuleDto, RuleParamDto ruleParamDto, String value) {
+ ActiveRuleParamDto dto = new ActiveRuleParamDto()
+ .setActiveRuleUuid(activeRuleDto.getUuid())
+ .setRulesParameterUuid(ruleParamDto.getUuid())
+ .setKey(ruleParamDto.getName())
+ .setValue(value);
+ db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRuleDto, dto);
+ db.commit();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import org.assertj.core.groups.Tuple;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationBuilder.Profile;
+
+import static java.util.Arrays.asList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.sonar.core.config.CorePropertyDefinitions.DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES;
+import static org.sonar.server.language.LanguageTesting.newLanguage;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED;
+
+public class BuiltInQualityProfilesUpdateListenerTest {
+
+ private NotificationManager notificationManager = mock(NotificationManager.class);
+ private MapSettings settings = new MapSettings();
+
+ @Test
+ public void add_profile_to_notification_for_added_rules() {
+ enableNotificationInGlobalSettings();
+ Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
+ Languages languages = new Languages();
+ Tuple expectedTuple = addProfile(profiles, languages, ACTIVATED);
+
+ BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
+ underTest.onChange(profiles, 0, 1);
+
+ ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
+ verifyNoMoreInteractions(notificationManager);
+ assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
+ .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules)
+ .containsExactlyInAnyOrder(expectedTuple);
+ }
+
+ @Test
+ public void add_profile_to_notification_for_updated_rules() {
+ enableNotificationInGlobalSettings();
+ Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
+ Languages languages = new Languages();
+ Tuple expectedTuple = addProfile(profiles, languages, UPDATED);
+
+ BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
+ underTest.onChange(profiles, 0, 1);
+
+ ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
+ verifyNoMoreInteractions(notificationManager);
+ assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
+ .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getUpdatedRules)
+ .containsExactlyInAnyOrder(expectedTuple);
+ }
+
+ @Test
+ public void add_profile_to_notification_for_removed_rules() {
+ enableNotificationInGlobalSettings();
+ Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
+ Languages languages = new Languages();
+ Tuple expectedTuple = addProfile(profiles, languages, DEACTIVATED);
+
+ BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
+ underTest.onChange(profiles, 0, 1);
+
+ ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
+ verifyNoMoreInteractions(notificationManager);
+ assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
+ .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getRemovedRules)
+ .containsExactlyInAnyOrder(expectedTuple);
+ }
+
+ @Test
+ public void add_multiple_profiles_to_notification() {
+ enableNotificationInGlobalSettings();
+ Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
+ Languages languages = new Languages();
+ Tuple expectedTuple1 = addProfile(profiles, languages, ACTIVATED);
+ Tuple expectedTuple2 = addProfile(profiles, languages, ACTIVATED);
+
+ BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
+ underTest.onChange(profiles, 0, 1);
+
+ ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
+ verifyNoMoreInteractions(notificationManager);
+ assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
+ .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules)
+ .containsExactlyInAnyOrder(expectedTuple1, expectedTuple2);
+ }
+
+ @Test
+ public void add_start_and_end_dates_to_notification() {
+ enableNotificationInGlobalSettings();
+ Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
+ Languages languages = new Languages();
+ addProfile(profiles, languages, ACTIVATED);
+ long startDate = 10_000_000_000L;
+ long endDate = 15_000_000_000L;
+
+ BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
+ underTest.onChange(profiles, startDate, endDate);
+
+ ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
+ verifyNoMoreInteractions(notificationManager);
+ assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles())
+ .extracting(Profile::getStartDate, Profile::getEndDate)
+ .containsExactlyInAnyOrder(tuple(startDate, endDate));
+ }
+
+ @Test
+ public void avoid_notification_if_configured_in_settings() {
+ settings.setProperty(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES, true);
+ Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create();
+ Languages languages = new Languages();
+ addProfile(profiles, languages, ACTIVATED);
+
+ BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig());
+ underTest.onChange(profiles, 0, 1);
+
+ verifyZeroInteractions(notificationManager);
+ }
+
+ private Tuple addProfile(Multimap<QProfileName, ActiveRuleChange> profiles, Languages languages, ActiveRuleChange.Type type) {
+ String profileName = randomLowerCaseText();
+ String ruleUuid1 = Uuids.createFast();
+ String ruleUuid2 = Uuids.createFast();
+ Language language = newLanguage(randomLowerCaseText(), randomLowerCaseText());
+ languages.add(language);
+ profiles.putAll(new QProfileName(language.getKey(), profileName),
+ asList(new ActiveRuleChange(
+ type,
+ ActiveRuleKey.parse("qp:repo:rule1"), new RuleDefinitionDto().setUuid(ruleUuid1)),
+ new ActiveRuleChange(type, ActiveRuleKey.parse("qp:repo:rule2"), new RuleDefinitionDto().setUuid(ruleUuid2))));
+ return tuple(profileName, language.getKey(), language.getName(), 2);
+ }
+
+ private static String randomLowerCaseText() {
+ return randomAlphanumeric(20).toLowerCase();
+ }
+
+ private void enableNotificationInGlobalSettings() {
+ settings.setProperty(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES, false);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.builtin;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.ActiveRuleInheritance;
+import org.sonar.server.qualityprofile.RuleActivation;
+import org.sonar.server.qualityprofile.builtin.DescendantProfilesSupplier.Result;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.util.IntegerTypeValidation;
+import org.sonar.server.util.StringTypeValidation;
+import org.sonar.server.util.TypeValidations;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED;
+import static org.sonar.server.qualityprofile.ActiveRuleInheritance.OVERRIDES;
+
+/**
+ * Class org.sonar.server.qualityprofile.builtin.RuleActivator is mostly covered in
+ * org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdateImplTest
+ */
+public class RuleActivatorTest {
+ @Rule
+ public final DbTester db = DbTester.create();
+
+ @Rule
+ public final UserSessionRule userSession = UserSessionRule.standalone();
+
+ private static final long NOW = 1_000;
+ private static final long PAST = NOW - 100;
+ private final System2 system2 = new TestSystem2().setNow(NOW);
+ private final TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation()));
+
+ private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
+ private final RuleActivator underTest = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession);
+
+ @Test
+ public void reset_overridden_active_rule() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
+ RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
+
+ QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+ ActiveRuleDto parentActiveRuleDto = activateRuleInDb(RulesProfileDto.from(parentProfile), rule,
+ RulePriority.valueOf(Severity.BLOCKER), null);
+ ActiveRuleParamDto parentActiveRuleParam = activateRuleParamInDb(parentActiveRuleDto, ruleParam, "10");
+
+ QProfileDto childProfile = createChildProfile(parentProfile);
+ ActiveRuleDto childActiveRuleDto = activateRuleInDb(RulesProfileDto.from(childProfile), rule,
+ RulePriority.valueOf(Severity.MINOR), OVERRIDES);
+ ActiveRuleParamDto childActiveRuleParam = activateRuleParamInDb(childActiveRuleDto, ruleParam, "15");
+
+ DbSession session = db.getSession();
+ RuleActivation resetRequest = RuleActivation.createReset(rule.getUuid());
+ RuleActivationContext context = new RuleActivationContext.Builder()
+ .setProfiles(asList(parentProfile, childProfile))
+ .setBaseProfile(RulesProfileDto.from(childProfile))
+ .setDate(NOW)
+ .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
+ .setRules(singletonList(rule))
+ .setRuleParams(singletonList(ruleParam))
+ .setActiveRules(asList(parentActiveRuleDto, childActiveRuleDto))
+ .setActiveRuleParams(asList(parentActiveRuleParam, childActiveRuleParam))
+ .build();
+
+ List<ActiveRuleChange> result = underTest.activate(session, resetRequest, context);
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getParameters()).containsEntry("min", "10");
+ assertThat(result.get(0).getSeverity()).isEqualTo(Severity.BLOCKER);
+ assertThat(result.get(0).getInheritance()).isEqualTo(ActiveRuleInheritance.INHERITED);
+ }
+
+ @Test
+ public void request_new_severity_and_param_for_child_rule() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
+ RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
+
+ QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+ ActiveRuleDto parentActiveRuleDto = activateRuleInDb(RulesProfileDto.from(parentProfile), rule,
+ RulePriority.valueOf(Severity.BLOCKER), null);
+ ActiveRuleParamDto parentActiveRuleParam = activateRuleParamInDb(parentActiveRuleDto, ruleParam, "10");
+
+ QProfileDto childProfile = createChildProfile(parentProfile);
+ ActiveRuleDto childActiveRuleDto = activateRuleInDb(RulesProfileDto.from(childProfile), rule,
+ RulePriority.valueOf(Severity.BLOCKER), INHERITED);
+ ActiveRuleParamDto childActiveRuleParam = activateRuleParamInDb(childActiveRuleDto, ruleParam, "10");
+
+ DbSession session = db.getSession();
+ RuleActivation resetRequest = RuleActivation.create(rule.getUuid(), Severity.MINOR, ImmutableMap.of("min", "15"));
+ RuleActivationContext context = new RuleActivationContext.Builder()
+ .setProfiles(asList(parentProfile, childProfile))
+ .setBaseProfile(RulesProfileDto.from(childProfile))
+ .setDate(NOW)
+ .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
+ .setRules(singletonList(rule))
+ .setRuleParams(singletonList(ruleParam))
+ .setActiveRules(asList(parentActiveRuleDto, childActiveRuleDto))
+ .setActiveRuleParams(asList(parentActiveRuleParam, childActiveRuleParam))
+ .build();
+
+ List<ActiveRuleChange> result = underTest.activate(session, resetRequest, context);
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getParameters()).containsEntry("min", "15");
+ assertThat(result.get(0).getSeverity()).isEqualTo(Severity.MINOR);
+ assertThat(result.get(0).getInheritance()).isEqualTo(OVERRIDES);
+ }
+
+ @Test
+ public void set_severity_and_param_for_child_rule_when_activating() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.BLOCKER));
+ RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10"));
+
+ QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true));
+
+ QProfileDto childProfile = createChildProfile(parentProfile);
+
+ DbSession session = db.getSession();
+ RuleActivation resetRequest = RuleActivation.create(rule.getUuid());
+ RuleActivationContext context = new RuleActivationContext.Builder()
+ .setProfiles(asList(parentProfile, childProfile))
+ .setBaseProfile(RulesProfileDto.from(childProfile))
+ .setDate(NOW)
+ .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
+ .setRules(singletonList(rule))
+ .setRuleParams(singletonList(ruleParam))
+ .setActiveRules(emptyList())
+ .setActiveRuleParams(emptyList())
+ .build();
+
+ List<ActiveRuleChange> result = underTest.activate(session, resetRequest, context);
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getParameters()).containsEntry("min", "10");
+ assertThat(result.get(0).getSeverity()).isEqualTo(Severity.BLOCKER);
+ assertThat(result.get(0).getInheritance()).isNull();
+ }
+
+ @Test
+ public void fail_if_rule_language_doesnt_match_qp() {
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")
+ .setRepositoryKey("repo")
+ .setRuleKey("rule")
+ .setSeverity(Severity.BLOCKER));
+ QProfileDto qp = db.qualityProfiles().insert(p -> p.setLanguage("xoo2").setKee("qp").setIsBuiltIn(true));
+
+ DbSession session = db.getSession();
+ RuleActivation resetRequest = RuleActivation.create(rule.getUuid());
+ RuleActivationContext context = new RuleActivationContext.Builder()
+ .setProfiles(singletonList(qp))
+ .setBaseProfile(RulesProfileDto.from(qp))
+ .setDate(NOW)
+ .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
+ .setRules(singletonList(rule))
+ .setRuleParams(emptyList())
+ .setActiveRules(emptyList())
+ .setActiveRuleParams(emptyList())
+ .build();
+
+
+ assertThrows("xoo rule repo:rule cannot be activated on xoo2 profile qp", BadRequestException.class,
+ () -> underTest.activate(session, resetRequest, context));
+ }
+
+
+ private ActiveRuleDto activateRuleInDb(RulesProfileDto ruleProfile, RuleDefinitionDto rule, RulePriority severity, @Nullable ActiveRuleInheritance inheritance) {
+ ActiveRuleDto dto = new ActiveRuleDto()
+ .setKey(ActiveRuleKey.of(ruleProfile, RuleKey.of(rule.getRepositoryKey(), rule.getRuleKey())))
+ .setProfileUuid(ruleProfile.getUuid())
+ .setSeverity(severity.name())
+ .setRuleUuid(rule.getUuid())
+ .setInheritance(inheritance != null ? inheritance.name() : null)
+ .setCreatedAt(PAST)
+ .setUpdatedAt(PAST);
+ db.getDbClient().activeRuleDao().insert(db.getSession(), dto);
+ db.commit();
+ return dto;
+ }
+
+ private ActiveRuleParamDto activateRuleParamInDb(ActiveRuleDto activeRuleDto, RuleParamDto ruleParamDto, String value) {
+ ActiveRuleParamDto dto = new ActiveRuleParamDto()
+ .setActiveRuleUuid(activeRuleDto.getUuid())
+ .setRulesParameterUuid(ruleParamDto.getUuid())
+ .setKey(ruleParamDto.getName())
+ .setValue(value);
+ db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRuleDto, dto);
+ db.commit();
+ return dto;
+ }
+
+ private QProfileDto createChildProfile(QProfileDto parent) {
+ return db.qualityProfiles().insert(p -> p
+ .setLanguage(parent.getLanguage())
+ .setParentKee(parent.getKee())
+ .setName("Child of " + parent.getName()))
+ .setIsBuiltIn(false);
+ }
+}
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.language.LanguageTesting;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import static java.lang.String.format;
-import static java.util.Optional.empty;
-import static java.util.Optional.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
assertThat(response.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT);
assertProjectIsAssociatedToProfile(project, profile);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile), empty());
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, profile, null);
}
@Test
call(project, qualityProfile);
assertProjectIsAssociatedToProfile(project, qualityProfile);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(qualityProfile), empty());
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, qualityProfile, null);
}
@Test
assertProjectIsNotAssociatedToProfile(project, profile1);
assertProjectIsAssociatedToProfile(project, profile2);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile2), of(profile1));
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, profile2, profile1);
}
@Test
assertProjectIsAssociatedToProfile(project, profile3Language1);
assertProjectIsAssociatedToProfile(project, profile2Language2);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile3Language1), of(profile1Language1));
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, profile3Language1, profile1Language1);
}
@Test
call(project, profile);
assertProjectIsAssociatedToProfile(project, profile);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, of(profile), empty());
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, profile, null);
}
@Test
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.QProfileTreeImpl;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
-import org.sonar.server.qualityprofile.RuleActivator;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonar.server.qualityprofile.QProfileFactoryImpl;
import org.sonar.server.qualityprofile.QProfileRules;
import org.sonar.server.qualityprofile.QProfileRulesImpl;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
-import org.sonar.server.qualityprofile.RuleActivator;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.QProfileRules;
import org.sonar.server.qualityprofile.QProfileRulesImpl;
import org.sonar.server.qualityprofile.QProfileTree;
import org.sonar.server.qualityprofile.QProfileTreeImpl;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.RuleActivation;
-import org.sonar.server.qualityprofile.RuleActivator;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.QProfileRules;
import org.sonar.server.qualityprofile.QProfileRulesImpl;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
-import org.sonar.server.qualityprofile.RuleActivator;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.language.LanguageTesting;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import static java.lang.String.format;
-import static java.util.Optional.empty;
-import static java.util.Optional.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.verify;
assertProjectIsNotAssociatedToProfile(project, profileLang1);
assertProjectIsAssociatedToProfile(project, profileLang2);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profileLang1));
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, null, profileLang1);
}
@Test
assertThat(response.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT);
assertProjectIsNotAssociatedToProfile(project, profile);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profile));
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, null, profile);
}
@Test
call(project, profile);
assertProjectIsNotAssociatedToProfile(project, profile);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profile));
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, null, profile);
}
@Test
call(project, profile);
assertProjectIsNotAssociatedToProfile(project, profile);
- verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, empty(), of(profile));
+ verify(qualityProfileChangeEventService).publishRuleActivationToSonarLintClients(project, null, profile);
}
@Test
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.ActiveRuleChange;
import org.sonar.server.qualityprofile.QProfileRules;
import org.sonar.server.qualityprofile.QProfileRulesImpl;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventService;
import org.sonar.server.qualityprofile.RuleActivation;
-import org.sonar.server.qualityprofile.RuleActivator;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.qualitygate.QualityGateModule;
import org.sonar.server.qualitygate.notification.QGChangeNotificationHandler;
import org.sonar.server.qualitygate.ws.QualityGateWsModule;
-import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationHandler;
-import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationTemplate;
-import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryImpl;
-import org.sonar.server.qualityprofile.DistributedRuleActivatorEventsDistributor;
+import org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationHandler;
+import org.sonar.server.qualityprofile.builtin.BuiltInQPChangeNotificationTemplate;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileRepositoryImpl;
+import org.sonar.server.pushapi.qualityprofile.DistributedRuleActivatorEventsDistributor;
import org.sonar.server.qualityprofile.QProfileBackuperImpl;
import org.sonar.server.qualityprofile.QProfileComparison;
import org.sonar.server.qualityprofile.QProfileCopier;
import org.sonar.server.qualityprofile.QProfileResetImpl;
import org.sonar.server.qualityprofile.QProfileRulesImpl;
import org.sonar.server.qualityprofile.QProfileTreeImpl;
-import org.sonar.server.qualityprofile.QualityProfileChangeEventServiceImpl;
-import org.sonar.server.qualityprofile.RuleActivator;
-import org.sonar.server.qualityprofile.StandaloneRuleActivatorEventsDistributor;
+import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventServiceImpl;
+import org.sonar.server.qualityprofile.builtin.RuleActivator;
+import org.sonar.server.pushapi.qualityprofile.StandaloneRuleActivatorEventsDistributor;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.qualityprofile.ws.QProfilesWsModule;
import org.sonar.server.root.ws.RootWsModule;
import org.sonar.server.plugins.PluginConsentVerifier;
import org.sonar.server.qualitygate.ProjectsInWarningDaemon;
import org.sonar.server.qualitygate.RegisterQualityGates;
-import org.sonar.server.qualityprofile.BuiltInQProfileInsertImpl;
-import org.sonar.server.qualityprofile.BuiltInQProfileLoader;
-import org.sonar.server.qualityprofile.BuiltInQProfileUpdateImpl;
-import org.sonar.server.qualityprofile.BuiltInQualityProfilesUpdateListener;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsertImpl;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileLoader;
+import org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdateImpl;
+import org.sonar.server.qualityprofile.builtin.BuiltInQualityProfilesUpdateListener;
import org.sonar.server.qualityprofile.RegisterQualityProfiles;
import org.sonar.server.rule.RegisterRules;
import org.sonar.server.rule.WebServerRuleFinder;
*/
package org.sonar.core.util;
-public class RuleChange {
+import java.io.Serializable;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class RuleChange implements Serializable {
String key;
String language;
String templateKey;
this.key = key;
}
+ @CheckForNull
public String getLanguage() {
return language;
}
- public void setLanguage(String language) {
+ public void setLanguage(@Nullable String language) {
this.language = language;
}
this.templateKey = templateKey;
}
+ @CheckForNull
public String getSeverity() {
return severity;
}
- public void setSeverity(String severity) {
+ public void setSeverity(@Nullable String severity) {
this.severity = severity;
}
public class RuleSetChangeEvent implements Serializable {
- private final String event = "RuleSetChange";
+ private static final String EVENT = "RuleSetChange";
private String[] projects;
+ private String language;
private RuleChange[] activatedRules;
private RuleChange[] deactivatedRules;
this.projects = projects;
this.activatedRules = activatedRules;
this.deactivatedRules = deactivatedRules;
+ if (activatedRules.length == 0 && deactivatedRules.length == 0) {
+ throw new IllegalArgumentException("Can't create RuleSetChangeEvent without any rules that have changed");
+ }
+ this.language = activatedRules.length > 0 ? activatedRules[0].getLanguage() : deactivatedRules[0].getLanguage();
}
public void setProjects(String[] projects) {
}
public String getEvent() {
- return event;
+ return EVENT;
}
public String[] getProjects() {
return projects;
}
+ public String getLanguage() {
+ return language;
+ }
+
public RuleChange[] getActivatedRules() {
return activatedRules;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.core.util;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class RuleSetChangeEventTest {
+
+ @Test
+ public void getLanguage_givenNoDeactivatedRules_languageIsCorrectlyIdentified() {
+ String[] projects = {"sonarqube"};
+ RuleChange[] activatedRules = {createRuleChange("java")};
+ RuleChange[] deactivatedRules = {};
+ RuleSetChangeEvent event = new RuleSetChangeEvent(projects, activatedRules, deactivatedRules);
+
+ String language = event.getLanguage();
+
+ assertThat(language).isEqualTo("java");
+ }
+
+ @Test
+ public void getLanguage_givenNoActivatedRules_languageIsCorrectlyIdentified() {
+ String[] projects = {"sonarqube"};
+ RuleChange[] activatedRules = {};
+ RuleChange[] deactivatedRules = {createRuleChange("java")};
+ RuleSetChangeEvent event = new RuleSetChangeEvent(projects, activatedRules, deactivatedRules);
+
+ String language = event.getLanguage();
+
+ assertThat(language).isEqualTo("java");
+ }
+
+ @Test
+ public void getLanguage_givenBothArraysEmpty_throwException() {
+ String[] projects = {"sonarqube"};
+ RuleChange[] activatedRules = {};
+ RuleChange[] deactivatedRules = {};
+
+ assertThatThrownBy(() -> new RuleSetChangeEvent(projects, activatedRules, deactivatedRules))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ private RuleChange createRuleChange(String language) {
+ RuleChange ruleChange = new RuleChange();
+ ruleChange.setLanguage(language);
+ return ruleChange;
+ }
+}