import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbSession;
import org.sonar.db.issue.ImpactDto;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
public class RuleDto {
return strings == null || strings.isEmpty() ? null : String.join(",", strings);
}
+ public static RuleDto from(RulesDefinition.Rule ruleDef, Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos, UuidFactory uuidFactory,
+ long now) {
+ RuleDto ruleDto = new RuleDto()
+ .setUuid(uuidFactory.create())
+ .setRuleKey(RuleKey.of(ruleDef.repository().key(), ruleDef.key()))
+ .setPluginKey(ruleDef.pluginKey())
+ .setIsTemplate(ruleDef.template())
+ .setConfigKey(ruleDef.internalKey())
+ .setLanguage(ruleDef.repository().language())
+ .setName(ruleDef.name())
+ .setSeverity(ruleDef.severity())
+ .setStatus(ruleDef.status())
+ .setGapDescription(ruleDef.gapDescription())
+ .setSystemTags(ruleDef.tags())
+ .setSecurityStandards(ruleDef.securityStandards())
+ .setType(RuleType.valueOf(ruleDef.type().name()))
+ .setScope(Scope.valueOf(ruleDef.scope().name()))
+ .setIsExternal(ruleDef.repository().isExternal())
+ .setIsAdHoc(false)
+ .setCreatedAt(now)
+ .setUpdatedAt(now)
+ .setEducationPrinciples(ruleDef.educationPrincipleKeys());
+
+ if (isNotEmpty(ruleDef.htmlDescription())) {
+ ruleDto.setDescriptionFormat(Format.HTML);
+ } else if (isNotEmpty(ruleDef.markdownDescription())) {
+ ruleDto.setDescriptionFormat(Format.MARKDOWN);
+ }
+
+ ruleDescriptionSectionDtos.forEach(ruleDto::addRuleDescriptionSectionDto);
+
+ DebtRemediationFunction debtRemediationFunction = ruleDef.debtRemediationFunction();
+ if (debtRemediationFunction != null) {
+ ruleDto.setDefRemediationFunction(debtRemediationFunction.type().name());
+ ruleDto.setDefRemediationGapMultiplier(debtRemediationFunction.gapMultiplier());
+ ruleDto.setDefRemediationBaseEffort(debtRemediationFunction.baseEffort());
+ ruleDto.setGapDescription(ruleDef.gapDescription());
+ }
+
+ return ruleDto;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RuleDto)) {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.rule;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.Startable;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleScope;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.server.debt.DebtRemediationFunction;
-import org.sonar.api.server.rule.RulesDefinition;
-import org.sonar.api.utils.System2;
-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.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.rule.DeprecatedRuleKeyDto;
-import org.sonar.db.rule.RuleDescriptionSectionDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleDto.Format;
-import org.sonar.db.rule.RuleDto.Scope;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.db.rule.RuleRepositoryDto;
-import org.sonar.server.es.metadata.MetadataIndex;
-import org.sonar.server.qualityprofile.ActiveRuleChange;
-import org.sonar.server.qualityprofile.QProfileRules;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.rule.index.RuleIndexer;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.Sets.difference;
-import static com.google.common.collect.Sets.intersection;
-import static java.lang.String.format;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.unmodifiableMap;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-
-/**
- * Register rules at server startup
- */
-public class RegisterRules implements Startable {
-
- private static final Logger LOG = Loggers.get(RegisterRules.class);
-
- private final RuleDefinitionsLoader defLoader;
- private final QProfileRules qProfileRules;
- private final DbClient dbClient;
- private final RuleIndexer ruleIndexer;
- private final ActiveRuleIndexer activeRuleIndexer;
- private final Languages languages;
- private final System2 system2;
- private final WebServerRuleFinder webServerRuleFinder;
- private final UuidFactory uuidFactory;
- private final MetadataIndex metadataIndex;
- private final RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver;
-
-
- public RegisterRules(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer,
- ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2,
- WebServerRuleFinder webServerRuleFinder, UuidFactory uuidFactory, MetadataIndex metadataIndex,
- RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver) {
- this.defLoader = defLoader;
- this.qProfileRules = qProfileRules;
- this.dbClient = dbClient;
- this.ruleIndexer = ruleIndexer;
- this.activeRuleIndexer = activeRuleIndexer;
- this.languages = languages;
- this.system2 = system2;
- this.webServerRuleFinder = webServerRuleFinder;
- this.uuidFactory = uuidFactory;
- this.metadataIndex = metadataIndex;
- this.ruleDescriptionSectionsGeneratorResolver = ruleDescriptionSectionsGeneratorResolver;
- }
-
- @Override
- public void start() {
- Profiler profiler = Profiler.create(LOG).startInfo("Register rules");
- try (DbSession dbSession = dbClient.openSession(true)) {
- RulesDefinition.Context ruleDefinitionContext = defLoader.load();
- List<RulesDefinition.Repository> repositories = ruleDefinitionContext.repositories();
- RegisterRulesContext registerRulesContext = createRegisterRulesContext(dbSession);
-
- verifyRuleKeyConsistency(repositories, registerRulesContext);
-
- for (RulesDefinition.ExtendedRepository repoDef : repositories) {
- if (languages.get(repoDef.language()) != null) {
- registerRules(registerRulesContext, repoDef.rules(), dbSession);
- dbSession.commit();
- }
- }
- processRemainingDbRules(registerRulesContext, dbSession);
- List<ActiveRuleChange> changes = removeActiveRulesOnStillExistingRepositories(dbSession, registerRulesContext, repositories);
- dbSession.commit();
-
- persistRepositories(dbSession, ruleDefinitionContext.repositories());
- // FIXME lack of resiliency, active rules index is corrupted if rule index fails
- // to be updated. Only a single DB commit should be executed.
- ruleIndexer.commitAndIndex(dbSession, registerRulesContext.getAllModified().map(RuleDto::getUuid).collect(Collectors.toSet()));
- changes.forEach(arChange -> dbClient.qProfileChangeDao().insert(dbSession, arChange.toDto(null)));
- activeRuleIndexer.commitAndIndex(dbSession, changes);
- registerRulesContext.getRenamed().forEach(e -> LOG.info("Rule {} re-keyed to {}", e.getValue(), e.getKey().getKey()));
- profiler.stopDebug();
-
- if (!registerRulesContext.hasDbRules()) {
- Stream.concat(ruleIndexer.getIndexTypes().stream(), activeRuleIndexer.getIndexTypes().stream())
- .forEach(t -> metadataIndex.setInitialized(t, true));
- }
-
- webServerRuleFinder.startCaching();
- }
- }
-
- private RegisterRulesContext createRegisterRulesContext(DbSession dbSession) {
- Map<RuleKey, RuleDto> allRules = dbClient.ruleDao().selectAll(dbSession).stream()
- .collect(Collectors.toMap(RuleDto::getKey, Function.identity()));
- Map<String, Set<SingleDeprecatedRuleKey>> existingDeprecatedKeysById = loadDeprecatedRuleKeys(dbSession);
- Map<String, List<RuleParamDto>> ruleParamsByRuleUuid = loadAllRuleParameters(dbSession);
- return new RegisterRulesContext(allRules, existingDeprecatedKeysById, ruleParamsByRuleUuid);
- }
-
- private Map<String, List<RuleParamDto>> loadAllRuleParameters(DbSession dbSession) {
- return dbClient.ruleDao().selectAllRuleParams(dbSession).stream()
- .collect(Collectors.groupingBy(RuleParamDto::getRuleUuid));
- }
-
- private Map<String, Set<SingleDeprecatedRuleKey>> loadDeprecatedRuleKeys(DbSession dbSession) {
- return dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
- .map(SingleDeprecatedRuleKey::from)
- .collect(Collectors.groupingBy(SingleDeprecatedRuleKey::getRuleUuid, Collectors.toSet()));
- }
-
- private static class RegisterRulesContext {
- // initial immutable data
- private final Map<RuleKey, RuleDto> dbRules;
- private final Set<RuleDto> known;
- private final Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid;
- private final Map<String, List<RuleParamDto>> ruleParamsByRuleUuid;
- private final Map<RuleKey, RuleDto> dbRulesByDbDeprecatedKey;
- // mutable data
- private final Set<RuleDto> created = new HashSet<>();
- private final Map<RuleDto, RuleKey> renamed = new HashMap<>();
- private final Set<RuleDto> updated = new HashSet<>();
- private final Set<RuleDto> unchanged = new HashSet<>();
- private final Set<RuleDto> removed = new HashSet<>();
-
- private RegisterRulesContext(Map<RuleKey, RuleDto> dbRules, Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid,
- Map<String, List<RuleParamDto>> ruleParamsByRuleUuid) {
- this.dbRules = ImmutableMap.copyOf(dbRules);
- this.known = ImmutableSet.copyOf(dbRules.values());
- this.dbDeprecatedKeysByUuid = dbDeprecatedKeysByUuid;
- this.ruleParamsByRuleUuid = ruleParamsByRuleUuid;
- this.dbRulesByDbDeprecatedKey = buildDbRulesByDbDeprecatedKey(dbDeprecatedKeysByUuid, dbRules);
- }
-
- private static Map<RuleKey, RuleDto> buildDbRulesByDbDeprecatedKey(Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid,
- Map<RuleKey, RuleDto> dbRules) {
- Map<String, RuleDto> dbRulesByRuleUuid = dbRules.values().stream()
- .collect(Collectors.toMap(RuleDto::getUuid, Function.identity()));
-
- Map<RuleKey, RuleDto> rulesByKey = new LinkedHashMap<>();
- for (Map.Entry<String, Set<SingleDeprecatedRuleKey>> entry : dbDeprecatedKeysByUuid.entrySet()) {
- String ruleUuid = entry.getKey();
- RuleDto rule = dbRulesByRuleUuid.get(ruleUuid);
- if (rule == null) {
- LOG.warn("Could not retrieve rule with uuid %s referenced by a deprecated rule key. " +
- "The following deprecated rule keys seem to be referencing a non-existing rule",
- ruleUuid, entry.getValue());
- } else {
- entry.getValue().forEach(d -> rulesByKey.put(d.getOldRuleKeyAsRuleKey(), rule));
- }
- }
- return unmodifiableMap(rulesByKey);
- }
-
- private boolean hasDbRules() {
- return !dbRules.isEmpty();
- }
-
- private Optional<RuleDto> getDbRuleFor(RulesDefinition.Rule ruleDef) {
- RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
- Optional<RuleDto> res = Stream.concat(Stream.of(ruleKey), ruleDef.deprecatedRuleKeys().stream())
- .map(dbRules::get)
- .filter(Objects::nonNull)
- .findFirst();
- // may occur in case of plugin downgrade
- if (res.isEmpty()) {
- return Optional.ofNullable(dbRulesByDbDeprecatedKey.get(ruleKey));
- }
- return res;
- }
-
- private Map<RuleKey, SingleDeprecatedRuleKey> getDbDeprecatedKeysByOldRuleKey() {
- return dbDeprecatedKeysByUuid.values().stream()
- .flatMap(Collection::stream)
- .collect(Collectors.toMap(SingleDeprecatedRuleKey::getOldRuleKeyAsRuleKey, Function.identity()));
- }
-
- private Set<SingleDeprecatedRuleKey> getDBDeprecatedKeysFor(RuleDto rule) {
- return dbDeprecatedKeysByUuid.getOrDefault(rule.getUuid(), emptySet());
- }
-
- private List<RuleParamDto> getRuleParametersFor(String ruleUuid) {
- return ruleParamsByRuleUuid.getOrDefault(ruleUuid, emptyList());
- }
-
- private Stream<RuleDto> getRemaining() {
- Set<RuleDto> res = new HashSet<>(dbRules.values());
- res.removeAll(unchanged);
- res.removeAll(renamed.keySet());
- res.removeAll(updated);
- res.removeAll(removed);
- return res.stream();
- }
-
- private Stream<RuleDto> getRemoved() {
- return removed.stream();
- }
-
- public Stream<Map.Entry<RuleDto, RuleKey>> getRenamed() {
- return renamed.entrySet().stream();
- }
-
- private Stream<RuleDto> getAllModified() {
- return Stream.of(
- created.stream(),
- updated.stream(),
- removed.stream(),
- renamed.keySet().stream())
- .flatMap(s -> s);
- }
-
- private boolean isCreated(RuleDto ruleDto) {
- return created.contains(ruleDto);
- }
-
- private boolean isRenamed(RuleDto ruleDto) {
- return renamed.containsKey(ruleDto);
- }
-
- private boolean isUpdated(RuleDto ruleDto) {
- return updated.contains(ruleDto);
- }
-
- private void created(RuleDto ruleDto) {
- checkState(!known.contains(ruleDto), "known RuleDto can't be created");
- created.add(ruleDto);
- }
-
- private void renamed(RuleDto ruleDto) {
- ensureKnown(ruleDto);
- renamed.put(ruleDto, ruleDto.getKey());
- }
-
- private void updated(RuleDto ruleDto) {
- ensureKnown(ruleDto);
- updated.add(ruleDto);
- }
-
- private void removed(RuleDto ruleDto) {
- ensureKnown(ruleDto);
- removed.add(ruleDto);
- }
-
- private void unchanged(RuleDto ruleDto) {
- ensureKnown(ruleDto);
- unchanged.add(ruleDto);
- }
-
- private void ensureKnown(RuleDto ruleDto) {
- checkState(known.contains(ruleDto), "unknown RuleDto");
- }
- }
-
- private void persistRepositories(DbSession dbSession, List<RulesDefinition.Repository> repositories) {
- List<String> keys = repositories.stream().map(RulesDefinition.Repository::key).toList();
- Set<String> existingKeys = dbClient.ruleRepositoryDao().selectAllKeys(dbSession);
-
- Map<Boolean, List<RuleRepositoryDto>> dtos = repositories.stream()
- .map(r -> new RuleRepositoryDto(r.key(), r.language(), r.name()))
- .collect(Collectors.groupingBy(i -> existingKeys.contains(i.getKey())));
-
- dbClient.ruleRepositoryDao().update(dbSession, dtos.getOrDefault(true, emptyList()));
- dbClient.ruleRepositoryDao().insert(dbSession, dtos.getOrDefault(false, emptyList()));
- dbClient.ruleRepositoryDao().deleteIfKeyNotIn(dbSession, keys);
- dbSession.commit();
- }
-
- @Override
- public void stop() {
- // nothing
- }
-
- private void registerRules(RegisterRulesContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) {
- Map<RulesDefinition.Rule, RuleDto> dtos = new LinkedHashMap<>(ruleDefs.size());
-
- for (RulesDefinition.Rule ruleDef : ruleDefs) {
- RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
- RuleDto ruleDto = findOrCreateRuleDto(context, session, ruleDef);
- dtos.put(ruleDef, ruleDto);
-
- // we must detect renaming __before__ we modify the DTO
- if (!ruleDto.getKey().equals(ruleKey)) {
- context.renamed(ruleDto);
- ruleDto.setRuleKey(ruleKey);
- }
-
- if (anyMerge(ruleDef, ruleDto)) {
- context.updated(ruleDto);
- }
-
- if (context.isUpdated(ruleDto) || context.isRenamed(ruleDto)) {
- update(session, ruleDto);
- } else if (!context.isCreated(ruleDto)) {
- context.unchanged(ruleDto);
- }
- }
-
- for (Map.Entry<RulesDefinition.Rule, RuleDto> e : dtos.entrySet()) {
- mergeParams(context, e.getKey(), e.getValue(), session);
- updateDeprecatedKeys(context, e.getKey(), e.getValue(), session);
- }
- }
-
- @Nonnull
- private RuleDto findOrCreateRuleDto(RegisterRulesContext context, DbSession session, RulesDefinition.Rule ruleDef) {
- return context.getDbRuleFor(ruleDef)
- .orElseGet(() -> {
- RuleDto newRule = createRuleDto(ruleDef, session);
- context.created(newRule);
- return newRule;
- });
- }
-
- private RuleDto createRuleDto(RulesDefinition.Rule ruleDef, DbSession session) {
- RuleDto ruleDto = new RuleDto()
- .setUuid(uuidFactory.create())
- .setRuleKey(RuleKey.of(ruleDef.repository().key(), ruleDef.key()))
- .setPluginKey(ruleDef.pluginKey())
- .setIsTemplate(ruleDef.template())
- .setConfigKey(ruleDef.internalKey())
- .setLanguage(ruleDef.repository().language())
- .setName(ruleDef.name())
- .setSeverity(ruleDef.severity())
- .setStatus(ruleDef.status())
- .setGapDescription(ruleDef.gapDescription())
- .setSystemTags(ruleDef.tags())
- .setSecurityStandards(ruleDef.securityStandards())
- .setType(RuleType.valueOf(ruleDef.type().name()))
- .setScope(toDtoScope(ruleDef.scope()))
- .setIsExternal(ruleDef.repository().isExternal())
- .setIsAdHoc(false)
- .setCreatedAt(system2.now())
- .setUpdatedAt(system2.now())
- .setEducationPrinciples(ruleDef.educationPrincipleKeys());
-
- if (isNotEmpty(ruleDef.htmlDescription())) {
- ruleDto.setDescriptionFormat(Format.HTML);
- } else if (isNotEmpty(ruleDef.markdownDescription())) {
- ruleDto.setDescriptionFormat(Format.MARKDOWN);
- }
-
- generateRuleDescriptionSections(ruleDef)
- .forEach(ruleDto::addRuleDescriptionSectionDto);
-
- DebtRemediationFunction debtRemediationFunction = ruleDef.debtRemediationFunction();
- if (debtRemediationFunction != null) {
- ruleDto.setDefRemediationFunction(debtRemediationFunction.type().name());
- ruleDto.setDefRemediationGapMultiplier(debtRemediationFunction.gapMultiplier());
- ruleDto.setDefRemediationBaseEffort(debtRemediationFunction.baseEffort());
- ruleDto.setGapDescription(ruleDef.gapDescription());
- }
-
- dbClient.ruleDao().insert(session, ruleDto);
- return ruleDto;
- }
-
- private Set<RuleDescriptionSectionDto> generateRuleDescriptionSections(RulesDefinition.Rule ruleDef) {
- RuleDescriptionSectionsGenerator descriptionSectionGenerator = ruleDescriptionSectionsGeneratorResolver.getRuleDescriptionSectionsGenerator(ruleDef);
- return descriptionSectionGenerator.generateSections(ruleDef);
- }
-
- private static Scope toDtoScope(RuleScope scope) {
- switch (scope) {
- case ALL:
- return Scope.ALL;
- case MAIN:
- return Scope.MAIN;
- case TEST:
- return Scope.TEST;
- default:
- throw new IllegalArgumentException("Unknown rule scope: " + scope);
- }
- }
-
- private boolean anyMerge(RulesDefinition.Rule ruleDef, RuleDto ruleDto) {
- boolean ruleMerged = mergeRule(ruleDef, ruleDto);
- boolean debtDefinitionsMerged = mergeDebtDefinitions(ruleDef, ruleDto);
- boolean tagsMerged = mergeTags(ruleDef, ruleDto);
- boolean securityStandardsMerged = mergeSecurityStandards(ruleDef, ruleDto);
- boolean educationPrinciplesMerged = mergeEducationPrinciples(ruleDef, ruleDto);
- return ruleMerged || debtDefinitionsMerged || tagsMerged || securityStandardsMerged || educationPrinciplesMerged;
- }
-
- private boolean mergeRule(RulesDefinition.Rule def, RuleDto dto) {
- boolean changed = false;
- if (!Objects.equals(dto.getName(), def.name())) {
- dto.setName(def.name());
- changed = true;
- }
- if (mergeDescription(def, dto)) {
- changed = true;
- }
- if (!Objects.equals(dto.getPluginKey(), def.pluginKey())) {
- dto.setPluginKey(def.pluginKey());
- changed = true;
- }
- if (!Objects.equals(dto.getConfigKey(), def.internalKey())) {
- dto.setConfigKey(def.internalKey());
- changed = true;
- }
- String severity = def.severity();
- if (!Objects.equals(dto.getSeverityString(), severity)) {
- dto.setSeverity(severity);
- changed = true;
- }
- boolean isTemplate = def.template();
- if (isTemplate != dto.isTemplate()) {
- dto.setIsTemplate(isTemplate);
- changed = true;
- }
- if (def.status() != dto.getStatus()) {
- dto.setStatus(def.status());
- changed = true;
- }
- if (!Objects.equals(dto.getScope().name(), def.scope().name())) {
- dto.setScope(toDtoScope(def.scope()));
- changed = true;
- }
- if (!Objects.equals(dto.getLanguage(), def.repository().language())) {
- dto.setLanguage(def.repository().language());
- changed = true;
- }
- RuleType type = RuleType.valueOf(def.type().name());
- if (!Objects.equals(dto.getType(), type.getDbConstant())) {
- dto.setType(type);
- changed = true;
- }
- if (dto.isAdHoc()) {
- dto.setIsAdHoc(false);
- changed = true;
- }
- return changed;
- }
-
- private boolean mergeDescription(RulesDefinition.Rule rule, RuleDto ruleDto) {
- Set<RuleDescriptionSectionDto> newRuleDescriptionSectionDtos = generateRuleDescriptionSections(rule);
- if (ruleDescriptionSectionsUnchanged(ruleDto, newRuleDescriptionSectionDtos)) {
- return false;
- }
- ruleDto.replaceRuleDescriptionSectionDtos(newRuleDescriptionSectionDtos);
- if (containsHtmlDescription(rule)) {
- ruleDto.setDescriptionFormat(Format.HTML);
- return true;
- } else if (isNotEmpty(rule.markdownDescription())) {
- ruleDto.setDescriptionFormat(Format.MARKDOWN);
- return true;
- }
- return false;
- }
-
- private static boolean containsHtmlDescription(RulesDefinition.Rule rule) {
- return isNotEmpty(rule.htmlDescription()) || !rule.ruleDescriptionSections().isEmpty();
- }
-
- private static boolean ruleDescriptionSectionsUnchanged(RuleDto ruleDto, Set<RuleDescriptionSectionDto> newRuleDescriptionSectionDtos) {
- if (ruleDto.getRuleDescriptionSectionDtos().size() != newRuleDescriptionSectionDtos.size()) {
- return false;
- }
- return ruleDto.getRuleDescriptionSectionDtos().stream()
- .allMatch(sectionDto -> contains(newRuleDescriptionSectionDtos, sectionDto));
- }
-
- private static boolean contains(Set<RuleDescriptionSectionDto> sectionDtos, RuleDescriptionSectionDto sectionDto) {
- return sectionDtos.stream()
- .filter(s -> s.getKey().equals(sectionDto.getKey()) && s.getContent().equals(sectionDto.getContent()))
- .anyMatch(s -> Objects.equals(s.getContext(), sectionDto.getContext()));
- }
-
- private static boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDto dto) {
- // Debt definitions are set to null if the sub-characteristic and the remediation function are null
- DebtRemediationFunction debtRemediationFunction = def.debtRemediationFunction();
- boolean hasDebt = debtRemediationFunction != null;
- if (hasDebt) {
- return mergeDebtDefinitions(dto,
- debtRemediationFunction.type().name(),
- debtRemediationFunction.gapMultiplier(),
- debtRemediationFunction.baseEffort(),
- def.gapDescription());
- }
- return mergeDebtDefinitions(dto, null, null, null, null);
- }
-
- private static boolean mergeDebtDefinitions(RuleDto dto, @Nullable String remediationFunction,
- @Nullable String remediationCoefficient, @Nullable String remediationOffset, @Nullable String gapDescription) {
- boolean changed = false;
-
- if (!Objects.equals(dto.getDefRemediationFunction(), remediationFunction)) {
- dto.setDefRemediationFunction(remediationFunction);
- changed = true;
- }
- if (!Objects.equals(dto.getDefRemediationGapMultiplier(), remediationCoefficient)) {
- dto.setDefRemediationGapMultiplier(remediationCoefficient);
- changed = true;
- }
- if (!Objects.equals(dto.getDefRemediationBaseEffort(), remediationOffset)) {
- dto.setDefRemediationBaseEffort(remediationOffset);
- changed = true;
- }
- if (!Objects.equals(dto.getGapDescription(), gapDescription)) {
- dto.setGapDescription(gapDescription);
- changed = true;
- }
- return changed;
- }
-
- private void mergeParams(RegisterRulesContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession session) {
- List<RuleParamDto> paramDtos = context.getRuleParametersFor(rule.getUuid());
- Map<String, RuleParamDto> existingParamsByName = new HashMap<>();
-
- Profiler profiler = Profiler.create(Loggers.get(getClass()));
- for (RuleParamDto paramDto : paramDtos) {
- RulesDefinition.Param paramDef = ruleDef.param(paramDto.getName());
- if (paramDef == null) {
- profiler.start();
- dbClient.activeRuleDao().deleteParamsByRuleParam(session, paramDto);
- profiler.stopDebug(format("Propagate deleted param with name %s to active rules of rule %s", paramDto.getName(), rule.getKey()));
- dbClient.ruleDao().deleteRuleParam(session, paramDto.getUuid());
- } else {
- if (mergeParam(paramDto, paramDef)) {
- dbClient.ruleDao().updateRuleParam(session, rule, paramDto);
- }
- existingParamsByName.put(paramDto.getName(), paramDto);
- }
- }
-
- // Create newly parameters
- for (RulesDefinition.Param param : ruleDef.params()) {
- RuleParamDto paramDto = existingParamsByName.get(param.key());
- if (paramDto != null) {
- continue;
- }
- paramDto = RuleParamDto.createFor(rule)
- .setName(param.key())
- .setDescription(param.description())
- .setDefaultValue(param.defaultValue())
- .setType(param.type().toString());
- dbClient.ruleDao().insertRuleParam(session, rule, paramDto);
- if (StringUtils.isEmpty(param.defaultValue())) {
- continue;
- }
- // Propagate the default value to existing active rule parameters
- profiler.start();
- for (ActiveRuleDto activeRule : dbClient.activeRuleDao().selectByRuleUuid(session, rule.getUuid())) {
- ActiveRuleParamDto activeParam = ActiveRuleParamDto.createFor(paramDto).setValue(param.defaultValue());
- dbClient.activeRuleDao().insertParam(session, activeRule, activeParam);
- }
- profiler.stopDebug(format("Propagate new param with name %s to active rules of rule %s", paramDto.getName(), rule.getKey()));
- }
- }
-
- private static boolean mergeParam(RuleParamDto paramDto, RulesDefinition.Param paramDef) {
- boolean changed = false;
- if (!Objects.equals(paramDto.getType(), paramDef.type().toString())) {
- paramDto.setType(paramDef.type().toString());
- changed = true;
- }
- if (!Objects.equals(paramDto.getDefaultValue(), paramDef.defaultValue())) {
- paramDto.setDefaultValue(paramDef.defaultValue());
- changed = true;
- }
- if (!Objects.equals(paramDto.getDescription(), paramDef.description())) {
- paramDto.setDescription(paramDef.description());
- changed = true;
- }
- return changed;
- }
-
- private void updateDeprecatedKeys(RegisterRulesContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession dbSession) {
-
- Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDefinition = SingleDeprecatedRuleKey.from(ruleDef);
- Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDB = context.getDBDeprecatedKeysFor(rule);
-
- // DeprecatedKeys that must be deleted
- List<String> uuidsToBeDeleted = difference(deprecatedRuleKeysFromDB, deprecatedRuleKeysFromDefinition).stream()
- .map(SingleDeprecatedRuleKey::getUuid)
- .toList();
-
- dbClient.ruleDao().deleteDeprecatedRuleKeys(dbSession, uuidsToBeDeleted);
-
- // DeprecatedKeys that must be created
- Sets.SetView<SingleDeprecatedRuleKey> deprecatedRuleKeysToBeCreated = difference(deprecatedRuleKeysFromDefinition, deprecatedRuleKeysFromDB);
-
- deprecatedRuleKeysToBeCreated
- .forEach(r -> dbClient.ruleDao().insert(dbSession, new DeprecatedRuleKeyDto()
- .setUuid(uuidFactory.create())
- .setRuleUuid(rule.getUuid())
- .setOldRepositoryKey(r.getOldRepositoryKey())
- .setOldRuleKey(r.getOldRuleKey())
- .setCreatedAt(system2.now())));
- }
-
- private static boolean mergeTags(RulesDefinition.Rule ruleDef, RuleDto dto) {
- boolean changed = false;
-
- if (RuleStatus.REMOVED == ruleDef.status()) {
- dto.setSystemTags(emptySet());
- changed = true;
- } else if (dto.getSystemTags().size() != ruleDef.tags().size() ||
- !dto.getSystemTags().containsAll(ruleDef.tags())) {
- dto.setSystemTags(ruleDef.tags());
- changed = true;
- }
- return changed;
- }
-
- private static boolean mergeSecurityStandards(RulesDefinition.Rule ruleDef, RuleDto dto) {
- boolean changed = false;
-
- if (RuleStatus.REMOVED == ruleDef.status()) {
- dto.setSecurityStandards(emptySet());
- changed = true;
- } else if (dto.getSecurityStandards().size() != ruleDef.securityStandards().size() ||
- !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) {
- dto.setSecurityStandards(ruleDef.securityStandards());
- changed = true;
- }
- return changed;
- }
-
- private static boolean mergeEducationPrinciples(RulesDefinition.Rule ruleDef, RuleDto dto) {
- boolean changed = false;
- if (dto.getEducationPrinciples().size() != ruleDef.educationPrincipleKeys().size() ||
- !dto.getEducationPrinciples().containsAll(ruleDef.educationPrincipleKeys())) {
- dto.setEducationPrinciples(ruleDef.educationPrincipleKeys());
- changed = true;
- }
- return changed;
- }
-
- private void processRemainingDbRules(RegisterRulesContext recorder, DbSession dbSession) {
- // custom rules check status of template, so they must be processed at the end
- List<RuleDto> customRules = new ArrayList<>();
-
- recorder.getRemaining().forEach(rule -> {
- if (rule.isCustomRule()) {
- customRules.add(rule);
- } else if (!rule.isAdHoc() && rule.getStatus() != RuleStatus.REMOVED) {
- removeRule(dbSession, recorder, rule);
- }
- });
-
- for (RuleDto customRule : customRules) {
- String templateUuid = customRule.getTemplateUuid();
- checkNotNull(templateUuid, "Template uuid of the custom rule '%s' is null", customRule);
- Optional<RuleDto> template = dbClient.ruleDao().selectByUuid(templateUuid, dbSession);
- if (template.isPresent() && template.get().getStatus() != RuleStatus.REMOVED) {
- if (updateCustomRuleFromTemplateRule(customRule, template.get())) {
- recorder.updated(customRule);
- update(dbSession, customRule);
- }
- } else {
- removeRule(dbSession, recorder, customRule);
- }
- }
-
- dbSession.commit();
- }
-
- private void removeRule(DbSession session, RegisterRulesContext recorder, RuleDto rule) {
- LOG.info(format("Disable rule %s", rule.getKey()));
- rule.setStatus(RuleStatus.REMOVED);
- rule.setSystemTags(emptySet());
- update(session, rule);
- // FIXME resetting the tags for all organizations must be handled a different way
- // rule.setTags(Collections.emptySet());
- // update(session, rule.getMetadata());
- recorder.removed(rule);
- if (recorder.getRemoved().count() % 100 == 0) {
- session.commit();
- }
- }
-
- private static boolean updateCustomRuleFromTemplateRule(RuleDto customRule, RuleDto templateRule) {
- boolean changed = false;
- if (!Objects.equals(customRule.getLanguage(), templateRule.getLanguage())) {
- customRule.setLanguage(templateRule.getLanguage());
- changed = true;
- }
- if (!Objects.equals(customRule.getConfigKey(), templateRule.getConfigKey())) {
- customRule.setConfigKey(templateRule.getConfigKey());
- changed = true;
- }
- if (!Objects.equals(customRule.getPluginKey(), templateRule.getPluginKey())) {
- customRule.setPluginKey(templateRule.getPluginKey());
- changed = true;
- }
- if (!Objects.equals(customRule.getDefRemediationFunction(), templateRule.getDefRemediationFunction())) {
- customRule.setDefRemediationFunction(templateRule.getDefRemediationFunction());
- changed = true;
- }
- if (!Objects.equals(customRule.getDefRemediationGapMultiplier(), templateRule.getDefRemediationGapMultiplier())) {
- customRule.setDefRemediationGapMultiplier(templateRule.getDefRemediationGapMultiplier());
- changed = true;
- }
- if (!Objects.equals(customRule.getDefRemediationBaseEffort(), templateRule.getDefRemediationBaseEffort())) {
- customRule.setDefRemediationBaseEffort(templateRule.getDefRemediationBaseEffort());
- changed = true;
- }
- if (!Objects.equals(customRule.getGapDescription(), templateRule.getGapDescription())) {
- customRule.setGapDescription(templateRule.getGapDescription());
- changed = true;
- }
- if (customRule.getStatus() != templateRule.getStatus()) {
- customRule.setStatus(templateRule.getStatus());
- changed = true;
- }
- if (!Objects.equals(customRule.getSeverityString(), templateRule.getSeverityString())) {
- customRule.setSeverity(templateRule.getSeverityString());
- changed = true;
- }
- if (!Objects.equals(customRule.getRepositoryKey(), templateRule.getRepositoryKey())) {
- customRule.setRepositoryKey(templateRule.getRepositoryKey());
- changed = true;
- }
- return changed;
- }
-
- /**
- * SONAR-4642
- * <p/>
- * Remove active rules on repositories that still exists.
- * <p/>
- * For instance, if the javascript repository do not provide anymore some rules, active rules related to this rules will be removed.
- * But if the javascript repository do not exists anymore, then related active rules will not be removed.
- * <p/>
- * The side effect of this approach is that extended repositories will not be managed the same way.
- * If an extended repository do not exists anymore, then related active rules will be removed.
- */
- private List<ActiveRuleChange> removeActiveRulesOnStillExistingRepositories(DbSession dbSession, RegisterRulesContext recorder, List<RulesDefinition.Repository> context) {
- Set<String> existingAndRenamedRepositories = getExistingAndRenamedRepositories(recorder, context);
- List<ActiveRuleChange> changes = new ArrayList<>();
- Profiler profiler = Profiler.create(Loggers.get(getClass()));
-
- recorder.getRemoved()
- .filter(rule -> existingAndRenamedRepositories.contains(rule.getRepositoryKey()))
- .forEach(rule -> {
- // SONAR-4642 Remove active rules only when repository still exists
- profiler.start();
- changes.addAll(qProfileRules.deleteRule(dbSession, rule));
- profiler.stopDebug(format("Remove active rule for rule %s", rule.getKey()));
- });
-
- return changes;
- }
-
- private static Set<String> getExistingAndRenamedRepositories(RegisterRulesContext recorder, Collection<RulesDefinition.Repository> context) {
- return Stream.concat(
- context.stream().map(RulesDefinition.ExtendedRepository::key),
- recorder.getRenamed().map(Map.Entry::getValue).map(RuleKey::repository))
- .collect(Collectors.toSet());
- }
-
- private void update(DbSession session, RuleDto rule) {
- rule.setUpdatedAt(system2.now());
- dbClient.ruleDao().update(session, rule);
- }
-
- private static void verifyRuleKeyConsistency(List<RulesDefinition.Repository> repositories, RegisterRulesContext registerRulesContext) {
- List<RulesDefinition.Rule> definedRules = repositories.stream()
- .flatMap(r -> r.rules().stream())
- .toList();
-
- Set<RuleKey> definedRuleKeys = definedRules.stream()
- .map(r -> RuleKey.of(r.repository().key(), r.key()))
- .collect(Collectors.toSet());
-
- List<RuleKey> definedDeprecatedRuleKeys = definedRules.stream()
- .flatMap(r -> r.deprecatedRuleKeys().stream())
- .toList();
-
- // Find duplicates in declared deprecated rule keys
- Set<RuleKey> duplicates = findDuplicates(definedDeprecatedRuleKeys);
- checkState(duplicates.isEmpty(), "The following deprecated rule keys are declared at least twice [%s]",
- lazyToString(() -> duplicates.stream().map(RuleKey::toString).collect(Collectors.joining(","))));
-
- // Find rule keys that are both deprecated and used
- Set<RuleKey> intersection = intersection(new HashSet<>(definedRuleKeys), new HashSet<>(definedDeprecatedRuleKeys)).immutableCopy();
- checkState(intersection.isEmpty(), "The following rule keys are declared both as deprecated and used key [%s]",
- lazyToString(() -> intersection.stream().map(RuleKey::toString).collect(Collectors.joining(","))));
-
- // Find incorrect usage of deprecated keys
- Map<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey = registerRulesContext.getDbDeprecatedKeysByOldRuleKey();
-
- Set<String> incorrectRuleKeyMessage = definedRules.stream()
- .flatMap(r -> filterInvalidDeprecatedRuleKeys(dbDeprecatedRuleKeysByOldRuleKey, r))
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
-
- checkState(incorrectRuleKeyMessage.isEmpty(), "An incorrect state of deprecated rule keys has been detected.\n %s",
- lazyToString(() -> String.join("\n", incorrectRuleKeyMessage)));
- }
-
- private static Stream<String> filterInvalidDeprecatedRuleKeys(Map<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey, RulesDefinition.Rule rule) {
- return rule.deprecatedRuleKeys().stream()
- .map(rk -> {
- SingleDeprecatedRuleKey singleDeprecatedRuleKey = dbDeprecatedRuleKeysByOldRuleKey.get(rk);
- if (singleDeprecatedRuleKey == null) {
- // new deprecated rule key : OK
- return null;
- }
- RuleKey parentRuleKey = RuleKey.of(rule.repository().key(), rule.key());
- if (parentRuleKey.equals(singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey())) {
- // same parent : OK
- return null;
- }
- if (rule.deprecatedRuleKeys().contains(singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey())) {
- // the new rule is deprecating the old parentRuleKey : OK
- return null;
- }
- return format("The deprecated rule key [%s] was previously deprecated by [%s]. [%s] should be a deprecated key of [%s],",
- rk.toString(),
- singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey().toString(),
- singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey().toString(),
- RuleKey.of(rule.repository().key(), rule.key()).toString());
- });
- }
-
- private static Object lazyToString(Supplier<String> toString) {
- return new Object() {
- @Override
- public String toString() {
- return toString.get();
- }
- };
- }
-
- private static <T> Set<T> findDuplicates(Collection<T> list) {
- Set<T> duplicates = new HashSet<>();
- Set<T> uniques = new HashSet<>();
-
- list.forEach(t -> {
- if (!uniques.add(t)) {
- duplicates.add(t);
- }
- });
-
- return duplicates;
- }
-}
import java.util.Set;
import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
import static java.util.stream.Collectors.toSet;
import static org.sonar.api.utils.Preconditions.checkState;
this.ruleDescriptionSectionsGenerators = ruleDescriptionSectionsGenerators;
}
- RuleDescriptionSectionsGenerator getRuleDescriptionSectionsGenerator(RulesDefinition.Rule ruleDef) {
+ public RuleDescriptionSectionsGenerator getRuleDescriptionSectionsGenerator(RulesDefinition.Rule ruleDef) {
Set<RuleDescriptionSectionsGenerator> generatorsFound = ruleDescriptionSectionsGenerators.stream()
.filter(generator -> generator.isGeneratorForRule(ruleDef))
.collect(toSet());
return generatorsFound.iterator().next();
}
+ public Set<RuleDescriptionSectionDto> generateFor(RulesDefinition.Rule ruleDef) {
+ return getRuleDescriptionSectionsGenerator(ruleDef).generateSections(ruleDef);
+ }
+
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.rule;
-
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.rule.RulesDefinition;
-import org.sonar.db.rule.DeprecatedRuleKeyDto;
-
-@Immutable
-class SingleDeprecatedRuleKey {
- private String oldRuleKey;
- private String oldRepositoryKey;
- private String newRuleKey;
- private String newRepositoryKey;
- private String uuid;
- private String ruleUuid;
-
- /**
- * static methods {@link #from(RulesDefinition.Rule)} and {@link #from(DeprecatedRuleKeyDto)} must be used
- */
- private SingleDeprecatedRuleKey() {
- // empty
- }
-
- public static Set<SingleDeprecatedRuleKey> from(RulesDefinition.Rule rule) {
- rule.deprecatedRuleKeys();
- return rule.deprecatedRuleKeys().stream()
- .map(r -> new SingleDeprecatedRuleKey()
- .setNewRepositoryKey(rule.repository().key())
- .setNewRuleKey(rule.key())
- .setOldRepositoryKey(r.repository())
- .setOldRuleKey(r.rule()))
- .collect(Collectors.toSet());
- }
-
- public static SingleDeprecatedRuleKey from(DeprecatedRuleKeyDto rule) {
- return new SingleDeprecatedRuleKey()
- .setUuid(rule.getUuid())
- .setRuleUuid(rule.getRuleUuid())
- .setNewRepositoryKey(rule.getNewRepositoryKey())
- .setNewRuleKey(rule.getNewRuleKey())
- .setOldRepositoryKey(rule.getOldRepositoryKey())
- .setOldRuleKey(rule.getOldRuleKey());
- }
-
- public String getOldRuleKey() {
- return oldRuleKey;
- }
-
- public String getOldRepositoryKey() {
- return oldRepositoryKey;
- }
-
- public RuleKey getOldRuleKeyAsRuleKey() {
- return RuleKey.of(oldRepositoryKey, oldRuleKey);
- }
-
- public RuleKey getNewRuleKeyAsRuleKey() {
- return RuleKey.of(newRepositoryKey, newRuleKey);
- }
-
- @CheckForNull
- public String getNewRuleKey() {
- return newRuleKey;
- }
-
- @CheckForNull
- public String getNewRepositoryKey() {
- return newRepositoryKey;
- }
-
- @CheckForNull
- public String getUuid() {
- return uuid;
- }
-
- @CheckForNull
- public String getRuleUuid() {
- return ruleUuid;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- SingleDeprecatedRuleKey that = (SingleDeprecatedRuleKey) o;
- return Objects.equals(oldRuleKey, that.oldRuleKey) &&
- Objects.equals(oldRepositoryKey, that.oldRepositoryKey) &&
- Objects.equals(newRuleKey, that.newRuleKey) &&
- Objects.equals(newRepositoryKey, that.newRepositoryKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(oldRuleKey, oldRepositoryKey, newRuleKey, newRepositoryKey);
- }
-
- private SingleDeprecatedRuleKey setRuleUuid(String ruleUuid) {
- this.ruleUuid = ruleUuid;
- return this;
- }
-
- private SingleDeprecatedRuleKey setUuid(String uuid) {
- this.uuid = uuid;
- return this;
- }
-
- private SingleDeprecatedRuleKey setOldRuleKey(String oldRuleKey) {
- this.oldRuleKey = oldRuleKey;
- return this;
- }
-
- private SingleDeprecatedRuleKey setOldRepositoryKey(String oldRepositoryKey) {
- this.oldRepositoryKey = oldRepositoryKey;
- return this;
- }
-
- private SingleDeprecatedRuleKey setNewRuleKey(@Nullable String newRuleKey) {
- this.newRuleKey = newRuleKey;
- return this;
- }
-
- private SingleDeprecatedRuleKey setNewRepositoryKey(@Nullable String newRepositoryKey) {
- this.newRepositoryKey = newRepositoryKey;
- return this;
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.registration;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Sets.intersection;
+import static java.lang.String.format;
+
+public class RulesKeyVerifier {
+
+ void verifyRuleKeyConsistency(List<RulesDefinition.Repository> repositories, RulesRegistrationContext rulesRegistrationContext) {
+ List<RulesDefinition.Rule> definedRules = repositories.stream()
+ .flatMap(r -> r.rules().stream())
+ .toList();
+
+ Set<RuleKey> definedRuleKeys = definedRules.stream()
+ .map(r -> RuleKey.of(r.repository().key(), r.key()))
+ .collect(Collectors.toSet());
+
+ List<RuleKey> definedDeprecatedRuleKeys = definedRules.stream()
+ .flatMap(r -> r.deprecatedRuleKeys().stream())
+ .toList();
+
+ // Find duplicates in declared deprecated rule keys
+ Set<RuleKey> duplicates = findDuplicates(definedDeprecatedRuleKeys);
+ checkState(duplicates.isEmpty(), "The following deprecated rule keys are declared at least twice [%s]",
+ lazyToString(() -> duplicates.stream().map(RuleKey::toString).collect(Collectors.joining(","))));
+
+ // Find rule keys that are both deprecated and used
+ Set<RuleKey> intersection = intersection(new HashSet<>(definedRuleKeys), new HashSet<>(definedDeprecatedRuleKeys)).immutableCopy();
+ checkState(intersection.isEmpty(), "The following rule keys are declared both as deprecated and used key [%s]",
+ lazyToString(() -> intersection.stream().map(RuleKey::toString).collect(Collectors.joining(","))));
+
+ // Find incorrect usage of deprecated keys
+ Map<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey = rulesRegistrationContext.getDbDeprecatedKeysByOldRuleKey();
+
+ Set<String> incorrectRuleKeyMessage = definedRules.stream()
+ .flatMap(r -> filterInvalidDeprecatedRuleKeys(dbDeprecatedRuleKeysByOldRuleKey, r))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ checkState(incorrectRuleKeyMessage.isEmpty(), "An incorrect state of deprecated rule keys has been detected.\n %s",
+ lazyToString(() -> String.join("\n", incorrectRuleKeyMessage)));
+ }
+
+ private static Stream<String> filterInvalidDeprecatedRuleKeys(Map<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey, RulesDefinition.Rule rule) {
+ return rule.deprecatedRuleKeys().stream()
+ .map(rk -> {
+ SingleDeprecatedRuleKey singleDeprecatedRuleKey = dbDeprecatedRuleKeysByOldRuleKey.get(rk);
+ if (singleDeprecatedRuleKey == null) {
+ // new deprecated rule key : OK
+ return null;
+ }
+ RuleKey parentRuleKey = RuleKey.of(rule.repository().key(), rule.key());
+ if (parentRuleKey.equals(singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey())) {
+ // same parent : OK
+ return null;
+ }
+ if (rule.deprecatedRuleKeys().contains(singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey())) {
+ // the new rule is deprecating the old parentRuleKey : OK
+ return null;
+ }
+ return format("The deprecated rule key [%s] was previously deprecated by [%s]. [%s] should be a deprecated key of [%s],",
+ rk.toString(),
+ singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey().toString(),
+ singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey().toString(),
+ RuleKey.of(rule.repository().key(), rule.key()).toString());
+ });
+ }
+
+ private static Object lazyToString(Supplier<String> toString) {
+ return new Object() {
+ @Override
+ public String toString() {
+ return toString.get();
+ }
+ };
+ }
+
+ private static <T> Set<T> findDuplicates(Collection<T> list) {
+ Set<T> duplicates = new HashSet<>();
+ Set<T> uniques = new HashSet<>();
+
+ list.forEach(t -> {
+ if (!uniques.add(t)) {
+ duplicates.add(t);
+ }
+ });
+
+ return duplicates;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.registration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+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 java.util.stream.Stream;
+import javax.annotation.Nonnull;
+import org.sonar.api.Startable;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.System2;
+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.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleRepositoryDto;
+import org.sonar.server.es.metadata.MetadataIndex;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.QProfileRules;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.RuleDefinitionsLoader;
+import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
+import org.sonar.server.rule.WebServerRuleFinder;
+import org.sonar.server.rule.index.RuleIndexer;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+
+/**
+ * Registers rules at server startup
+ */
+public class RulesRegistrant implements Startable {
+
+ private static final Logger LOG = Loggers.get(RulesRegistrant.class);
+
+ private final RuleDefinitionsLoader defLoader;
+ private final QProfileRules qProfileRules;
+ private final DbClient dbClient;
+ private final RuleIndexer ruleIndexer;
+ private final ActiveRuleIndexer activeRuleIndexer;
+ private final Languages languages;
+ private final System2 system2;
+ private final WebServerRuleFinder webServerRuleFinder;
+ private final UuidFactory uuidFactory;
+ private final MetadataIndex metadataIndex;
+ private final RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver;
+ private final RulesKeyVerifier rulesKeyVerifier;
+ private final StartupRuleUpdater startupRuleUpdater;
+
+ public RulesRegistrant(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer,
+ ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2,
+ WebServerRuleFinder webServerRuleFinder, UuidFactory uuidFactory, MetadataIndex metadataIndex,
+ RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver,
+ RulesKeyVerifier rulesKeyVerifier, StartupRuleUpdater startupRuleUpdater) {
+ this.defLoader = defLoader;
+ this.qProfileRules = qProfileRules;
+ this.dbClient = dbClient;
+ this.ruleIndexer = ruleIndexer;
+ this.activeRuleIndexer = activeRuleIndexer;
+ this.languages = languages;
+ this.system2 = system2;
+ this.webServerRuleFinder = webServerRuleFinder;
+ this.uuidFactory = uuidFactory;
+ this.metadataIndex = metadataIndex;
+ this.ruleDescriptionSectionsGeneratorResolver = ruleDescriptionSectionsGeneratorResolver;
+ this.rulesKeyVerifier = rulesKeyVerifier;
+ this.startupRuleUpdater = startupRuleUpdater;
+ }
+
+ @Override
+ public void start() {
+ Profiler profiler = Profiler.create(LOG).startInfo("Register rules");
+ try (DbSession dbSession = dbClient.openSession(true)) {
+ List<RulesDefinition.Repository> repositories = defLoader.load().repositories();
+ RulesRegistrationContext rulesRegistrationContext = RulesRegistrationContext.create(dbClient, dbSession);
+ rulesKeyVerifier.verifyRuleKeyConsistency(repositories, rulesRegistrationContext);
+
+ for (RulesDefinition.ExtendedRepository repoDef : repositories) {
+ if (languages.get(repoDef.language()) != null) {
+ registerRules(rulesRegistrationContext, repoDef.rules(), dbSession);
+ dbSession.commit();
+ }
+ }
+ processRemainingDbRules(rulesRegistrationContext, dbSession);
+ List<ActiveRuleChange> changes = removeActiveRulesOnStillExistingRepositories(dbSession, rulesRegistrationContext, repositories);
+ dbSession.commit();
+
+ persistRepositories(dbSession, repositories);
+ // FIXME lack of resiliency, active rules index is corrupted if rule index fails
+ // to be updated. Only a single DB commit should be executed.
+ ruleIndexer.commitAndIndex(dbSession, rulesRegistrationContext.getAllModified().map(RuleDto::getUuid).collect(Collectors.toSet()));
+ changes.forEach(arChange -> dbClient.qProfileChangeDao().insert(dbSession, arChange.toDto(null)));
+ activeRuleIndexer.commitAndIndex(dbSession, changes);
+ rulesRegistrationContext.getRenamed().forEach(e -> LOG.info("Rule {} re-keyed to {}", e.getValue(), e.getKey().getKey()));
+ profiler.stopDebug();
+
+ if (!rulesRegistrationContext.hasDbRules()) {
+ Stream.concat(ruleIndexer.getIndexTypes().stream(), activeRuleIndexer.getIndexTypes().stream())
+ .forEach(t -> metadataIndex.setInitialized(t, true));
+ }
+
+ webServerRuleFinder.startCaching();
+ }
+ }
+
+ private void persistRepositories(DbSession dbSession, List<RulesDefinition.Repository> repositories) {
+ List<String> keys = repositories.stream().map(RulesDefinition.Repository::key).toList();
+ Set<String> existingKeys = dbClient.ruleRepositoryDao().selectAllKeys(dbSession);
+
+ Map<Boolean, List<RuleRepositoryDto>> dtos = repositories.stream()
+ .map(r -> new RuleRepositoryDto(r.key(), r.language(), r.name()))
+ .collect(Collectors.groupingBy(i -> existingKeys.contains(i.getKey())));
+
+ dbClient.ruleRepositoryDao().update(dbSession, dtos.getOrDefault(true, emptyList()));
+ dbClient.ruleRepositoryDao().insert(dbSession, dtos.getOrDefault(false, emptyList()));
+ dbClient.ruleRepositoryDao().deleteIfKeyNotIn(dbSession, keys);
+ dbSession.commit();
+ }
+
+ @Override
+ public void stop() {
+ // nothing
+ }
+
+ private void registerRules(RulesRegistrationContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) {
+ Map<RulesDefinition.Rule, RuleDto> dtos = new LinkedHashMap<>(ruleDefs.size());
+
+ for (RulesDefinition.Rule ruleDef : ruleDefs) {
+ RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
+ RuleDto ruleDto = findOrCreateRuleDto(context, session, ruleDef);
+ dtos.put(ruleDef, ruleDto);
+
+ // we must detect renaming __before__ we modify the DTO
+ if (!ruleDto.getKey().equals(ruleKey)) {
+ context.renamed(ruleDto);
+ ruleDto.setRuleKey(ruleKey);
+ }
+
+ if (startupRuleUpdater.findChangesAndUpdateRule(ruleDef, ruleDto)) {
+ context.updated(ruleDto);
+ }
+
+ if (context.isUpdated(ruleDto) || context.isRenamed(ruleDto)) {
+ update(session, ruleDto);
+ } else if (!context.isCreated(ruleDto)) {
+ context.unchanged(ruleDto);
+ }
+ }
+
+ for (Map.Entry<RulesDefinition.Rule, RuleDto> e : dtos.entrySet()) {
+ startupRuleUpdater.mergeParams(context, e.getKey(), e.getValue(), session);
+ startupRuleUpdater.updateDeprecatedKeys(context, e.getKey(), e.getValue(), session);
+ }
+ }
+
+ @Nonnull
+ private RuleDto findOrCreateRuleDto(RulesRegistrationContext context, DbSession session, RulesDefinition.Rule ruleDef) {
+ return context.getDbRuleFor(ruleDef)
+ .orElseGet(() -> {
+ RuleDto newRule = RuleDto.from(ruleDef, ruleDescriptionSectionsGeneratorResolver.generateFor(ruleDef), uuidFactory, system2.now());
+ dbClient.ruleDao().insert(session, newRule);
+ context.created(newRule);
+ return newRule;
+ });
+ }
+
+ private void processRemainingDbRules(RulesRegistrationContext recorder, DbSession dbSession) {
+ // custom rules check status of template, so they must be processed at the end
+ List<RuleDto> customRules = new ArrayList<>();
+
+ recorder.getRemaining().forEach(rule -> {
+ if (rule.isCustomRule()) {
+ customRules.add(rule);
+ } else if (!rule.isAdHoc() && rule.getStatus() != RuleStatus.REMOVED) {
+ removeRule(dbSession, recorder, rule);
+ }
+ });
+
+ for (RuleDto customRule : customRules) {
+ String templateUuid = customRule.getTemplateUuid();
+ checkNotNull(templateUuid, "Template uuid of the custom rule '%s' is null", customRule);
+ Optional<RuleDto> template = dbClient.ruleDao().selectByUuid(templateUuid, dbSession);
+ if (template.isPresent() && template.get().getStatus() != RuleStatus.REMOVED) {
+ if (updateCustomRuleFromTemplateRule(customRule, template.get())) {
+ recorder.updated(customRule);
+ update(dbSession, customRule);
+ }
+ } else {
+ removeRule(dbSession, recorder, customRule);
+ }
+ }
+
+ dbSession.commit();
+ }
+
+ private void removeRule(DbSession session, RulesRegistrationContext recorder, RuleDto rule) {
+ LOG.info(format("Disable rule %s", rule.getKey()));
+ rule.setStatus(RuleStatus.REMOVED);
+ rule.setSystemTags(emptySet());
+ update(session, rule);
+ // FIXME resetting the tags for all organizations must be handled a different way
+ // rule.setTags(Collections.emptySet());
+ // update(session, rule.getMetadata());
+ recorder.removed(rule);
+ if (recorder.getRemoved().count() % 100 == 0) {
+ session.commit();
+ }
+ }
+
+ private static boolean updateCustomRuleFromTemplateRule(RuleDto customRule, RuleDto templateRule) {
+ boolean changed = false;
+ if (!Objects.equals(customRule.getLanguage(), templateRule.getLanguage())) {
+ customRule.setLanguage(templateRule.getLanguage());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getConfigKey(), templateRule.getConfigKey())) {
+ customRule.setConfigKey(templateRule.getConfigKey());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getPluginKey(), templateRule.getPluginKey())) {
+ customRule.setPluginKey(templateRule.getPluginKey());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getDefRemediationFunction(), templateRule.getDefRemediationFunction())) {
+ customRule.setDefRemediationFunction(templateRule.getDefRemediationFunction());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getDefRemediationGapMultiplier(), templateRule.getDefRemediationGapMultiplier())) {
+ customRule.setDefRemediationGapMultiplier(templateRule.getDefRemediationGapMultiplier());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getDefRemediationBaseEffort(), templateRule.getDefRemediationBaseEffort())) {
+ customRule.setDefRemediationBaseEffort(templateRule.getDefRemediationBaseEffort());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getGapDescription(), templateRule.getGapDescription())) {
+ customRule.setGapDescription(templateRule.getGapDescription());
+ changed = true;
+ }
+ if (customRule.getStatus() != templateRule.getStatus()) {
+ customRule.setStatus(templateRule.getStatus());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getSeverityString(), templateRule.getSeverityString())) {
+ customRule.setSeverity(templateRule.getSeverityString());
+ changed = true;
+ }
+ if (!Objects.equals(customRule.getRepositoryKey(), templateRule.getRepositoryKey())) {
+ customRule.setRepositoryKey(templateRule.getRepositoryKey());
+ changed = true;
+ }
+ return changed;
+ }
+
+ /**
+ * SONAR-4642
+ * <p/>
+ * Remove active rules on repositories that still exists.
+ * <p/>
+ * For instance, if the javascript repository do not provide anymore some rules, active rules related to this rules will be removed.
+ * But if the javascript repository do not exists anymore, then related active rules will not be removed.
+ * <p/>
+ * The side effect of this approach is that extended repositories will not be managed the same way.
+ * If an extended repository do not exists anymore, then related active rules will be removed.
+ */
+ private List<ActiveRuleChange> removeActiveRulesOnStillExistingRepositories(DbSession dbSession, RulesRegistrationContext recorder, List<RulesDefinition.Repository> context) {
+ Set<String> existingAndRenamedRepositories = getExistingAndRenamedRepositories(recorder, context);
+ List<ActiveRuleChange> changes = new ArrayList<>();
+ Profiler profiler = Profiler.create(LOG);
+
+ recorder.getRemoved()
+ .filter(rule -> existingAndRenamedRepositories.contains(rule.getRepositoryKey()))
+ .forEach(rule -> {
+ // SONAR-4642 Remove active rules only when repository still exists
+ profiler.start();
+ changes.addAll(qProfileRules.deleteRule(dbSession, rule));
+ profiler.stopDebug(format("Remove active rule for rule %s", rule.getKey()));
+ });
+
+ return changes;
+ }
+
+ private static Set<String> getExistingAndRenamedRepositories(RulesRegistrationContext recorder, Collection<RulesDefinition.Repository> context) {
+ return Stream.concat(
+ context.stream().map(RulesDefinition.ExtendedRepository::key),
+ recorder.getRenamed().map(Map.Entry::getValue).map(RuleKey::repository))
+ .collect(Collectors.toSet());
+ }
+
+ private void update(DbSession session, RuleDto rule) {
+ rule.setUpdatedAt(system2.now());
+ dbClient.ruleDao().update(session, rule);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.registration;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableMap;
+
+class RulesRegistrationContext {
+
+ private static final Logger LOG = Loggers.get(RulesRegistrationContext.class);
+
+ // initial immutable data
+ private final Map<RuleKey, RuleDto> dbRules;
+ private final Set<RuleDto> known;
+ private final Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid;
+ private final Map<String, List<RuleParamDto>> ruleParamsByRuleUuid;
+ private final Map<RuleKey, RuleDto> dbRulesByDbDeprecatedKey;
+ // mutable data
+ private final Set<RuleDto> created = new HashSet<>();
+ private final Map<RuleDto, RuleKey> renamed = new HashMap<>();
+ private final Set<RuleDto> updated = new HashSet<>();
+ private final Set<RuleDto> unchanged = new HashSet<>();
+ private final Set<RuleDto> removed = new HashSet<>();
+
+ private RulesRegistrationContext(Map<RuleKey, RuleDto> dbRules, Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid,
+ Map<String, List<RuleParamDto>> ruleParamsByRuleUuid) {
+ this.dbRules = ImmutableMap.copyOf(dbRules);
+ this.known = ImmutableSet.copyOf(dbRules.values());
+ this.dbDeprecatedKeysByUuid = dbDeprecatedKeysByUuid;
+ this.ruleParamsByRuleUuid = ruleParamsByRuleUuid;
+ this.dbRulesByDbDeprecatedKey = buildDbRulesByDbDeprecatedKey(dbDeprecatedKeysByUuid, dbRules);
+ }
+
+ private static Map<RuleKey, RuleDto> buildDbRulesByDbDeprecatedKey(Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid,
+ Map<RuleKey, RuleDto> dbRules) {
+ Map<String, RuleDto> dbRulesByRuleUuid = dbRules.values().stream()
+ .collect(Collectors.toMap(RuleDto::getUuid, Function.identity()));
+
+ Map<RuleKey, RuleDto> rulesByKey = new LinkedHashMap<>();
+ for (Map.Entry<String, Set<SingleDeprecatedRuleKey>> entry : dbDeprecatedKeysByUuid.entrySet()) {
+ String ruleUuid = entry.getKey();
+ RuleDto rule = dbRulesByRuleUuid.get(ruleUuid);
+ if (rule == null) {
+ LOG.warn("Could not retrieve rule with uuid %s referenced by a deprecated rule key. " +
+ "The following deprecated rule keys seem to be referencing a non-existing rule",
+ ruleUuid, entry.getValue());
+ } else {
+ entry.getValue().forEach(d -> rulesByKey.put(d.getOldRuleKeyAsRuleKey(), rule));
+ }
+ }
+ return unmodifiableMap(rulesByKey);
+ }
+
+ boolean hasDbRules() {
+ return !dbRules.isEmpty();
+ }
+
+ Optional<RuleDto> getDbRuleFor(RulesDefinition.Rule ruleDef) {
+ RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
+ Optional<RuleDto> res = Stream.concat(Stream.of(ruleKey), ruleDef.deprecatedRuleKeys().stream())
+ .map(dbRules::get)
+ .filter(Objects::nonNull)
+ .findFirst();
+ // may occur in case of plugin downgrade
+ if (res.isEmpty()) {
+ return Optional.ofNullable(dbRulesByDbDeprecatedKey.get(ruleKey));
+ }
+ return res;
+ }
+
+ Map<RuleKey, SingleDeprecatedRuleKey> getDbDeprecatedKeysByOldRuleKey() {
+ return dbDeprecatedKeysByUuid.values().stream()
+ .flatMap(Collection::stream)
+ .collect(Collectors.toMap(SingleDeprecatedRuleKey::getOldRuleKeyAsRuleKey, Function.identity()));
+ }
+
+ Set<SingleDeprecatedRuleKey> getDBDeprecatedKeysFor(RuleDto rule) {
+ return dbDeprecatedKeysByUuid.getOrDefault(rule.getUuid(), emptySet());
+ }
+
+ List<RuleParamDto> getRuleParametersFor(String ruleUuid) {
+ return ruleParamsByRuleUuid.getOrDefault(ruleUuid, emptyList());
+ }
+
+ Stream<RuleDto> getRemaining() {
+ Set<RuleDto> res = new HashSet<>(dbRules.values());
+ res.removeAll(unchanged);
+ res.removeAll(renamed.keySet());
+ res.removeAll(updated);
+ res.removeAll(removed);
+ return res.stream();
+ }
+
+ Stream<RuleDto> getRemoved() {
+ return removed.stream();
+ }
+
+ public Stream<Map.Entry<RuleDto, RuleKey>> getRenamed() {
+ return renamed.entrySet().stream();
+ }
+
+ Stream<RuleDto> getAllModified() {
+ return Stream.of(
+ created.stream(),
+ updated.stream(),
+ removed.stream(),
+ renamed.keySet().stream())
+ .flatMap(s -> s);
+ }
+
+ boolean isCreated(RuleDto ruleDto) {
+ return created.contains(ruleDto);
+ }
+
+ boolean isRenamed(RuleDto ruleDto) {
+ return renamed.containsKey(ruleDto);
+ }
+
+ boolean isUpdated(RuleDto ruleDto) {
+ return updated.contains(ruleDto);
+ }
+
+ void created(RuleDto ruleDto) {
+ checkState(!known.contains(ruleDto), "known RuleDto can't be created");
+ created.add(ruleDto);
+ }
+
+ void renamed(RuleDto ruleDto) {
+ ensureKnown(ruleDto);
+ renamed.put(ruleDto, ruleDto.getKey());
+ }
+
+ void updated(RuleDto ruleDto) {
+ ensureKnown(ruleDto);
+ updated.add(ruleDto);
+ }
+
+ void removed(RuleDto ruleDto) {
+ ensureKnown(ruleDto);
+ removed.add(ruleDto);
+ }
+
+ void unchanged(RuleDto ruleDto) {
+ ensureKnown(ruleDto);
+ unchanged.add(ruleDto);
+ }
+
+ private void ensureKnown(RuleDto ruleDto) {
+ checkState(known.contains(ruleDto), "unknown RuleDto");
+ }
+
+ static RulesRegistrationContext create(DbClient dbClient, DbSession dbSession) {
+ Map<RuleKey, RuleDto> allRules = dbClient.ruleDao().selectAll(dbSession).stream()
+ .collect(Collectors.toMap(RuleDto::getKey, Function.identity()));
+ Map<String, Set<SingleDeprecatedRuleKey>> existingDeprecatedKeysById = loadDeprecatedRuleKeys(dbClient, dbSession);
+ Map<String, List<RuleParamDto>> ruleParamsByRuleUuid = loadAllRuleParameters(dbClient, dbSession);
+ return new RulesRegistrationContext(allRules, existingDeprecatedKeysById, ruleParamsByRuleUuid);
+ }
+
+ private static Map<String, List<RuleParamDto>> loadAllRuleParameters(DbClient dbClient, DbSession dbSession) {
+ return dbClient.ruleDao().selectAllRuleParams(dbSession).stream()
+ .collect(Collectors.groupingBy(RuleParamDto::getRuleUuid));
+ }
+
+ private static Map<String, Set<SingleDeprecatedRuleKey>> loadDeprecatedRuleKeys(DbClient dbClient, DbSession dbSession) {
+ return dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
+ .map(SingleDeprecatedRuleKey::from)
+ .collect(Collectors.groupingBy(SingleDeprecatedRuleKey::getRuleUuid, Collectors.toSet()));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.registration;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+
+@Immutable
+class SingleDeprecatedRuleKey {
+ private String oldRuleKey;
+ private String oldRepositoryKey;
+ private String newRuleKey;
+ private String newRepositoryKey;
+ private String uuid;
+ private String ruleUuid;
+
+ /**
+ * static methods {@link #from(RulesDefinition.Rule)} and {@link #from(DeprecatedRuleKeyDto)} must be used
+ */
+ private SingleDeprecatedRuleKey() {
+ // empty
+ }
+
+ public static Set<SingleDeprecatedRuleKey> from(RulesDefinition.Rule rule) {
+ rule.deprecatedRuleKeys();
+ return rule.deprecatedRuleKeys().stream()
+ .map(r -> new SingleDeprecatedRuleKey()
+ .setNewRepositoryKey(rule.repository().key())
+ .setNewRuleKey(rule.key())
+ .setOldRepositoryKey(r.repository())
+ .setOldRuleKey(r.rule()))
+ .collect(Collectors.toSet());
+ }
+
+ public static SingleDeprecatedRuleKey from(DeprecatedRuleKeyDto rule) {
+ return new SingleDeprecatedRuleKey()
+ .setUuid(rule.getUuid())
+ .setRuleUuid(rule.getRuleUuid())
+ .setNewRepositoryKey(rule.getNewRepositoryKey())
+ .setNewRuleKey(rule.getNewRuleKey())
+ .setOldRepositoryKey(rule.getOldRepositoryKey())
+ .setOldRuleKey(rule.getOldRuleKey());
+ }
+
+ public String getOldRuleKey() {
+ return oldRuleKey;
+ }
+
+ public String getOldRepositoryKey() {
+ return oldRepositoryKey;
+ }
+
+ public RuleKey getOldRuleKeyAsRuleKey() {
+ return RuleKey.of(oldRepositoryKey, oldRuleKey);
+ }
+
+ public RuleKey getNewRuleKeyAsRuleKey() {
+ return RuleKey.of(newRepositoryKey, newRuleKey);
+ }
+
+ @CheckForNull
+ public String getNewRuleKey() {
+ return newRuleKey;
+ }
+
+ @CheckForNull
+ public String getNewRepositoryKey() {
+ return newRepositoryKey;
+ }
+
+ @CheckForNull
+ public String getUuid() {
+ return uuid;
+ }
+
+ @CheckForNull
+ public String getRuleUuid() {
+ return ruleUuid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SingleDeprecatedRuleKey that = (SingleDeprecatedRuleKey) o;
+ return Objects.equals(oldRuleKey, that.oldRuleKey) &&
+ Objects.equals(oldRepositoryKey, that.oldRepositoryKey) &&
+ Objects.equals(newRuleKey, that.newRuleKey) &&
+ Objects.equals(newRepositoryKey, that.newRepositoryKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(oldRuleKey, oldRepositoryKey, newRuleKey, newRepositoryKey);
+ }
+
+ private SingleDeprecatedRuleKey setRuleUuid(String ruleUuid) {
+ this.ruleUuid = ruleUuid;
+ return this;
+ }
+
+ private SingleDeprecatedRuleKey setUuid(String uuid) {
+ this.uuid = uuid;
+ return this;
+ }
+
+ private SingleDeprecatedRuleKey setOldRuleKey(String oldRuleKey) {
+ this.oldRuleKey = oldRuleKey;
+ return this;
+ }
+
+ private SingleDeprecatedRuleKey setOldRepositoryKey(String oldRepositoryKey) {
+ this.oldRepositoryKey = oldRepositoryKey;
+ return this;
+ }
+
+ private SingleDeprecatedRuleKey setNewRuleKey(@Nullable String newRuleKey) {
+ this.newRuleKey = newRuleKey;
+ return this;
+ }
+
+ private SingleDeprecatedRuleKey setNewRepositoryKey(@Nullable String newRepositoryKey) {
+ this.newRepositoryKey = newRepositoryKey;
+ return this;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.registration;
+
+import com.google.common.collect.Sets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.System2;
+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.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
+
+import static com.google.common.collect.Sets.difference;
+import static java.lang.String.format;
+import static java.util.Collections.emptySet;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+
+/**
+ * The class detects changes between the rule definition coming from plugins during startup and rule from database.
+ * In case any changes are detected the rule is updated with the new information from plugin.
+ */
+public class StartupRuleUpdater {
+
+ private static final Logger LOG = Loggers.get(StartupRuleUpdater.class);
+
+ private final DbClient dbClient;
+ private final System2 system2;
+ private final UuidFactory uuidFactory;
+ private final RuleDescriptionSectionsGeneratorResolver sectionsGeneratorResolver;
+
+ public StartupRuleUpdater(DbClient dbClient, System2 system2, UuidFactory uuidFactory,
+ RuleDescriptionSectionsGeneratorResolver sectionsGeneratorResolver) {
+ this.dbClient = dbClient;
+ this.system2 = system2;
+ this.uuidFactory = uuidFactory;
+ this.sectionsGeneratorResolver = sectionsGeneratorResolver;
+ }
+
+ /**
+ * Returns true in case there was any change detected between rule in the database and rule from the plugin.
+ */
+ boolean findChangesAndUpdateRule(RulesDefinition.Rule ruleDef, RuleDto ruleDto) {
+ boolean ruleMerged = mergeRule(ruleDef, ruleDto);
+ boolean debtDefinitionsMerged = mergeDebtDefinitions(ruleDef, ruleDto);
+ boolean tagsMerged = mergeTags(ruleDef, ruleDto);
+ boolean securityStandardsMerged = mergeSecurityStandards(ruleDef, ruleDto);
+ boolean educationPrinciplesMerged = mergeEducationPrinciples(ruleDef, ruleDto);
+ return ruleMerged || debtDefinitionsMerged || tagsMerged || securityStandardsMerged || educationPrinciplesMerged;
+ }
+
+ void updateDeprecatedKeys(RulesRegistrationContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession dbSession) {
+ Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDefinition = SingleDeprecatedRuleKey.from(ruleDef);
+ Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDB = context.getDBDeprecatedKeysFor(rule);
+
+ // DeprecatedKeys that must be deleted
+ List<String> uuidsToBeDeleted = difference(deprecatedRuleKeysFromDB, deprecatedRuleKeysFromDefinition).stream()
+ .map(SingleDeprecatedRuleKey::getUuid)
+ .toList();
+
+ dbClient.ruleDao().deleteDeprecatedRuleKeys(dbSession, uuidsToBeDeleted);
+
+ // DeprecatedKeys that must be created
+ Sets.SetView<SingleDeprecatedRuleKey> deprecatedRuleKeysToBeCreated = difference(deprecatedRuleKeysFromDefinition, deprecatedRuleKeysFromDB);
+
+ deprecatedRuleKeysToBeCreated
+ .forEach(r -> dbClient.ruleDao().insert(dbSession, new DeprecatedRuleKeyDto()
+ .setUuid(uuidFactory.create())
+ .setRuleUuid(rule.getUuid())
+ .setOldRepositoryKey(r.getOldRepositoryKey())
+ .setOldRuleKey(r.getOldRuleKey())
+ .setCreatedAt(system2.now())));
+ }
+
+ private boolean mergeRule(RulesDefinition.Rule def, RuleDto dto) {
+ boolean changed = false;
+ if (!Objects.equals(dto.getName(), def.name())) {
+ dto.setName(def.name());
+ changed = true;
+ }
+ if (mergeDescription(def, dto)) {
+ changed = true;
+ }
+ if (!Objects.equals(dto.getPluginKey(), def.pluginKey())) {
+ dto.setPluginKey(def.pluginKey());
+ changed = true;
+ }
+ if (!Objects.equals(dto.getConfigKey(), def.internalKey())) {
+ dto.setConfigKey(def.internalKey());
+ changed = true;
+ }
+ String severity = def.severity();
+ if (!Objects.equals(dto.getSeverityString(), severity)) {
+ dto.setSeverity(severity);
+ changed = true;
+ }
+ boolean isTemplate = def.template();
+ if (isTemplate != dto.isTemplate()) {
+ dto.setIsTemplate(isTemplate);
+ changed = true;
+ }
+ if (def.status() != dto.getStatus()) {
+ dto.setStatus(def.status());
+ changed = true;
+ }
+ if (!Objects.equals(dto.getScope().name(), def.scope().name())) {
+ dto.setScope(RuleDto.Scope.valueOf(def.scope().name()));
+ changed = true;
+ }
+ if (!Objects.equals(dto.getLanguage(), def.repository().language())) {
+ dto.setLanguage(def.repository().language());
+ changed = true;
+ }
+ RuleType type = RuleType.valueOf(def.type().name());
+ if (!Objects.equals(dto.getType(), type.getDbConstant())) {
+ dto.setType(type);
+ changed = true;
+ }
+ if (dto.isAdHoc()) {
+ dto.setIsAdHoc(false);
+ changed = true;
+ }
+ return changed;
+ }
+
+ private static boolean mergeEducationPrinciples(RulesDefinition.Rule ruleDef, RuleDto dto) {
+ boolean changed = false;
+ if (dto.getEducationPrinciples().size() != ruleDef.educationPrincipleKeys().size() ||
+ !dto.getEducationPrinciples().containsAll(ruleDef.educationPrincipleKeys())) {
+ dto.setEducationPrinciples(ruleDef.educationPrincipleKeys());
+ changed = true;
+ }
+ return changed;
+ }
+
+ private static boolean mergeTags(RulesDefinition.Rule ruleDef, RuleDto dto) {
+ boolean changed = false;
+
+ if (RuleStatus.REMOVED == ruleDef.status()) {
+ dto.setSystemTags(emptySet());
+ changed = true;
+ } else if (dto.getSystemTags().size() != ruleDef.tags().size() ||
+ !dto.getSystemTags().containsAll(ruleDef.tags())) {
+ dto.setSystemTags(ruleDef.tags());
+ changed = true;
+ }
+ return changed;
+ }
+
+ private static boolean mergeSecurityStandards(RulesDefinition.Rule ruleDef, RuleDto dto) {
+ boolean changed = false;
+
+ if (RuleStatus.REMOVED == ruleDef.status()) {
+ dto.setSecurityStandards(emptySet());
+ changed = true;
+ } else if (dto.getSecurityStandards().size() != ruleDef.securityStandards().size() ||
+ !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) {
+ dto.setSecurityStandards(ruleDef.securityStandards());
+ changed = true;
+ }
+ return changed;
+ }
+
+ private static boolean containsHtmlDescription(RulesDefinition.Rule rule) {
+ return isNotEmpty(rule.htmlDescription()) || !rule.ruleDescriptionSections().isEmpty();
+ }
+
+ private static boolean ruleDescriptionSectionsUnchanged(RuleDto ruleDto, Set<RuleDescriptionSectionDto> newRuleDescriptionSectionDtos) {
+ if (ruleDto.getRuleDescriptionSectionDtos().size() != newRuleDescriptionSectionDtos.size()) {
+ return false;
+ }
+ return ruleDto.getRuleDescriptionSectionDtos().stream()
+ .allMatch(sectionDto -> contains(newRuleDescriptionSectionDtos, sectionDto));
+ }
+
+ private static boolean contains(Set<RuleDescriptionSectionDto> sectionDtos, RuleDescriptionSectionDto sectionDto) {
+ return sectionDtos.stream()
+ .filter(s -> s.getKey().equals(sectionDto.getKey()) && s.getContent().equals(sectionDto.getContent()))
+ .anyMatch(s -> Objects.equals(s.getContext(), sectionDto.getContext()));
+ }
+
+ private static boolean mergeDebtDefinitions(RuleDto dto, @Nullable String remediationFunction,
+ @Nullable String remediationCoefficient, @Nullable String remediationOffset, @Nullable String gapDescription) {
+ boolean changed = false;
+
+ if (!Objects.equals(dto.getDefRemediationFunction(), remediationFunction)) {
+ dto.setDefRemediationFunction(remediationFunction);
+ changed = true;
+ }
+ if (!Objects.equals(dto.getDefRemediationGapMultiplier(), remediationCoefficient)) {
+ dto.setDefRemediationGapMultiplier(remediationCoefficient);
+ changed = true;
+ }
+ if (!Objects.equals(dto.getDefRemediationBaseEffort(), remediationOffset)) {
+ dto.setDefRemediationBaseEffort(remediationOffset);
+ changed = true;
+ }
+ if (!Objects.equals(dto.getGapDescription(), gapDescription)) {
+ dto.setGapDescription(gapDescription);
+ changed = true;
+ }
+ return changed;
+ }
+
+
+ private static boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDto dto) {
+ // Debt definitions are set to null if the sub-characteristic and the remediation function are null
+ DebtRemediationFunction debtRemediationFunction = def.debtRemediationFunction();
+ boolean hasDebt = debtRemediationFunction != null;
+ if (hasDebt) {
+ return mergeDebtDefinitions(dto,
+ debtRemediationFunction.type().name(),
+ debtRemediationFunction.gapMultiplier(),
+ debtRemediationFunction.baseEffort(),
+ def.gapDescription());
+ }
+ return mergeDebtDefinitions(dto, null, null, null, null);
+ }
+
+
+ private boolean mergeDescription(RulesDefinition.Rule rule, RuleDto ruleDto) {
+ Set<RuleDescriptionSectionDto> newRuleDescriptionSectionDtos = sectionsGeneratorResolver.generateFor(rule);
+ if (ruleDescriptionSectionsUnchanged(ruleDto, newRuleDescriptionSectionDtos)) {
+ return false;
+ }
+ ruleDto.replaceRuleDescriptionSectionDtos(newRuleDescriptionSectionDtos);
+ if (containsHtmlDescription(rule)) {
+ ruleDto.setDescriptionFormat(RuleDto.Format.HTML);
+ return true;
+ } else if (isNotEmpty(rule.markdownDescription())) {
+ ruleDto.setDescriptionFormat(RuleDto.Format.MARKDOWN);
+ return true;
+ }
+ return false;
+ }
+
+
+ void mergeParams(RulesRegistrationContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession session) {
+ List<RuleParamDto> paramDtos = context.getRuleParametersFor(rule.getUuid());
+ Map<String, RuleParamDto> existingParamsByName = new HashMap<>();
+
+ Profiler profiler = Profiler.create(LOG);
+ for (RuleParamDto paramDto : paramDtos) {
+ RulesDefinition.Param paramDef = ruleDef.param(paramDto.getName());
+ if (paramDef == null) {
+ profiler.start();
+ dbClient.activeRuleDao().deleteParamsByRuleParam(session, paramDto);
+ profiler.stopDebug(format("Propagate deleted param with name %s to active rules of rule %s", paramDto.getName(), rule.getKey()));
+ dbClient.ruleDao().deleteRuleParam(session, paramDto.getUuid());
+ } else {
+ if (mergeParam(paramDto, paramDef)) {
+ dbClient.ruleDao().updateRuleParam(session, rule, paramDto);
+ }
+ existingParamsByName.put(paramDto.getName(), paramDto);
+ }
+ }
+
+ // Create newly parameters
+ for (RulesDefinition.Param param : ruleDef.params()) {
+ RuleParamDto paramDto = existingParamsByName.get(param.key());
+ if (paramDto != null) {
+ continue;
+ }
+ paramDto = RuleParamDto.createFor(rule)
+ .setName(param.key())
+ .setDescription(param.description())
+ .setDefaultValue(param.defaultValue())
+ .setType(param.type().toString());
+ dbClient.ruleDao().insertRuleParam(session, rule, paramDto);
+ if (StringUtils.isEmpty(param.defaultValue())) {
+ continue;
+ }
+ // Propagate the default value to existing active rule parameters
+ profiler.start();
+ for (ActiveRuleDto activeRule : dbClient.activeRuleDao().selectByRuleUuid(session, rule.getUuid())) {
+ ActiveRuleParamDto activeParam = ActiveRuleParamDto.createFor(paramDto).setValue(param.defaultValue());
+ dbClient.activeRuleDao().insertParam(session, activeRule, activeParam);
+ }
+ profiler.stopDebug(format("Propagate new param with name %s to active rules of rule %s", paramDto.getName(), rule.getKey()));
+ }
+ }
+
+ private static boolean mergeParam(RuleParamDto paramDto, RulesDefinition.Param paramDef) {
+ boolean changed = false;
+ if (!Objects.equals(paramDto.getType(), paramDef.type().toString())) {
+ paramDto.setType(paramDef.type().toString());
+ changed = true;
+ }
+ if (!Objects.equals(paramDto.getDefaultValue(), paramDef.defaultValue())) {
+ paramDto.setDefaultValue(paramDef.defaultValue());
+ changed = true;
+ }
+ if (!Objects.equals(paramDto.getDescription(), paramDef.description())) {
+ paramDto.setDescription(paramDef.description());
+ changed = true;
+ }
+ return changed;
+ }
+
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.rule;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.elasticsearch.common.util.set.Sets;
-import org.jetbrains.annotations.Nullable;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleScope;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.server.debt.DebtRemediationFunction;
-import org.sonar.api.server.rule.Context;
-import org.sonar.api.server.rule.RuleDescriptionSection;
-import org.sonar.api.server.rule.RulesDefinition;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.testfixtures.log.LogTester;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.QProfileChangeDto;
-import org.sonar.db.qualityprofile.QProfileChangeQuery;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.DeprecatedRuleKeyDto;
-import org.sonar.db.rule.RuleDescriptionSectionContextDto;
-import org.sonar.db.rule.RuleDescriptionSectionDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleDto.Scope;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.db.rule.RuleRepositoryDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.es.metadata.MetadataIndex;
-import org.sonar.server.plugins.ServerPluginRepository;
-import org.sonar.server.qualityprofile.ActiveRuleChange;
-import org.sonar.server.qualityprofile.QProfileRules;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.rule.index.RuleIndex;
-import org.sonar.server.rule.index.RuleIndexDefinition;
-import org.sonar.server.rule.index.RuleIndexer;
-import org.sonar.server.rule.index.RuleQuery;
-
-import static com.google.common.collect.Sets.newHashSet;
-import static java.lang.String.format;
-import static java.lang.String.valueOf;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
-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;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.rule.RuleStatus.READY;
-import static org.sonar.api.rule.RuleStatus.REMOVED;
-import static org.sonar.api.rule.Severity.BLOCKER;
-import static org.sonar.api.rule.Severity.INFO;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
-import static org.sonar.api.server.rule.RulesDefinition.NewRepository;
-import static org.sonar.api.server.rule.RulesDefinition.NewRule;
-import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10;
-import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.builder;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
-
-@RunWith(DataProviderRunner.class)
-public class RegisterRulesTest {
-
- private static final String FAKE_PLUGIN_KEY = "unittest";
- private static final Date DATE1 = DateUtils.parseDateTime("2014-01-01T19:10:03+0100");
- private static final Date DATE2 = DateUtils.parseDateTime("2014-02-01T12:10:03+0100");
- private static final Date DATE3 = DateUtils.parseDateTime("2014-03-01T12:10:03+0100");
-
- private static final RuleKey EXTERNAL_RULE_KEY1 = RuleKey.of("external_eslint", "rule1");
- private static final RuleKey EXTERNAL_HOTSPOT_RULE_KEY = RuleKey.of("external_eslint", "hotspot");
-
- private static final RuleKey RULE_KEY1 = RuleKey.of("fake", "rule1");
- private static final RuleKey RULE_KEY2 = RuleKey.of("fake", "rule2");
- private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
- private static final RuleKey HOTSPOT_RULE_KEY = RuleKey.of("fake", "hotspot");
-
- private final TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
-
- @org.junit.Rule
- public DbTester db = DbTester.create(system);
- @org.junit.Rule
- public EsTester es = EsTester.create();
- @org.junit.Rule
- public LogTester logTester = new LogTester();
-
- private final QProfileRules qProfileRules = mock(QProfileRules.class);
- private final WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
- private final DbClient dbClient = db.getDbClient();
- private final MetadataIndex metadataIndex = mock(MetadataIndex.class);
- private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
-
- private RuleIndexer ruleIndexer;
- private ActiveRuleIndexer activeRuleIndexer;
- private RuleIndex ruleIndex;
- private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock(RuleDescriptionSectionsGenerator.class);
- private final RuleDescriptionSectionsGeneratorResolver resolver = mock(RuleDescriptionSectionsGeneratorResolver.class);
-
- @Before
- public void before() {
- ruleIndexer = new RuleIndexer(es.client(), dbClient);
- ruleIndex = new RuleIndex(es.client(), system);
- activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
- when(resolver.getRuleDescriptionSectionsGenerator(any())).thenReturn(ruleDescriptionSectionsGenerator);
- when(ruleDescriptionSectionsGenerator.generateSections(any())).thenAnswer(answer -> {
- RulesDefinition.Rule rule = answer.getArgument(0, RulesDefinition.Rule.class);
- String description = rule.htmlDescription() == null ? rule.markdownDescription() : rule.htmlDescription();
-
- Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = rule.ruleDescriptionSections().stream() //
- .map(s -> builder()
- .uuid(UuidFactoryFast.getInstance().create())
- .key(s.getKey())
- .content(s.getHtmlContent())
- .context(s.getContext().map(c -> RuleDescriptionSectionContextDto.of(c.getKey(), c.getDisplayName())).orElse(null))
- .build()
- )
- .collect(Collectors.toSet());
- return Sets.union(ruleDescriptionSectionDtos, Set.of(builder().uuid(UuidFactoryFast.getInstance().create()).key("default").content(description).build()));
- });
-
- when(ruleDescriptionSectionsGenerator.isGeneratorForRule(any())).thenReturn(true);
- }
-
- @Test
- public void insert_new_rules() {
- execute(new FakeRepositoryV1());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- verifyRule(rule1);
- assertThat(rule1.isExternal()).isFalse();
- assertThat(rule1.getDefRemediationFunction()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
- assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d");
- assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h");
-
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
- verifyHotspot(hotspotRule);
-
- List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
- assertThat(params).hasSize(2);
- RuleParamDto param = getParam(params, "param1");
- assertThat(param.getDescription()).isEqualTo("parameter one");
- assertThat(param.getDefaultValue()).isEqualTo("default1");
-
- // verify index
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid(), hotspotRule.getUuid());
- verifyIndicesMarkedAsInitialized();
-
- // verify repositories
- assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
- }
-
- private void verifyHotspot(RuleDto hotspotRule) {
- assertThat(hotspotRule.getName()).isEqualTo("Hotspot");
- assertThat(hotspotRule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Minimal hotspot");
- assertThat(hotspotRule.getCreatedAt()).isEqualTo(RegisterRulesTest.DATE1.getTime());
- assertThat(hotspotRule.getUpdatedAt()).isEqualTo(RegisterRulesTest.DATE1.getTime());
- assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
- assertThat(hotspotRule.getSecurityStandards()).containsExactly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
- }
-
- @Test
- public void insert_new_external_rule() {
- execute(new ExternalRuleRepository());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(2);
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1);
- verifyRule(rule1);
- assertThat(rule1.isExternal()).isTrue();
- assertThat(rule1.getDefRemediationFunction()).isNull();
- assertThat(rule1.getDefRemediationGapMultiplier()).isNull();
- assertThat(rule1.getDefRemediationBaseEffort()).isNull();
-
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_HOTSPOT_RULE_KEY);
- verifyHotspot(hotspotRule);
- }
-
- private void verifyRule(RuleDto rule) {
- assertThat(rule.getName()).isEqualTo("One");
- assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description of One");
- assertThat(rule.getSeverityString()).isEqualTo(BLOCKER);
- assertThat(rule.getTags()).isEmpty();
- assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
- assertThat(rule.getConfigKey()).isEqualTo("config1");
- assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
- assertThat(rule.getCreatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule.getScope()).isEqualTo(Scope.ALL);
- assertThat(rule.getUpdatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
- assertThat(rule.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
- assertThat(rule.isAdHoc()).isFalse();
- assertThat(rule.getEducationPrinciples()).containsOnly("concept1", "concept2", "concept3");
- }
-
- @Test
- public void insert_then_remove_rule() {
- String ruleKey = randomAlphanumeric(5);
-
- // register one rule
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule(ruleKey)
- .setName(randomAlphanumeric(5))
- .setHtmlDescription(randomAlphanumeric(20));
- repo.done();
- });
-
- // verify db
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- assertThat(rules)
- .extracting(RuleDto::getKey)
- .extracting(RuleKey::rule)
- .containsExactly(ruleKey);
- RuleDto rule = rules.iterator().next();
-
- // verify index
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .containsExactly(rule.getUuid());
- verifyIndicesMarkedAsInitialized();
-
- // register no rule
- execute(context -> context.createRepository("fake", "java").done());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .extracting(RuleDto::getKey)
- .extracting(RuleKey::rule)
- .containsExactly(ruleKey);
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .extracting(RuleDto::getStatus)
- .containsExactly(REMOVED);
-
- // verify index
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .isEmpty();
- verifyIndicesNotMarkedAsInitialized();
- }
-
- @Test
- public void mass_insert_then_remove_rule() {
- int numberOfRules = 5000;
-
- // register many rules
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- IntStream.range(0, numberOfRules)
- .mapToObj(i -> "rule-" + i)
- .forEach(ruleKey -> repo.createRule(ruleKey)
- .setName(randomAlphanumeric(20))
- .setHtmlDescription(randomAlphanumeric(20)));
- repo.done();
- });
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .hasSize(numberOfRules)
- .extracting(RuleDto::getStatus)
- .containsOnly(READY);
-
- // verify index
- assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .isNotEmpty();
-
- // register no rule
- execute(context -> context.createRepository("fake", "java").done());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .hasSize(numberOfRules)
- .extracting(RuleDto::getStatus)
- .containsOnly(REMOVED);
-
- // verify index (documents are still in the index, but all are removed)
- assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .isEmpty();
- }
-
- @Test
- public void delete_repositories_that_have_been_uninstalled() {
- RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs");
- DbSession dbSession = db.getSession();
- db.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository));
- dbSession.commit();
-
- execute(new FakeRepositoryV1());
-
- assertThat(db.getDbClient().ruleRepositoryDao().selectAll(dbSession)).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
- }
-
- @Test
- public void update_and_remove_rules_on_changes() {
- execute(new FakeRepositoryV1());
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
- assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
- verifyIndicesMarkedAsInitialized();
-
- // user adds tags and sets markdown note
- rule1.setTags(newHashSet("usertag1", "usertag2"));
- rule1.setNoteData("user *note*");
- rule1.setNoteUserUuid("marius");
- dbClient.ruleDao().update(db.getSession(), rule1);
- db.getSession().commit();
-
- system.setNow(DATE2.getTime());
- execute(new FakeRepositoryV2());
-
- verifyIndicesNotMarkedAsInitialized();
- // rule1 has been updated
- rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThatRule1IsV2(rule1);
-
- List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
- assertThat(params).hasSize(2);
- RuleParamDto param = getParam(params, "param1");
- assertThat(param.getDescription()).isEqualTo("parameter one v2");
- assertThat(param.getDefaultValue()).isEqualTo("default1 v2");
-
- // rule2 has been removed -> status set to REMOVED but db row is not deleted
- rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- assertThat(rule2.getStatus()).isEqualTo(REMOVED);
- assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
-
- // rule3 has been created
- RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
- assertThat(rule3).isNotNull();
- assertThat(rule3.getStatus()).isEqualTo(READY);
-
- // verify index
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
-
- // verify repositories
- assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
-
- system.setNow(DATE3.getTime());
- execute(new FakeRepositoryV3());
- rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
- assertThat(rule3.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Rule Three V2");
- assertThat(rule3.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
- }
-
- private void assertThatRule1IsV2(RuleDto rule1) {
- assertThat(rule1.getName()).isEqualTo("One v2");
- RuleDescriptionSectionDto defaultRuleDescriptionSection = rule1.getDefaultRuleDescriptionSection();
- assertThat(defaultRuleDescriptionSection.getContent()).isEqualTo("Description of One v2");
- assertThat(defaultRuleDescriptionSection.getKey()).isEqualTo(DEFAULT_KEY);
- assertThat(rule1.getDescriptionFormat()).isEqualTo(RuleDto.Format.HTML);
- assertThat(rule1.getSeverityString()).isEqualTo(INFO);
- assertThat(rule1.getTags()).containsOnly("usertag1", "usertag2");
- assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag4");
- assertThat(rule1.getConfigKey()).isEqualTo("config1 v2");
- assertThat(rule1.getNoteData()).isEqualTo("user *note*");
- assertThat(rule1.getNoteUserUuid()).isEqualTo("marius");
- assertThat(rule1.getStatus()).isEqualTo(READY);
- assertThat(rule1.getType()).isEqualTo(RuleType.BUG.getDbConstant());
- assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule1.getUpdatedAt()).isEqualTo(DATE2.getTime());
- assertThat(rule1.getEducationPrinciples()).containsOnly("concept1","concept4");
- }
-
- @Test
- public void add_new_tag() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .setTags("tag1");
- repo.done();
- });
-
- RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSystemTags()).containsOnly("tag1");
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .setTags("tag1", "tag2");
- repo.done();
- });
-
- rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2");
- }
-
- @Test
- public void add_new_security_standards() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .addOwaspTop10(Y2021, OwaspTop10.A1)
- .addCwe(123);
- repo.done();
- });
-
- RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSecurityStandards()).containsOnly("cwe:123", "owaspTop10-2021:a1");
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
- .addCwe(1, 123, 863);
- repo.done();
- });
-
- rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSecurityStandards()).containsOnly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
- }
-
- @Test
- public void update_only_rule_name() {
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name2")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- // rule1 has been updated
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
- assertThat(rule1.getName()).isEqualTo("Name2");
- assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description");
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- public void update_template_rule_key_should_also_update_custom_rules() {
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("squid", "java");
- repo.createRule("rule")
- .setName("Name1")
- .setHtmlDescription("Description")
- .setTemplate(true);
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("squid", "rule"));
-
- // insert custom rule
- db.rules().insert(new RuleDto()
- .setRuleKey(RuleKey.of("squid", "custom"))
- .setLanguage("java")
- .setScope(Scope.ALL)
- .setTemplateUuid(rule1.getUuid())
- .setName("custom1"));
- db.commit();
-
- // re-key rule
- execute(context -> {
- NewRepository repo = context.createRepository("java", "java");
- repo.createRule("rule")
- .setName("Name1")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey("squid", "rule")
- .setTemplate(true);
- repo.done();
- });
-
- // template rule and custom rule have been updated
- rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "rule"));
- RuleDto custom = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "custom"));
- }
-
- @Test
- public void update_if_rule_key_renamed_and_deprecated_key_declared() {
- String ruleKey1 = "rule1";
- String ruleKey2 = "rule2";
- String repository = "fake";
-
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository, "java");
- repo.createRule(ruleKey1)
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey1));
- SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
- assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
- assertThat(searchRule1.getTotal()).isOne();
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository, "java");
- repo.createRule(ruleKey2)
- .setName("Name2")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey(repository, ruleKey1);
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey2));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
- assertThat(rule2.getName()).isEqualTo("Name2");
- assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
-
- SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
- assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
- assertThat(searchRule2.getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- public void update_if_repository_changed_and_deprecated_key_declared() {
- String ruleKey = "rule";
- String repository1 = "fake1";
- String repository2 = "fake2";
-
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository1, "java");
- repo.createRule(ruleKey)
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey));
- SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
- assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
- assertThat(searchRule1.getTotal()).isOne();
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository2, "java");
- repo.createRule(ruleKey)
- .setName("Name2")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey(repository1, ruleKey);
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
- assertThat(rule2.getName()).isEqualTo("Name2");
- assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
-
- SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
- assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
- assertThat(searchRule2.getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- @UseDataProvider("allRenamingCases")
- public void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
- String name = "Name1";
- String description = "Description";
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repo1, "java");
- repo.createRule(ruleKey1)
- .setName(name)
- .setHtmlDescription(description);
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo1, ruleKey1));
- assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
- .containsOnly(rule1.getUuid());
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repo2, "java");
- repo.createRule(ruleKey2)
- .setName(name)
- .setHtmlDescription(description)
- .addDeprecatedRuleKey(repo1, ruleKey1);
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo2, ruleKey2));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
- assertThat(rule2.getName()).isEqualTo(rule1.getName());
- assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
- .containsOnly(rule2.getUuid());
- }
-
- @DataProvider
- public static Object[][] allRenamingCases() {
- return new Object[][]{
- {"repo1", "rule1", "repo1", "rule2"},
- {"repo1", "rule1", "repo2", "rule1"},
- {"repo1", "rule1", "repo2", "rule2"},
- };
- }
-
- @Test
- public void update_if_repository_and_key_changed_and_deprecated_key_declared_among_others() {
- String ruleKey1 = "rule1";
- String ruleKey2 = "rule2";
- String repository1 = "fake1";
- String repository2 = "fake2";
-
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository1, "java");
- repo.createRule(ruleKey1)
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey1));
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getUuids())
- .containsOnly(rule1.getUuid());
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository2, "java");
- repo.createRule(ruleKey2)
- .setName("Name2")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey("foo", "bar")
- .addDeprecatedRuleKey(repository1, ruleKey1)
- .addDeprecatedRuleKey("some", "noise");
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey2));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getUuids())
- .containsOnly(rule1.getUuid());
- }
-
- @Test
- public void update_only_rule_description() {
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .setHtmlDescription("Desc1");
- repo.done();
- });
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .setHtmlDescription("Desc2");
- repo.done();
- });
-
- // rule1 has been updated
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
- assertThat(rule1.getName()).isEqualTo("Name");
- assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc2"), new SearchOptions()).getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- public void update_several_rule_descriptions() {
- system.setNow(DATE1.getTime());
-
- RuleDescriptionSection section1context1 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx1 content", "ctx_1");
- RuleDescriptionSection section1context2 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 content", "ctx_2");
- RuleDescriptionSection section2context1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "section2 content", "ctx_1");
- RuleDescriptionSection section2context2 = createRuleDescriptionSection(RESOURCES_SECTION_KEY,"section2 ctx2 content", "ctx_2");
- RuleDescriptionSection section3noContext = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY, "section3 content", null);
- RuleDescriptionSection section4noContext = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "section4 content", null);
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .addDescriptionSection(section1context1)
- .addDescriptionSection(section1context2)
- .addDescriptionSection(section2context1)
- .addDescriptionSection(section2context2)
- .addDescriptionSection(section3noContext)
- .addDescriptionSection(section4noContext)
- .setHtmlDescription("Desc1");
- repo.done();
- });
-
- RuleDescriptionSection section1context2updated = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 updated content", "ctx_2");
- RuleDescriptionSection section2updatedWithoutContext = createRuleDescriptionSection(RESOURCES_SECTION_KEY, section2context1.getHtmlContent(), null);
- RuleDescriptionSection section4updatedWithContext1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_1");
- RuleDescriptionSection section4updatedWithContext2 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_2");
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .addDescriptionSection(section1context1)
- .addDescriptionSection(section1context2updated)
- .addDescriptionSection(section2updatedWithoutContext)
- .addDescriptionSection(section3noContext)
- .addDescriptionSection(section4updatedWithContext1)
- .addDescriptionSection(section4updatedWithContext2)
- .setHtmlDescription("Desc2");
- repo.done();
-
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
- assertThat(rule1.getName()).isEqualTo("Name");
- assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
-
- Set<RuleDescriptionSection> expectedSections = Set.of(section1context1, section1context2updated,
- section2updatedWithoutContext, section3noContext, section4updatedWithContext1, section4updatedWithContext2);
- assertThat(rule1.getRuleDescriptionSectionDtos()).hasSize(expectedSections.size() + 1);
- expectedSections.forEach(apiSection -> assertSectionExists(apiSection, rule1.getRuleDescriptionSectionDtos()));
- }
-
- private static RuleDescriptionSection createRuleDescriptionSection(String sectionKey, String description, @Nullable String contextKey) {
- Context context = Optional.ofNullable(contextKey).map(key -> new Context(contextKey, contextKey + randomAlphanumeric(10))).orElse(null);
- return RuleDescriptionSection.builder().sectionKey(sectionKey)
- .htmlContent(description)
- .context(context)
- .build();
- }
-
- private static void assertSectionExists(RuleDescriptionSection apiSection, Set<RuleDescriptionSectionDto> sectionDtos) {
- sectionDtos.stream()
- .filter(sectionDto -> sectionDto.getKey().equals(apiSection.getKey()) && sectionDto.getContent().equals(apiSection.getHtmlContent()))
- .filter(sectionDto -> isSameContext(apiSection.getContext(), sectionDto.getContext()))
- .findAny()
- .orElseThrow(() -> new AssertionError(format("Impossible to find a section dto matching the API section %s", apiSection.getKey())));
- }
-
- private static boolean isSameContext(Optional<Context> apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
- if (apiContext.isEmpty() && contextDto == null) {
- return true;
- }
- return apiContext.filter(context -> isSameContext(context, contextDto)).isPresent();
- }
-
- private static boolean isSameContext(Context apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
- if (contextDto == null) {
- return false;
- }
- return Objects.equals(apiContext.getKey(), contextDto.getKey()) && Objects.equals(apiContext.getDisplayName(), contextDto.getDisplayName());
- }
-
- @Test
- public void rule_previously_created_as_adhoc_becomes_none_adhoc() {
- RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true));
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createExternalRepository("fake", rule.getLanguage());
- repo.createRule(rule.getRuleKey())
- .setName(rule.getName())
- .setHtmlDescription(rule.getDefaultRuleDescriptionSection().getContent());
- repo.done();
- });
-
- RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
- assertThat(reloaded.isAdHoc()).isFalse();
- }
-
- @Test
- public void remove_no_more_defined_external_rule() {
- RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
- .setStatus(READY)
- .setIsExternal(true)
- .setIsAdHoc(false));
-
- execute();
-
- RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
- assertThat(reloaded.getStatus()).isEqualTo(REMOVED);
- }
-
- @Test
- public void do_not_remove_no_more_defined_ad_hoc_rule() {
- RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
- .setStatus(READY)
- .setIsExternal(true)
- .setIsAdHoc(true));
-
- execute();
-
- RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
- assertThat(reloaded.getStatus()).isEqualTo(READY);
- }
-
- @Test
- public void disable_then_enable_rule() {
- // Install rule
- system.setNow(DATE1.getTime());
- execute(new FakeRepositoryV1());
-
- // Uninstall rule
- system.setNow(DATE2.getTime());
- execute();
-
- RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getStatus()).isEqualTo(REMOVED);
- assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isZero();
-
- // Re-install rule
- system.setNow(DATE3.getTime());
- execute(new FakeRepositoryV1());
-
- rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
- assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isOne();
- }
-
- @Test
- public void do_not_update_rules_when_no_changes() {
- execute(new FakeRepositoryV1());
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
-
- system.setNow(DATE2.getTime());
- execute(new FakeRepositoryV1());
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule1.getUpdatedAt()).isEqualTo(DATE1.getTime());
- }
-
- @Test
- public void do_not_update_already_removed_rules() {
- execute(new FakeRepositoryV1());
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
- assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
-
- assertThat(rule2.getStatus()).isEqualTo(READY);
-
- system.setNow(DATE2.getTime());
- execute(new FakeRepositoryV2());
-
- // On MySQL, need to update a rule otherwise rule2 will be seen as READY, but why ???
- dbClient.ruleDao().update(db.getSession(), rule1);
- db.getSession().commit();
-
- // rule2 is removed
- rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
- assertThat(rule2.getStatus()).isEqualTo(REMOVED);
-
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
-
- system.setNow(DATE3.getTime());
- execute(new FakeRepositoryV2());
- db.getSession().commit();
-
- // -> rule2 is still removed, but not update at DATE3
- rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- assertThat(rule2.getStatus()).isEqualTo(REMOVED);
- assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
-
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
- }
-
- @Test
- public void mass_insert() {
- execute(new BigRepository());
- assertThat(db.countRowsOfTable("rules")).isEqualTo(BigRepository.SIZE);
- assertThat(db.countRowsOfTable("rules_parameters")).isEqualTo(BigRepository.SIZE * 20);
- assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).hasSize(BigRepository.SIZE);
- }
-
- @Test
- public void manage_repository_extensions() {
- execute(new FindbugsRepository(), new FbContribRepository());
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- assertThat(rules).hasSize(2);
- for (RuleDto rule : rules) {
- assertThat(rule.getRepositoryKey()).isEqualTo("findbugs");
- }
- }
-
- @Test
- public void remove_system_tags_when_plugin_does_not_provide_any() {
- // Rule already exists in DB, with some system tags
- db.rules().insert(new RuleDto()
- .setRuleKey("rule1")
- .setRepositoryKey("findbugs")
- .setName("Rule One")
- .setScope(Scope.ALL)
- .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule one description"))
- .setDescriptionFormat(RuleDto.Format.HTML)
- .setSystemTags(newHashSet("tag1", "tag2")));
- db.getSession().commit();
-
- // Synchronize rule without tag
- execute(new FindbugsRepository());
-
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- assertThat(rules).hasSize(1).extracting(RuleDto::getKey, RuleDto::getSystemTags)
- .containsOnly(tuple(RuleKey.of("findbugs", "rule1"), emptySet()));
- }
-
- @Test
- public void rules_that_deprecate_previous_rule_must_be_recorded() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "rule1");
- repo.done();
- });
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey")
- .addDeprecatedRuleKey("fake", "rule1")
- .addDeprecatedRuleKey("fake", "rule2");
- repo.done();
- });
-
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
- assertThat(rules).hasSize(1);
- assertThat(deprecatedRuleKeys).hasSize(2);
- }
-
- @Test
- public void rules_that_remove_deprecated_key_must_remove_records() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "rule1");
- repo.done();
- });
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey")
- .addDeprecatedRuleKey("fake", "rule1")
- .addDeprecatedRuleKey("fake", "rule2");
- repo.done();
- });
-
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
- Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
- assertThat(deprecatedRuleKeys).hasSize(2);
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey");
- repo.done();
- });
-
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
- deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
- assertThat(deprecatedRuleKeys).isEmpty();
- }
-
- @Test
- public void declaring_two_rules_with_same_deprecated_RuleKey_should_throw_ISE() {
- assertThatThrownBy(() -> {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey1")
- .addDeprecatedRuleKey("fake", "old");
- createRule(repo, "newKey2")
- .addDeprecatedRuleKey("fake", "old");
- repo.done();
- });
- })
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("The following deprecated rule keys are declared at least twice [fake:old]");
- }
-
- @Test
- public void declaring_a_rule_with_a_deprecated_RuleKey_still_used_should_throw_ISE() {
- assertThatThrownBy(() -> {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey1");
- createRule(repo, "newKey2")
- .addDeprecatedRuleKey("fake", "newKey1");
- repo.done();
- });
- })
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("The following rule keys are declared both as deprecated and used key [fake:newKey1]");
- }
-
- @Test
- public void updating_the_deprecated_to_a_new_ruleKey_should_throw_an_ISE() {
- // On this new rule add a deprecated key
- execute(context -> createRule(context, "javascript", "javascript", "s103",
- r -> r.addDeprecatedRuleKey("javascript", "linelength")));
-
- assertThatThrownBy(() -> {
- // This rule should have been moved to another repository
- execute(context -> createRule(context, "javascript", "sonarjs", "s103",
- r -> r.addDeprecatedRuleKey("javascript", "linelength")));
- })
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("An incorrect state of deprecated rule keys has been detected.\n " +
- "The deprecated rule key [javascript:linelength] was previously deprecated by [javascript:s103]. [javascript:s103] should be a deprecated key of [sonarjs:s103],");
- }
-
- @Test
- public void deprecate_rule_that_deprecated_another_rule() {
- execute(context -> createRule(context, "javascript", "javascript", "s103"));
- execute(context -> createRule(context, "javascript", "javascript", "s104",
- r -> r.addDeprecatedRuleKey("javascript", "s103")));
-
- // This rule should have been moved to another repository
- execute(context -> createRule(context, "javascript", "sonarjs", "s105",
- r -> r.addDeprecatedRuleKey("javascript", "s103")
- .addDeprecatedRuleKey("javascript", "s104")));
- }
-
- @Test
- public void declaring_a_rule_with_an_existing_RuleKey_still_used_should_throw_IAE() {
- assertThatThrownBy(() -> {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey1");
- createRule(repo, "newKey1");
- repo.done();
- });
- })
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("The rule 'newKey1' of repository 'fake' is declared several times");
- }
-
- @Test
- public void removed_rule_should_appear_in_changelog() {
- //GIVEN
- QProfileDto qProfileDto = db.qualityProfiles().insert();
- RuleDto ruleDto = db.rules().insert(RULE_KEY1);
- db.qualityProfiles().activateRule(qProfileDto, ruleDto);
- ActiveRuleChange arChange = new ActiveRuleChange(DEACTIVATED, ActiveRuleDto.createFor(qProfileDto, ruleDto), ruleDto);
- when(qProfileRules.deleteRule(any(DbSession.class), eq(ruleDto))).thenReturn(List.of(arChange));
- //WHEN
- execute(context -> context.createRepository("fake", "java").done());
- //THEN
- List<QProfileChangeDto> qProfileChangeDtos = dbClient.qProfileChangeDao().selectByQuery(db.getSession(), new QProfileChangeQuery(qProfileDto.getKee()));
- assertThat(qProfileChangeDtos).extracting(QProfileChangeDto::getRulesProfileUuid, QProfileChangeDto::getChangeType)
- .contains(tuple(qProfileDto.getRulesProfileUuid(), "DEACTIVATED"));
- }
-
- @Test
- public void removed_rule_should_be_deleted_when_renamed_repository() {
- //GIVEN
- RuleDto removedRuleDto = db.rules().insert(RuleKey.of("old_repo", "removed_rule"));
- RuleDto renamedRuleDto = db.rules().insert(RuleKey.of("old_repo", "renamed_rule"));
- //WHEN
- execute(context -> createRule(context, "java", "new_repo", renamedRuleDto.getRuleKey(),
- rule -> rule.addDeprecatedRuleKey(renamedRuleDto.getRepositoryKey(), renamedRuleDto.getRuleKey())));
- //THEN
- verify(qProfileRules).deleteRule(any(DbSession.class), eq(removedRuleDto));
- }
-
- private void execute(RulesDefinition... defs) {
- ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
- when(pluginRepository.getPluginKey(any(RulesDefinition.class))).thenReturn(FAKE_PLUGIN_KEY);
- RuleDefinitionsLoader loader = new RuleDefinitionsLoader(pluginRepository, defs);
- Languages languages = mock(Languages.class);
- when(languages.get(any())).thenReturn(mock(Language.class));
- reset(webServerRuleFinder);
-
- RegisterRules task = new RegisterRules(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, uuidFactory, metadataIndex,
- resolver);
- task.start();
- // Execute a commit to refresh session state as the task is using its own session
- db.getSession().commit();
-
- verify(webServerRuleFinder).startCaching();
- }
-
- private NewRule createRule(NewRepository repo, String key) {
- return repo.createRule(key)
- .setName(key + " name")
- .setHtmlDescription("Description of " + key)
- .setSeverity(BLOCKER)
- .setInternalKey("config1")
- .setTags("tag1", "tag2", "tag3")
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA);
- }
-
- @SafeVarargs
- private void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) {
- NewRepository repo = context.createRepository(repositoryKey, language);
- NewRule newRule = repo.createRule(ruleKey)
- .setName(ruleKey)
- .setHtmlDescription("Description of One")
- .setSeverity(BLOCKER)
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA);
-
- Arrays.stream(consumers).forEach(c -> c.accept(newRule));
- repo.done();
- }
-
- private void verifyIndicesMarkedAsInitialized() {
- verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_RULE, true);
- verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_ACTIVE_RULE, true);
- reset(metadataIndex);
- }
-
- private void verifyIndicesNotMarkedAsInitialized() {
- verifyNoInteractions(metadataIndex);
- }
-
- private RuleParamDto getParam(List<RuleParamDto> params, String key) {
- for (RuleParamDto param : params) {
- if (param.getName().equals(key)) {
- return param;
- }
- }
- return null;
- }
-
- static class FakeRepositoryV1 implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("fake", "java");
- NewRule rule1 = repo.createRule(RULE_KEY1.rule())
- .setName("One")
- .setHtmlDescription("Description of One")
- .setSeverity(BLOCKER)
- .setInternalKey("config1")
- .setTags("tag1", "tag2", "tag3")
- .setScope(RuleScope.ALL)
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA)
- .setGapDescription("java.S115.effortToFix")
- .addEducationPrincipleKeys("concept1", "concept2", "concept3");
- rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h"));
-
- rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default1");
- rule1.createParam("param2").setDescription("parameter two").setDefaultValue("default2");
-
- repo.createRule(HOTSPOT_RULE_KEY.rule())
- .setName("Hotspot")
- .setHtmlDescription("Minimal hotspot")
- .setType(RuleType.SECURITY_HOTSPOT)
- .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
- .addCwe(1, 123, 863);
-
- repo.createRule(RULE_KEY2.rule())
- .setName("Two")
- .setHtmlDescription("Minimal rule");
- repo.done();
- }
- }
-
- /**
- * FakeRepositoryV1 with some changes
- */
- static class FakeRepositoryV2 implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("fake", "java");
-
- // almost all the attributes of rule1 are changed
- NewRule rule1 = repo.createRule(RULE_KEY1.rule())
- .setName("One v2")
- .setHtmlDescription("Description of One v2")
- .setSeverity(INFO)
- .setInternalKey("config1 v2")
- // tag2 and tag3 removed, tag4 added
- .setTags("tag1", "tag4")
- .setType(RuleType.BUG)
- .setStatus(READY)
- .setGapDescription("java.S115.effortToFix.v2")
- .addEducationPrincipleKeys("concept1", "concept4");
- rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("6d", "2h"));
- rule1.createParam("param1").setDescription("parameter one v2").setDefaultValue("default1 v2");
- rule1.createParam("param2").setDescription("parameter two v2").setDefaultValue("default2 v2");
-
- // rule2 is dropped, rule3 is new
- repo.createRule(RULE_KEY3.rule())
- .setName("Three")
- .setHtmlDescription("Rule Three");
-
- repo.done();
- }
- }
-
- static class FakeRepositoryV3 implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("fake", "java");
- // rule 3 is dropped
- repo.createRule(RULE_KEY3.rule())
- .setName("Three")
- .setMarkdownDescription("Rule Three V2");
-
- repo.done();
- }
- }
-
- static class ExternalRuleRepository implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createExternalRepository("eslint", "js");
- repo.createRule(RULE_KEY1.rule())
- .setName("One")
- .setHtmlDescription("Description of One")
- .setSeverity(BLOCKER)
- .setInternalKey("config1")
- .setTags("tag1", "tag2", "tag3")
- .setScope(RuleScope.ALL)
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA)
- .addEducationPrincipleKeys("concept1", "concept2", "concept3");
-
- repo.createRule(EXTERNAL_HOTSPOT_RULE_KEY.rule())
- .setName("Hotspot")
- .setHtmlDescription("Minimal hotspot")
- .setType(RuleType.SECURITY_HOTSPOT)
- .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
- .addCwe(1, 123, 863);
-
- repo.done();
- }
- }
-
- static class BigRepository implements RulesDefinition {
- static final int SIZE = 500;
-
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("big", "java");
- for (int i = 0; i < SIZE; i++) {
- NewRule rule = repo.createRule("rule" + i)
- .setName("name of " + i)
- .setHtmlDescription("description of " + i);
- for (int j = 0; j < 20; j++) {
- rule.createParam("param" + j);
- }
-
- }
- repo.done();
- }
- }
-
- static class FindbugsRepository implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("findbugs", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One");
- repo.done();
- }
- }
-
- static class FbContribRepository implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewExtendedRepository repo = context.createRepository("findbugs", "java");
- repo.createRule("rule2")
- .setName("Rule Two")
- .setHtmlDescription("Description of Rule Two");
- repo.done();
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.rule;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.Set;
-import org.assertj.core.groups.Tuple;
-import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.rule.RulesDefinition;
-import org.sonar.db.rule.DeprecatedRuleKeyDto;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class SingleDeprecatedRuleKeyTest {
-
- @Test
- public void test_creation_from_DeprecatedRuleKeyDto() {
- // Creation from DeprecatedRuleKeyDto
- DeprecatedRuleKeyDto deprecatedRuleKeyDto = new DeprecatedRuleKeyDto()
- .setOldRuleKey(randomAlphanumeric(50))
- .setOldRepositoryKey(randomAlphanumeric(50))
- .setRuleUuid(randomAlphanumeric(50))
- .setUuid(randomAlphanumeric(40));
-
- SingleDeprecatedRuleKey singleDeprecatedRuleKey = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto);
-
- assertThat(singleDeprecatedRuleKey.getOldRepositoryKey()).isEqualTo(deprecatedRuleKeyDto.getOldRepositoryKey());
- assertThat(singleDeprecatedRuleKey.getOldRuleKey()).isEqualTo(deprecatedRuleKeyDto.getOldRuleKey());
- assertThat(singleDeprecatedRuleKey.getNewRepositoryKey()).isEqualTo(deprecatedRuleKeyDto.getNewRepositoryKey());
- assertThat(singleDeprecatedRuleKey.getNewRuleKey()).isEqualTo(deprecatedRuleKeyDto.getNewRuleKey());
- assertThat(singleDeprecatedRuleKey.getUuid()).isEqualTo(deprecatedRuleKeyDto.getUuid());
- assertThat(singleDeprecatedRuleKey.getRuleUuid()).isEqualTo(deprecatedRuleKeyDto.getRuleUuid());
- assertThat(singleDeprecatedRuleKey.getOldRuleKeyAsRuleKey())
- .isEqualTo(RuleKey.of(deprecatedRuleKeyDto.getOldRepositoryKey(), deprecatedRuleKeyDto.getOldRuleKey()));
- }
-
- @Test
- public void test_creation_from_RulesDefinitionRule() {
- // Creation from RulesDefinition.Rule
- ImmutableSet<RuleKey> deprecatedRuleKeys = ImmutableSet.of(
- RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)),
- RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)),
- RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)));
-
- RulesDefinition.Repository repository = mock(RulesDefinition.Repository.class);
- when(repository.key()).thenReturn(randomAlphanumeric(50));
-
- RulesDefinition.Rule rule = mock(RulesDefinition.Rule.class);
- when(rule.key()).thenReturn(randomAlphanumeric(50));
- when(rule.deprecatedRuleKeys()).thenReturn(deprecatedRuleKeys);
- when(rule.repository()).thenReturn(repository);
-
- Set<SingleDeprecatedRuleKey> singleDeprecatedRuleKeys = SingleDeprecatedRuleKey.from(rule);
- assertThat(singleDeprecatedRuleKeys).hasSize(deprecatedRuleKeys.size());
- assertThat(singleDeprecatedRuleKeys)
- .extracting(SingleDeprecatedRuleKey::getUuid, SingleDeprecatedRuleKey::getOldRepositoryKey, SingleDeprecatedRuleKey::getOldRuleKey,
- SingleDeprecatedRuleKey::getNewRepositoryKey, SingleDeprecatedRuleKey::getNewRuleKey, SingleDeprecatedRuleKey::getOldRuleKeyAsRuleKey)
- .containsExactlyInAnyOrder(
- deprecatedRuleKeys.stream().map(
- r -> tuple(null, r.repository(), r.rule(), rule.repository().key(), rule.key(), RuleKey.of(r.repository(), r.rule())))
- .toList().toArray(new Tuple[deprecatedRuleKeys.size()]));
- }
-
- @Test
- public void test_equality() {
- DeprecatedRuleKeyDto deprecatedRuleKeyDto1 = new DeprecatedRuleKeyDto()
- .setOldRuleKey(randomAlphanumeric(50))
- .setOldRepositoryKey(randomAlphanumeric(50))
- .setUuid(randomAlphanumeric(40))
- .setRuleUuid("some-uuid");
-
- DeprecatedRuleKeyDto deprecatedRuleKeyDto1WithoutUuid = new DeprecatedRuleKeyDto()
- .setOldRuleKey(deprecatedRuleKeyDto1.getOldRuleKey())
- .setOldRepositoryKey(deprecatedRuleKeyDto1.getOldRepositoryKey());
-
- DeprecatedRuleKeyDto deprecatedRuleKeyDto2 = new DeprecatedRuleKeyDto()
- .setOldRuleKey(randomAlphanumeric(50))
- .setOldRepositoryKey(randomAlphanumeric(50))
- .setUuid(randomAlphanumeric(40));
-
- SingleDeprecatedRuleKey singleDeprecatedRuleKey1 = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1);
- SingleDeprecatedRuleKey singleDeprecatedRuleKey2 = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2);
-
- assertThat(singleDeprecatedRuleKey1)
- .isEqualTo(singleDeprecatedRuleKey1)
- .isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1))
- .isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1WithoutUuid));
- assertThat(singleDeprecatedRuleKey2).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2));
-
- assertThat(singleDeprecatedRuleKey1)
- .hasSameHashCodeAs(singleDeprecatedRuleKey1)
- .hasSameHashCodeAs(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1))
- .hasSameHashCodeAs(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1WithoutUuid));
- assertThat(singleDeprecatedRuleKey2).hasSameHashCodeAs(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2));
-
- assertThat(singleDeprecatedRuleKey1)
- .isNotNull()
- .isNotEqualTo("")
- .isNotEqualTo(null)
- .isNotEqualTo(singleDeprecatedRuleKey2);
- assertThat(singleDeprecatedRuleKey2).isNotEqualTo(singleDeprecatedRuleKey1);
-
- assertThat(singleDeprecatedRuleKey1.hashCode()).isNotEqualTo(singleDeprecatedRuleKey2.hashCode());
- assertThat(singleDeprecatedRuleKey2.hashCode()).isNotEqualTo(singleDeprecatedRuleKey1.hashCode());
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.registration;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.elasticsearch.common.util.set.Sets;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleScope;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.rule.Context;
+import org.sonar.api.server.rule.RuleDescriptionSection;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.testfixtures.log.LogTester;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.QProfileChangeDto;
+import org.sonar.db.qualityprofile.QProfileChangeQuery;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+import org.sonar.db.rule.RuleDescriptionSectionContextDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleDto.Scope;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.db.rule.RuleRepositoryDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.es.metadata.MetadataIndex;
+import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.QProfileRules;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.RuleDefinitionsLoader;
+import org.sonar.server.rule.RuleDescriptionSectionsGenerator;
+import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
+import org.sonar.server.rule.WebServerRuleFinder;
+import org.sonar.server.rule.index.RuleIndex;
+import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
+import org.sonar.server.rule.index.RuleQuery;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static java.lang.String.format;
+import static java.lang.String.valueOf;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+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;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.rule.RuleStatus.READY;
+import static org.sonar.api.rule.RuleStatus.REMOVED;
+import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.api.rule.Severity.INFO;
+import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
+import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
+import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY;
+import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
+import static org.sonar.api.server.rule.RulesDefinition.NewRepository;
+import static org.sonar.api.server.rule.RulesDefinition.NewRule;
+import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10;
+import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.builder;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
+
+@RunWith(DataProviderRunner.class)
+public class RulesRegistrantIT {
+
+ private static final String FAKE_PLUGIN_KEY = "unittest";
+ private static final Date DATE1 = DateUtils.parseDateTime("2014-01-01T19:10:03+0100");
+ private static final Date DATE2 = DateUtils.parseDateTime("2014-02-01T12:10:03+0100");
+ private static final Date DATE3 = DateUtils.parseDateTime("2014-03-01T12:10:03+0100");
+
+ private static final RuleKey EXTERNAL_RULE_KEY1 = RuleKey.of("external_eslint", "rule1");
+ private static final RuleKey EXTERNAL_HOTSPOT_RULE_KEY = RuleKey.of("external_eslint", "hotspot");
+
+ private static final RuleKey RULE_KEY1 = RuleKey.of("fake", "rule1");
+ private static final RuleKey RULE_KEY2 = RuleKey.of("fake", "rule2");
+ private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
+ private static final RuleKey HOTSPOT_RULE_KEY = RuleKey.of("fake", "hotspot");
+
+ private final TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
+
+ @org.junit.Rule
+ public DbTester db = DbTester.create(system);
+ @org.junit.Rule
+ public EsTester es = EsTester.create();
+ @org.junit.Rule
+ public LogTester logTester = new LogTester();
+
+ private final QProfileRules qProfileRules = mock(QProfileRules.class);
+ private final WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
+ private final DbClient dbClient = db.getDbClient();
+ private final MetadataIndex metadataIndex = mock(MetadataIndex.class);
+ private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+
+ private RuleIndexer ruleIndexer;
+ private ActiveRuleIndexer activeRuleIndexer;
+ private RuleIndex ruleIndex;
+ private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock(RuleDescriptionSectionsGenerator.class);
+ private final RuleDescriptionSectionsGeneratorResolver resolver = mock(RuleDescriptionSectionsGeneratorResolver.class);
+
+ private final RulesKeyVerifier rulesKeyVerifier = new RulesKeyVerifier();
+ private final StartupRuleUpdater startupRuleUpdater = new StartupRuleUpdater(dbClient, system, uuidFactory, resolver);
+
+ @Before
+ public void before() {
+ ruleIndexer = new RuleIndexer(es.client(), dbClient);
+ ruleIndex = new RuleIndex(es.client(), system);
+ activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
+ when(resolver.generateFor(any())).thenAnswer(answer -> {
+ RulesDefinition.Rule rule = answer.getArgument(0, RulesDefinition.Rule.class);
+ String description = rule.htmlDescription() == null ? rule.markdownDescription() : rule.htmlDescription();
+
+ Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = rule.ruleDescriptionSections().stream()
+ .map(s -> builder()
+ .uuid(UuidFactoryFast.getInstance().create())
+ .key(s.getKey())
+ .content(s.getHtmlContent())
+ .context(s.getContext().map(c -> RuleDescriptionSectionContextDto.of(c.getKey(), c.getDisplayName())).orElse(null))
+ .build()
+ )
+ .collect(Collectors.toSet());
+ return Sets.union(ruleDescriptionSectionDtos, Set.of(builder().uuid(UuidFactoryFast.getInstance().create()).key("default").content(description).build()));
+ });
+
+ when(ruleDescriptionSectionsGenerator.isGeneratorForRule(any())).thenReturn(true);
+ }
+
+ @Test
+ public void insert_new_rules() {
+ execute(new FakeRepositoryV1());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ verifyRule(rule1);
+ assertThat(rule1.isExternal()).isFalse();
+ assertThat(rule1.getDefRemediationFunction()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
+ assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d");
+ assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h");
+
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
+ verifyHotspot(hotspotRule);
+
+ List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
+ assertThat(params).hasSize(2);
+ RuleParamDto param = getParam(params, "param1");
+ assertThat(param.getDescription()).isEqualTo("parameter one");
+ assertThat(param.getDefaultValue()).isEqualTo("default1");
+
+ // verify index
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid(), hotspotRule.getUuid());
+ verifyIndicesMarkedAsInitialized();
+
+ // verify repositories
+ assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
+ }
+
+ private void verifyHotspot(RuleDto hotspotRule) {
+ assertThat(hotspotRule.getName()).isEqualTo("Hotspot");
+ assertThat(hotspotRule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Minimal hotspot");
+ assertThat(hotspotRule.getCreatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
+ assertThat(hotspotRule.getUpdatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
+ assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
+ assertThat(hotspotRule.getSecurityStandards()).containsExactly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
+ }
+
+ @Test
+ public void insert_new_external_rule() {
+ execute(new ExternalRuleRepository());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(2);
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1);
+ verifyRule(rule1);
+ assertThat(rule1.isExternal()).isTrue();
+ assertThat(rule1.getDefRemediationFunction()).isNull();
+ assertThat(rule1.getDefRemediationGapMultiplier()).isNull();
+ assertThat(rule1.getDefRemediationBaseEffort()).isNull();
+
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_HOTSPOT_RULE_KEY);
+ verifyHotspot(hotspotRule);
+ }
+
+ private void verifyRule(RuleDto rule) {
+ assertThat(rule.getName()).isEqualTo("One");
+ assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description of One");
+ assertThat(rule.getSeverityString()).isEqualTo(BLOCKER);
+ assertThat(rule.getTags()).isEmpty();
+ assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
+ assertThat(rule.getConfigKey()).isEqualTo("config1");
+ assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
+ assertThat(rule.getCreatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule.getScope()).isEqualTo(Scope.ALL);
+ assertThat(rule.getUpdatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
+ assertThat(rule.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
+ assertThat(rule.isAdHoc()).isFalse();
+ assertThat(rule.getEducationPrinciples()).containsOnly("concept1", "concept2", "concept3");
+ }
+
+ @Test
+ public void insert_then_remove_rule() {
+ String ruleKey = randomAlphanumeric(5);
+
+ // register one rule
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule(ruleKey)
+ .setName(randomAlphanumeric(5))
+ .setHtmlDescription(randomAlphanumeric(20));
+ repo.done();
+ });
+
+ // verify db
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ assertThat(rules)
+ .extracting(RuleDto::getKey)
+ .extracting(RuleKey::rule)
+ .containsExactly(ruleKey);
+ RuleDto rule = rules.iterator().next();
+
+ // verify index
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .containsExactly(rule.getUuid());
+ verifyIndicesMarkedAsInitialized();
+
+ // register no rule
+ execute(context -> context.createRepository("fake", "java").done());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .extracting(RuleDto::getKey)
+ .extracting(RuleKey::rule)
+ .containsExactly(ruleKey);
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .extracting(RuleDto::getStatus)
+ .containsExactly(REMOVED);
+
+ // verify index
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .isEmpty();
+ verifyIndicesNotMarkedAsInitialized();
+ }
+
+ @Test
+ public void mass_insert_then_remove_rule() {
+ int numberOfRules = 5000;
+
+ // register many rules
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ IntStream.range(0, numberOfRules)
+ .mapToObj(i -> "rule-" + i)
+ .forEach(ruleKey -> repo.createRule(ruleKey)
+ .setName(randomAlphanumeric(20))
+ .setHtmlDescription(randomAlphanumeric(20)));
+ repo.done();
+ });
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .hasSize(numberOfRules)
+ .extracting(RuleDto::getStatus)
+ .containsOnly(READY);
+
+ // verify index
+ assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .isNotEmpty();
+
+ // register no rule
+ execute(context -> context.createRepository("fake", "java").done());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .hasSize(numberOfRules)
+ .extracting(RuleDto::getStatus)
+ .containsOnly(REMOVED);
+
+ // verify index (documents are still in the index, but all are removed)
+ assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .isEmpty();
+ }
+
+ @Test
+ public void delete_repositories_that_have_been_uninstalled() {
+ RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs");
+ DbSession dbSession = db.getSession();
+ db.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository));
+ dbSession.commit();
+
+ execute(new FakeRepositoryV1());
+
+ assertThat(db.getDbClient().ruleRepositoryDao().selectAll(dbSession)).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
+ }
+
+ @Test
+ public void update_and_remove_rules_on_changes() {
+ execute(new FakeRepositoryV1());
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
+ assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
+ verifyIndicesMarkedAsInitialized();
+
+ // user adds tags and sets markdown note
+ rule1.setTags(newHashSet("usertag1", "usertag2"));
+ rule1.setNoteData("user *note*");
+ rule1.setNoteUserUuid("marius");
+ dbClient.ruleDao().update(db.getSession(), rule1);
+ db.getSession().commit();
+
+ system.setNow(DATE2.getTime());
+ execute(new FakeRepositoryV2());
+
+ verifyIndicesNotMarkedAsInitialized();
+ // rule1 has been updated
+ rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThatRule1IsV2(rule1);
+
+ List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
+ assertThat(params).hasSize(2);
+ RuleParamDto param = getParam(params, "param1");
+ assertThat(param.getDescription()).isEqualTo("parameter one v2");
+ assertThat(param.getDefaultValue()).isEqualTo("default1 v2");
+
+ // rule2 has been removed -> status set to REMOVED but db row is not deleted
+ rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ assertThat(rule2.getStatus()).isEqualTo(REMOVED);
+ assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
+
+ // rule3 has been created
+ RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
+ assertThat(rule3).isNotNull();
+ assertThat(rule3.getStatus()).isEqualTo(READY);
+
+ // verify index
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
+
+ // verify repositories
+ assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
+
+ system.setNow(DATE3.getTime());
+ execute(new FakeRepositoryV3());
+ rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
+ assertThat(rule3.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Rule Three V2");
+ assertThat(rule3.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
+ }
+
+ private void assertThatRule1IsV2(RuleDto rule1) {
+ assertThat(rule1.getName()).isEqualTo("One v2");
+ RuleDescriptionSectionDto defaultRuleDescriptionSection = rule1.getDefaultRuleDescriptionSection();
+ assertThat(defaultRuleDescriptionSection.getContent()).isEqualTo("Description of One v2");
+ assertThat(defaultRuleDescriptionSection.getKey()).isEqualTo(DEFAULT_KEY);
+ assertThat(rule1.getDescriptionFormat()).isEqualTo(RuleDto.Format.HTML);
+ assertThat(rule1.getSeverityString()).isEqualTo(INFO);
+ assertThat(rule1.getTags()).containsOnly("usertag1", "usertag2");
+ assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag4");
+ assertThat(rule1.getConfigKey()).isEqualTo("config1 v2");
+ assertThat(rule1.getNoteData()).isEqualTo("user *note*");
+ assertThat(rule1.getNoteUserUuid()).isEqualTo("marius");
+ assertThat(rule1.getStatus()).isEqualTo(READY);
+ assertThat(rule1.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+ assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule1.getUpdatedAt()).isEqualTo(DATE2.getTime());
+ assertThat(rule1.getEducationPrinciples()).containsOnly("concept1","concept4");
+ }
+
+ @Test
+ public void add_new_tag() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .setTags("tag1");
+ repo.done();
+ });
+
+ RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSystemTags()).containsOnly("tag1");
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .setTags("tag1", "tag2");
+ repo.done();
+ });
+
+ rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2");
+ }
+
+ @Test
+ public void add_new_security_standards() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .addOwaspTop10(Y2021, OwaspTop10.A1)
+ .addCwe(123);
+ repo.done();
+ });
+
+ RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSecurityStandards()).containsOnly("cwe:123", "owaspTop10-2021:a1");
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
+ .addCwe(1, 123, 863);
+ repo.done();
+ });
+
+ rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSecurityStandards()).containsOnly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
+ }
+
+ @Test
+ public void update_only_rule_name() {
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name2")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ // rule1 has been updated
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
+ assertThat(rule1.getName()).isEqualTo("Name2");
+ assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description");
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ public void update_template_rule_key_should_also_update_custom_rules() {
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("squid", "java");
+ repo.createRule("rule")
+ .setName("Name1")
+ .setHtmlDescription("Description")
+ .setTemplate(true);
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("squid", "rule"));
+
+ // insert custom rule
+ db.rules().insert(new RuleDto()
+ .setRuleKey(RuleKey.of("squid", "custom"))
+ .setLanguage("java")
+ .setScope(Scope.ALL)
+ .setTemplateUuid(rule1.getUuid())
+ .setName("custom1"));
+ db.commit();
+
+ // re-key rule
+ execute(context -> {
+ NewRepository repo = context.createRepository("java", "java");
+ repo.createRule("rule")
+ .setName("Name1")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey("squid", "rule")
+ .setTemplate(true);
+ repo.done();
+ });
+
+ // template rule and custom rule have been updated
+ rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "rule"));
+ RuleDto custom = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "custom"));
+ }
+
+ @Test
+ public void update_if_rule_key_renamed_and_deprecated_key_declared() {
+ String ruleKey1 = "rule1";
+ String ruleKey2 = "rule2";
+ String repository = "fake";
+
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository, "java");
+ repo.createRule(ruleKey1)
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey1));
+ SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
+ assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
+ assertThat(searchRule1.getTotal()).isOne();
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository, "java");
+ repo.createRule(ruleKey2)
+ .setName("Name2")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey(repository, ruleKey1);
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey2));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+ assertThat(rule2.getName()).isEqualTo("Name2");
+ assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
+
+ SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
+ assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
+ assertThat(searchRule2.getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ public void update_if_repository_changed_and_deprecated_key_declared() {
+ String ruleKey = "rule";
+ String repository1 = "fake1";
+ String repository2 = "fake2";
+
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository1, "java");
+ repo.createRule(ruleKey)
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey));
+ SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
+ assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
+ assertThat(searchRule1.getTotal()).isOne();
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository2, "java");
+ repo.createRule(ruleKey)
+ .setName("Name2")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey(repository1, ruleKey);
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+ assertThat(rule2.getName()).isEqualTo("Name2");
+ assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
+
+ SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
+ assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
+ assertThat(searchRule2.getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ @UseDataProvider("allRenamingCases")
+ public void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
+ String name = "Name1";
+ String description = "Description";
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repo1, "java");
+ repo.createRule(ruleKey1)
+ .setName(name)
+ .setHtmlDescription(description);
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo1, ruleKey1));
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
+ .containsOnly(rule1.getUuid());
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repo2, "java");
+ repo.createRule(ruleKey2)
+ .setName(name)
+ .setHtmlDescription(description)
+ .addDeprecatedRuleKey(repo1, ruleKey1);
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo2, ruleKey2));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+ assertThat(rule2.getName()).isEqualTo(rule1.getName());
+ assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
+ .containsOnly(rule2.getUuid());
+ }
+
+ @DataProvider
+ public static Object[][] allRenamingCases() {
+ return new Object[][]{
+ {"repo1", "rule1", "repo1", "rule2"},
+ {"repo1", "rule1", "repo2", "rule1"},
+ {"repo1", "rule1", "repo2", "rule2"},
+ };
+ }
+
+ @Test
+ public void update_if_repository_and_key_changed_and_deprecated_key_declared_among_others() {
+ String ruleKey1 = "rule1";
+ String ruleKey2 = "rule2";
+ String repository1 = "fake1";
+ String repository2 = "fake2";
+
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository1, "java");
+ repo.createRule(ruleKey1)
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey1));
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getUuids())
+ .containsOnly(rule1.getUuid());
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository2, "java");
+ repo.createRule(ruleKey2)
+ .setName("Name2")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey("foo", "bar")
+ .addDeprecatedRuleKey(repository1, ruleKey1)
+ .addDeprecatedRuleKey("some", "noise");
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey2));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getUuids())
+ .containsOnly(rule1.getUuid());
+ }
+
+ @Test
+ public void update_only_rule_description() {
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .setHtmlDescription("Desc1");
+ repo.done();
+ });
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .setHtmlDescription("Desc2");
+ repo.done();
+ });
+
+ // rule1 has been updated
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
+ assertThat(rule1.getName()).isEqualTo("Name");
+ assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc2"), new SearchOptions()).getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ public void update_several_rule_descriptions() {
+ system.setNow(DATE1.getTime());
+
+ RuleDescriptionSection section1context1 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx1 content", "ctx_1");
+ RuleDescriptionSection section1context2 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 content", "ctx_2");
+ RuleDescriptionSection section2context1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "section2 content", "ctx_1");
+ RuleDescriptionSection section2context2 = createRuleDescriptionSection(RESOURCES_SECTION_KEY,"section2 ctx2 content", "ctx_2");
+ RuleDescriptionSection section3noContext = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY, "section3 content", null);
+ RuleDescriptionSection section4noContext = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "section4 content", null);
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .addDescriptionSection(section1context1)
+ .addDescriptionSection(section1context2)
+ .addDescriptionSection(section2context1)
+ .addDescriptionSection(section2context2)
+ .addDescriptionSection(section3noContext)
+ .addDescriptionSection(section4noContext)
+ .setHtmlDescription("Desc1");
+ repo.done();
+ });
+
+ RuleDescriptionSection section1context2updated = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 updated content", "ctx_2");
+ RuleDescriptionSection section2updatedWithoutContext = createRuleDescriptionSection(RESOURCES_SECTION_KEY, section2context1.getHtmlContent(), null);
+ RuleDescriptionSection section4updatedWithContext1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_1");
+ RuleDescriptionSection section4updatedWithContext2 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_2");
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .addDescriptionSection(section1context1)
+ .addDescriptionSection(section1context2updated)
+ .addDescriptionSection(section2updatedWithoutContext)
+ .addDescriptionSection(section3noContext)
+ .addDescriptionSection(section4updatedWithContext1)
+ .addDescriptionSection(section4updatedWithContext2)
+ .setHtmlDescription("Desc2");
+ repo.done();
+
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
+ assertThat(rule1.getName()).isEqualTo("Name");
+ assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
+
+ Set<RuleDescriptionSection> expectedSections = Set.of(section1context1, section1context2updated,
+ section2updatedWithoutContext, section3noContext, section4updatedWithContext1, section4updatedWithContext2);
+ assertThat(rule1.getRuleDescriptionSectionDtos()).hasSize(expectedSections.size() + 1);
+ expectedSections.forEach(apiSection -> assertSectionExists(apiSection, rule1.getRuleDescriptionSectionDtos()));
+ }
+
+ private static RuleDescriptionSection createRuleDescriptionSection(String sectionKey, String description, @Nullable String contextKey) {
+ Context context = Optional.ofNullable(contextKey).map(key -> new Context(contextKey, contextKey + randomAlphanumeric(10))).orElse(null);
+ return RuleDescriptionSection.builder().sectionKey(sectionKey)
+ .htmlContent(description)
+ .context(context)
+ .build();
+ }
+
+ private static void assertSectionExists(RuleDescriptionSection apiSection, Set<RuleDescriptionSectionDto> sectionDtos) {
+ sectionDtos.stream()
+ .filter(sectionDto -> sectionDto.getKey().equals(apiSection.getKey()) && sectionDto.getContent().equals(apiSection.getHtmlContent()))
+ .filter(sectionDto -> isSameContext(apiSection.getContext(), sectionDto.getContext()))
+ .findAny()
+ .orElseThrow(() -> new AssertionError(format("Impossible to find a section dto matching the API section %s", apiSection.getKey())));
+ }
+
+ private static boolean isSameContext(Optional<Context> apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
+ if (apiContext.isEmpty() && contextDto == null) {
+ return true;
+ }
+ return apiContext.filter(context -> isSameContext(context, contextDto)).isPresent();
+ }
+
+ private static boolean isSameContext(Context apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
+ if (contextDto == null) {
+ return false;
+ }
+ return Objects.equals(apiContext.getKey(), contextDto.getKey()) && Objects.equals(apiContext.getDisplayName(), contextDto.getDisplayName());
+ }
+
+ @Test
+ public void rule_previously_created_as_adhoc_becomes_none_adhoc() {
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true));
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createExternalRepository("fake", rule.getLanguage());
+ repo.createRule(rule.getRuleKey())
+ .setName(rule.getName())
+ .setHtmlDescription(rule.getDefaultRuleDescriptionSection().getContent());
+ repo.done();
+ });
+
+ RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
+ assertThat(reloaded.isAdHoc()).isFalse();
+ }
+
+ @Test
+ public void remove_no_more_defined_external_rule() {
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
+ .setStatus(READY)
+ .setIsExternal(true)
+ .setIsAdHoc(false));
+
+ execute();
+
+ RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
+ assertThat(reloaded.getStatus()).isEqualTo(REMOVED);
+ }
+
+ @Test
+ public void do_not_remove_no_more_defined_ad_hoc_rule() {
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
+ .setStatus(READY)
+ .setIsExternal(true)
+ .setIsAdHoc(true));
+
+ execute();
+
+ RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
+ assertThat(reloaded.getStatus()).isEqualTo(READY);
+ }
+
+ @Test
+ public void disable_then_enable_rule() {
+ // Install rule
+ system.setNow(DATE1.getTime());
+ execute(new FakeRepositoryV1());
+
+ // Uninstall rule
+ system.setNow(DATE2.getTime());
+ execute();
+
+ RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getStatus()).isEqualTo(REMOVED);
+ assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isZero();
+
+ // Re-install rule
+ system.setNow(DATE3.getTime());
+ execute(new FakeRepositoryV1());
+
+ rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
+ assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isOne();
+ }
+
+ @Test
+ public void do_not_update_rules_when_no_changes() {
+ execute(new FakeRepositoryV1());
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+
+ system.setNow(DATE2.getTime());
+ execute(new FakeRepositoryV1());
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule1.getUpdatedAt()).isEqualTo(DATE1.getTime());
+ }
+
+ @Test
+ public void do_not_update_already_removed_rules() {
+ execute(new FakeRepositoryV1());
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
+ assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
+
+ assertThat(rule2.getStatus()).isEqualTo(READY);
+
+ system.setNow(DATE2.getTime());
+ execute(new FakeRepositoryV2());
+
+ // On MySQL, need to update a rule otherwise rule2 will be seen as READY, but why ???
+ dbClient.ruleDao().update(db.getSession(), rule1);
+ db.getSession().commit();
+
+ // rule2 is removed
+ rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
+ assertThat(rule2.getStatus()).isEqualTo(REMOVED);
+
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
+
+ system.setNow(DATE3.getTime());
+ execute(new FakeRepositoryV2());
+ db.getSession().commit();
+
+ // -> rule2 is still removed, but not update at DATE3
+ rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ assertThat(rule2.getStatus()).isEqualTo(REMOVED);
+ assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
+
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
+ }
+
+ @Test
+ public void mass_insert() {
+ execute(new BigRepository());
+ assertThat(db.countRowsOfTable("rules")).isEqualTo(BigRepository.SIZE);
+ assertThat(db.countRowsOfTable("rules_parameters")).isEqualTo(BigRepository.SIZE * 20);
+ assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).hasSize(BigRepository.SIZE);
+ }
+
+ @Test
+ public void manage_repository_extensions() {
+ execute(new FindbugsRepository(), new FbContribRepository());
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ assertThat(rules).hasSize(2);
+ for (RuleDto rule : rules) {
+ assertThat(rule.getRepositoryKey()).isEqualTo("findbugs");
+ }
+ }
+
+ @Test
+ public void remove_system_tags_when_plugin_does_not_provide_any() {
+ // Rule already exists in DB, with some system tags
+ db.rules().insert(new RuleDto()
+ .setRuleKey("rule1")
+ .setRepositoryKey("findbugs")
+ .setName("Rule One")
+ .setScope(Scope.ALL)
+ .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule one description"))
+ .setDescriptionFormat(RuleDto.Format.HTML)
+ .setSystemTags(newHashSet("tag1", "tag2")));
+ db.getSession().commit();
+
+ // Synchronize rule without tag
+ execute(new FindbugsRepository());
+
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ assertThat(rules).hasSize(1).extracting(RuleDto::getKey, RuleDto::getSystemTags)
+ .containsOnly(tuple(RuleKey.of("findbugs", "rule1"), emptySet()));
+ }
+
+ @Test
+ public void rules_that_deprecate_previous_rule_must_be_recorded() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "rule1");
+ repo.done();
+ });
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey")
+ .addDeprecatedRuleKey("fake", "rule1")
+ .addDeprecatedRuleKey("fake", "rule2");
+ repo.done();
+ });
+
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
+ assertThat(rules).hasSize(1);
+ assertThat(deprecatedRuleKeys).hasSize(2);
+ }
+
+ @Test
+ public void rules_that_remove_deprecated_key_must_remove_records() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "rule1");
+ repo.done();
+ });
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey")
+ .addDeprecatedRuleKey("fake", "rule1")
+ .addDeprecatedRuleKey("fake", "rule2");
+ repo.done();
+ });
+
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
+ Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
+ assertThat(deprecatedRuleKeys).hasSize(2);
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey");
+ repo.done();
+ });
+
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
+ deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
+ assertThat(deprecatedRuleKeys).isEmpty();
+ }
+
+ @Test
+ public void declaring_two_rules_with_same_deprecated_RuleKey_should_throw_ISE() {
+ assertThatThrownBy(() -> {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey1")
+ .addDeprecatedRuleKey("fake", "old");
+ createRule(repo, "newKey2")
+ .addDeprecatedRuleKey("fake", "old");
+ repo.done();
+ });
+ })
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("The following deprecated rule keys are declared at least twice [fake:old]");
+ }
+
+ @Test
+ public void declaring_a_rule_with_a_deprecated_RuleKey_still_used_should_throw_ISE() {
+ assertThatThrownBy(() -> {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey1");
+ createRule(repo, "newKey2")
+ .addDeprecatedRuleKey("fake", "newKey1");
+ repo.done();
+ });
+ })
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("The following rule keys are declared both as deprecated and used key [fake:newKey1]");
+ }
+
+ @Test
+ public void updating_the_deprecated_to_a_new_ruleKey_should_throw_an_ISE() {
+ // On this new rule add a deprecated key
+ execute(context -> createRule(context, "javascript", "javascript", "s103",
+ r -> r.addDeprecatedRuleKey("javascript", "linelength")));
+
+ assertThatThrownBy(() -> {
+ // This rule should have been moved to another repository
+ execute(context -> createRule(context, "javascript", "sonarjs", "s103",
+ r -> r.addDeprecatedRuleKey("javascript", "linelength")));
+ })
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("An incorrect state of deprecated rule keys has been detected.\n " +
+ "The deprecated rule key [javascript:linelength] was previously deprecated by [javascript:s103]. [javascript:s103] should be a deprecated key of [sonarjs:s103],");
+ }
+
+ @Test
+ public void deprecate_rule_that_deprecated_another_rule() {
+ execute(context -> createRule(context, "javascript", "javascript", "s103"));
+ execute(context -> createRule(context, "javascript", "javascript", "s104",
+ r -> r.addDeprecatedRuleKey("javascript", "s103")));
+
+ // This rule should have been moved to another repository
+ execute(context -> createRule(context, "javascript", "sonarjs", "s105",
+ r -> r.addDeprecatedRuleKey("javascript", "s103")
+ .addDeprecatedRuleKey("javascript", "s104")));
+ }
+
+ @Test
+ public void declaring_a_rule_with_an_existing_RuleKey_still_used_should_throw_IAE() {
+ assertThatThrownBy(() -> {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey1");
+ createRule(repo, "newKey1");
+ repo.done();
+ });
+ })
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("The rule 'newKey1' of repository 'fake' is declared several times");
+ }
+
+ @Test
+ public void removed_rule_should_appear_in_changelog() {
+ //GIVEN
+ QProfileDto qProfileDto = db.qualityProfiles().insert();
+ RuleDto ruleDto = db.rules().insert(RULE_KEY1);
+ db.qualityProfiles().activateRule(qProfileDto, ruleDto);
+ ActiveRuleChange arChange = new ActiveRuleChange(DEACTIVATED, ActiveRuleDto.createFor(qProfileDto, ruleDto), ruleDto);
+ when(qProfileRules.deleteRule(any(DbSession.class), eq(ruleDto))).thenReturn(List.of(arChange));
+ //WHEN
+ execute(context -> context.createRepository("fake", "java").done());
+ //THEN
+ List<QProfileChangeDto> qProfileChangeDtos = dbClient.qProfileChangeDao().selectByQuery(db.getSession(), new QProfileChangeQuery(qProfileDto.getKee()));
+ assertThat(qProfileChangeDtos).extracting(QProfileChangeDto::getRulesProfileUuid, QProfileChangeDto::getChangeType)
+ .contains(tuple(qProfileDto.getRulesProfileUuid(), "DEACTIVATED"));
+ }
+
+ @Test
+ public void removed_rule_should_be_deleted_when_renamed_repository() {
+ //GIVEN
+ RuleDto removedRuleDto = db.rules().insert(RuleKey.of("old_repo", "removed_rule"));
+ RuleDto renamedRuleDto = db.rules().insert(RuleKey.of("old_repo", "renamed_rule"));
+ //WHEN
+ execute(context -> createRule(context, "java", "new_repo", renamedRuleDto.getRuleKey(),
+ rule -> rule.addDeprecatedRuleKey(renamedRuleDto.getRepositoryKey(), renamedRuleDto.getRuleKey())));
+ //THEN
+ verify(qProfileRules).deleteRule(any(DbSession.class), eq(removedRuleDto));
+ }
+
+ private void execute(RulesDefinition... defs) {
+ ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+ when(pluginRepository.getPluginKey(any(RulesDefinition.class))).thenReturn(FAKE_PLUGIN_KEY);
+ RuleDefinitionsLoader loader = new RuleDefinitionsLoader(pluginRepository, defs);
+ Languages languages = mock(Languages.class);
+ when(languages.get(any())).thenReturn(mock(Language.class));
+ reset(webServerRuleFinder);
+
+ RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, uuidFactory, metadataIndex,
+ resolver, rulesKeyVerifier, startupRuleUpdater);
+ task.start();
+ // Execute a commit to refresh session state as the task is using its own session
+ db.getSession().commit();
+
+ verify(webServerRuleFinder).startCaching();
+ }
+
+ private NewRule createRule(NewRepository repo, String key) {
+ return repo.createRule(key)
+ .setName(key + " name")
+ .setHtmlDescription("Description of " + key)
+ .setSeverity(BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag2", "tag3")
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA);
+ }
+
+ @SafeVarargs
+ private void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) {
+ NewRepository repo = context.createRepository(repositoryKey, language);
+ NewRule newRule = repo.createRule(ruleKey)
+ .setName(ruleKey)
+ .setHtmlDescription("Description of One")
+ .setSeverity(BLOCKER)
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA);
+
+ Arrays.stream(consumers).forEach(c -> c.accept(newRule));
+ repo.done();
+ }
+
+ private void verifyIndicesMarkedAsInitialized() {
+ verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_RULE, true);
+ verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_ACTIVE_RULE, true);
+ reset(metadataIndex);
+ }
+
+ private void verifyIndicesNotMarkedAsInitialized() {
+ verifyNoInteractions(metadataIndex);
+ }
+
+ private RuleParamDto getParam(List<RuleParamDto> params, String key) {
+ for (RuleParamDto param : params) {
+ if (param.getName().equals(key)) {
+ return param;
+ }
+ }
+ return null;
+ }
+
+ static class FakeRepositoryV1 implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("fake", "java");
+ NewRule rule1 = repo.createRule(RULE_KEY1.rule())
+ .setName("One")
+ .setHtmlDescription("Description of One")
+ .setSeverity(BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag2", "tag3")
+ .setScope(RuleScope.ALL)
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA)
+ .setGapDescription("java.S115.effortToFix")
+ .addEducationPrincipleKeys("concept1", "concept2", "concept3");
+ rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h"));
+
+ rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default1");
+ rule1.createParam("param2").setDescription("parameter two").setDefaultValue("default2");
+
+ repo.createRule(HOTSPOT_RULE_KEY.rule())
+ .setName("Hotspot")
+ .setHtmlDescription("Minimal hotspot")
+ .setType(RuleType.SECURITY_HOTSPOT)
+ .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
+ .addCwe(1, 123, 863);
+
+ repo.createRule(RULE_KEY2.rule())
+ .setName("Two")
+ .setHtmlDescription("Minimal rule");
+ repo.done();
+ }
+ }
+
+ /**
+ * FakeRepositoryV1 with some changes
+ */
+ static class FakeRepositoryV2 implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("fake", "java");
+
+ // almost all the attributes of rule1 are changed
+ NewRule rule1 = repo.createRule(RULE_KEY1.rule())
+ .setName("One v2")
+ .setHtmlDescription("Description of One v2")
+ .setSeverity(INFO)
+ .setInternalKey("config1 v2")
+ // tag2 and tag3 removed, tag4 added
+ .setTags("tag1", "tag4")
+ .setType(RuleType.BUG)
+ .setStatus(READY)
+ .setGapDescription("java.S115.effortToFix.v2")
+ .addEducationPrincipleKeys("concept1", "concept4");
+ rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("6d", "2h"));
+ rule1.createParam("param1").setDescription("parameter one v2").setDefaultValue("default1 v2");
+ rule1.createParam("param2").setDescription("parameter two v2").setDefaultValue("default2 v2");
+
+ // rule2 is dropped, rule3 is new
+ repo.createRule(RULE_KEY3.rule())
+ .setName("Three")
+ .setHtmlDescription("Rule Three");
+
+ repo.done();
+ }
+ }
+
+ static class FakeRepositoryV3 implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("fake", "java");
+ // rule 3 is dropped
+ repo.createRule(RULE_KEY3.rule())
+ .setName("Three")
+ .setMarkdownDescription("Rule Three V2");
+
+ repo.done();
+ }
+ }
+
+ static class ExternalRuleRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createExternalRepository("eslint", "js");
+ repo.createRule(RULE_KEY1.rule())
+ .setName("One")
+ .setHtmlDescription("Description of One")
+ .setSeverity(BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag2", "tag3")
+ .setScope(RuleScope.ALL)
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA)
+ .addEducationPrincipleKeys("concept1", "concept2", "concept3");
+
+ repo.createRule(EXTERNAL_HOTSPOT_RULE_KEY.rule())
+ .setName("Hotspot")
+ .setHtmlDescription("Minimal hotspot")
+ .setType(RuleType.SECURITY_HOTSPOT)
+ .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
+ .addCwe(1, 123, 863);
+
+ repo.done();
+ }
+ }
+
+ static class BigRepository implements RulesDefinition {
+ static final int SIZE = 500;
+
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("big", "java");
+ for (int i = 0; i < SIZE; i++) {
+ NewRule rule = repo.createRule("rule" + i)
+ .setName("name of " + i)
+ .setHtmlDescription("description of " + i);
+ for (int j = 0; j < 20; j++) {
+ rule.createParam("param" + j);
+ }
+
+ }
+ repo.done();
+ }
+ }
+
+ static class FindbugsRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("findbugs", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One");
+ repo.done();
+ }
+ }
+
+ static class FbContribRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewExtendedRepository repo = context.createRepository("findbugs", "java");
+ repo.createRule("rule2")
+ .setName("Rule Two")
+ .setHtmlDescription("Description of Rule Two");
+ repo.done();
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.registration;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.assertj.core.groups.Tuple;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+import org.sonar.server.rule.registration.SingleDeprecatedRuleKey;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SingleDeprecatedRuleKeyTest {
+
+ @Test
+ public void test_creation_from_DeprecatedRuleKeyDto() {
+ // Creation from DeprecatedRuleKeyDto
+ DeprecatedRuleKeyDto deprecatedRuleKeyDto = new DeprecatedRuleKeyDto()
+ .setOldRuleKey(randomAlphanumeric(50))
+ .setOldRepositoryKey(randomAlphanumeric(50))
+ .setRuleUuid(randomAlphanumeric(50))
+ .setUuid(randomAlphanumeric(40));
+
+ SingleDeprecatedRuleKey singleDeprecatedRuleKey = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto);
+
+ assertThat(singleDeprecatedRuleKey.getOldRepositoryKey()).isEqualTo(deprecatedRuleKeyDto.getOldRepositoryKey());
+ assertThat(singleDeprecatedRuleKey.getOldRuleKey()).isEqualTo(deprecatedRuleKeyDto.getOldRuleKey());
+ assertThat(singleDeprecatedRuleKey.getNewRepositoryKey()).isEqualTo(deprecatedRuleKeyDto.getNewRepositoryKey());
+ assertThat(singleDeprecatedRuleKey.getNewRuleKey()).isEqualTo(deprecatedRuleKeyDto.getNewRuleKey());
+ assertThat(singleDeprecatedRuleKey.getUuid()).isEqualTo(deprecatedRuleKeyDto.getUuid());
+ assertThat(singleDeprecatedRuleKey.getRuleUuid()).isEqualTo(deprecatedRuleKeyDto.getRuleUuid());
+ assertThat(singleDeprecatedRuleKey.getOldRuleKeyAsRuleKey())
+ .isEqualTo(RuleKey.of(deprecatedRuleKeyDto.getOldRepositoryKey(), deprecatedRuleKeyDto.getOldRuleKey()));
+ }
+
+ @Test
+ public void test_creation_from_RulesDefinitionRule() {
+ // Creation from RulesDefinition.Rule
+ ImmutableSet<RuleKey> deprecatedRuleKeys = ImmutableSet.of(
+ RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)),
+ RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)),
+ RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)));
+
+ RulesDefinition.Repository repository = mock(RulesDefinition.Repository.class);
+ when(repository.key()).thenReturn(randomAlphanumeric(50));
+
+ RulesDefinition.Rule rule = mock(RulesDefinition.Rule.class);
+ when(rule.key()).thenReturn(randomAlphanumeric(50));
+ when(rule.deprecatedRuleKeys()).thenReturn(deprecatedRuleKeys);
+ when(rule.repository()).thenReturn(repository);
+
+ Set<SingleDeprecatedRuleKey> singleDeprecatedRuleKeys = SingleDeprecatedRuleKey.from(rule);
+ assertThat(singleDeprecatedRuleKeys).hasSize(deprecatedRuleKeys.size());
+ assertThat(singleDeprecatedRuleKeys)
+ .extracting(SingleDeprecatedRuleKey::getUuid, SingleDeprecatedRuleKey::getOldRepositoryKey, SingleDeprecatedRuleKey::getOldRuleKey,
+ SingleDeprecatedRuleKey::getNewRepositoryKey, SingleDeprecatedRuleKey::getNewRuleKey, SingleDeprecatedRuleKey::getOldRuleKeyAsRuleKey)
+ .containsExactlyInAnyOrder(
+ deprecatedRuleKeys.stream().map(
+ r -> tuple(null, r.repository(), r.rule(), rule.repository().key(), rule.key(), RuleKey.of(r.repository(), r.rule())))
+ .toList().toArray(new Tuple[deprecatedRuleKeys.size()]));
+ }
+
+ @Test
+ public void test_equality() {
+ DeprecatedRuleKeyDto deprecatedRuleKeyDto1 = new DeprecatedRuleKeyDto()
+ .setOldRuleKey(randomAlphanumeric(50))
+ .setOldRepositoryKey(randomAlphanumeric(50))
+ .setUuid(randomAlphanumeric(40))
+ .setRuleUuid("some-uuid");
+
+ DeprecatedRuleKeyDto deprecatedRuleKeyDto1WithoutUuid = new DeprecatedRuleKeyDto()
+ .setOldRuleKey(deprecatedRuleKeyDto1.getOldRuleKey())
+ .setOldRepositoryKey(deprecatedRuleKeyDto1.getOldRepositoryKey());
+
+ DeprecatedRuleKeyDto deprecatedRuleKeyDto2 = new DeprecatedRuleKeyDto()
+ .setOldRuleKey(randomAlphanumeric(50))
+ .setOldRepositoryKey(randomAlphanumeric(50))
+ .setUuid(randomAlphanumeric(40));
+
+ SingleDeprecatedRuleKey singleDeprecatedRuleKey1 = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1);
+ SingleDeprecatedRuleKey singleDeprecatedRuleKey2 = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2);
+
+ assertThat(singleDeprecatedRuleKey1)
+ .isEqualTo(singleDeprecatedRuleKey1)
+ .isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1))
+ .isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1WithoutUuid));
+ assertThat(singleDeprecatedRuleKey2).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2));
+
+ assertThat(singleDeprecatedRuleKey1)
+ .hasSameHashCodeAs(singleDeprecatedRuleKey1)
+ .hasSameHashCodeAs(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1))
+ .hasSameHashCodeAs(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1WithoutUuid));
+ assertThat(singleDeprecatedRuleKey2).hasSameHashCodeAs(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2));
+
+ assertThat(singleDeprecatedRuleKey1)
+ .isNotNull()
+ .isNotEqualTo("")
+ .isNotEqualTo(null)
+ .isNotEqualTo(singleDeprecatedRuleKey2);
+ assertThat(singleDeprecatedRuleKey2).isNotEqualTo(singleDeprecatedRuleKey1);
+
+ assertThat(singleDeprecatedRuleKey1.hashCode()).isNotEqualTo(singleDeprecatedRuleKey2.hashCode());
+ assertThat(singleDeprecatedRuleKey2.hashCode()).isNotEqualTo(singleDeprecatedRuleKey1.hashCode());
+ }
+}
import org.sonar.server.rule.AdvancedRuleDescriptionSectionsGenerator;
import org.sonar.server.rule.LegacyHotspotRuleDescriptionSectionsGenerator;
import org.sonar.server.rule.LegacyIssueRuleDescriptionSectionsGenerator;
-import org.sonar.server.rule.RegisterRules;
+import org.sonar.server.rule.registration.RulesKeyVerifier;
+import org.sonar.server.rule.registration.RulesRegistrant;
import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
import org.sonar.server.rule.WebServerRuleFinder;
+import org.sonar.server.rule.registration.StartupRuleUpdater;
import org.sonar.server.startup.GeneratePluginIndex;
import org.sonar.server.startup.RegisterMetrics;
import org.sonar.server.startup.RegisterPermissionTemplates;
AdvancedRuleDescriptionSectionsGenerator.class,
LegacyHotspotRuleDescriptionSectionsGenerator.class,
LegacyIssueRuleDescriptionSectionsGenerator.class,
- RegisterRules.class,
+ RulesRegistrant.class,
+ RulesKeyVerifier.class,
+ StartupRuleUpdater.class,
BuiltInQProfileLoader.class);
addIfStartupLeader(
BuiltInQualityProfilesUpdateListener.class,