You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RegisterRules.java 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.rule;
  21. import com.google.common.collect.ImmutableMap;
  22. import com.google.common.collect.ImmutableSet;
  23. import com.google.common.collect.Sets;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.HashMap;
  27. import java.util.HashSet;
  28. import java.util.LinkedHashMap;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Objects;
  32. import java.util.Optional;
  33. import java.util.Set;
  34. import java.util.function.Supplier;
  35. import java.util.stream.Collectors;
  36. import java.util.stream.Stream;
  37. import javax.annotation.Nonnull;
  38. import javax.annotation.Nullable;
  39. import org.apache.commons.lang.StringUtils;
  40. import org.sonar.api.Startable;
  41. import org.sonar.api.resources.Languages;
  42. import org.sonar.api.rule.RuleKey;
  43. import org.sonar.api.rule.RuleScope;
  44. import org.sonar.api.rule.RuleStatus;
  45. import org.sonar.api.rules.RuleType;
  46. import org.sonar.api.server.debt.DebtRemediationFunction;
  47. import org.sonar.api.server.rule.RulesDefinition;
  48. import org.sonar.api.utils.System2;
  49. import org.sonar.api.utils.log.Logger;
  50. import org.sonar.api.utils.log.Loggers;
  51. import org.sonar.api.utils.log.Profiler;
  52. import org.sonar.core.util.UuidFactory;
  53. import org.sonar.core.util.stream.MoreCollectors;
  54. import org.sonar.db.DbClient;
  55. import org.sonar.db.DbSession;
  56. import org.sonar.db.qualityprofile.ActiveRuleDto;
  57. import org.sonar.db.qualityprofile.ActiveRuleParamDto;
  58. import org.sonar.db.rule.DeprecatedRuleKeyDto;
  59. import org.sonar.db.rule.RuleDescriptionSectionDto;
  60. import org.sonar.db.rule.RuleDto;
  61. import org.sonar.db.rule.RuleDto.Format;
  62. import org.sonar.db.rule.RuleDto.Scope;
  63. import org.sonar.db.rule.RuleParamDto;
  64. import org.sonar.db.rule.RuleRepositoryDto;
  65. import org.sonar.server.es.metadata.MetadataIndex;
  66. import org.sonar.server.qualityprofile.ActiveRuleChange;
  67. import org.sonar.server.qualityprofile.QProfileRules;
  68. import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
  69. import org.sonar.server.rule.index.RuleIndexer;
  70. import static com.google.common.base.Preconditions.checkNotNull;
  71. import static com.google.common.base.Preconditions.checkState;
  72. import static com.google.common.collect.Sets.difference;
  73. import static com.google.common.collect.Sets.intersection;
  74. import static java.lang.String.format;
  75. import static java.util.Collections.emptyList;
  76. import static java.util.Collections.emptySet;
  77. import static java.util.Collections.unmodifiableMap;
  78. import static org.apache.commons.lang.StringUtils.isNotEmpty;
  79. import static org.sonar.core.util.stream.MoreCollectors.toList;
  80. import static org.sonar.core.util.stream.MoreCollectors.toSet;
  81. import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
  82. /**
  83. * Register rules at server startup
  84. */
  85. public class RegisterRules implements Startable {
  86. private static final Logger LOG = Loggers.get(RegisterRules.class);
  87. private final RuleDefinitionsLoader defLoader;
  88. private final QProfileRules qProfileRules;
  89. private final DbClient dbClient;
  90. private final RuleIndexer ruleIndexer;
  91. private final ActiveRuleIndexer activeRuleIndexer;
  92. private final Languages languages;
  93. private final System2 system2;
  94. private final WebServerRuleFinder webServerRuleFinder;
  95. private final UuidFactory uuidFactory;
  96. private final MetadataIndex metadataIndex;
  97. private final RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver;
  98. public RegisterRules(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer,
  99. ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2,
  100. WebServerRuleFinder webServerRuleFinder, UuidFactory uuidFactory, MetadataIndex metadataIndex,
  101. RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver) {
  102. this.defLoader = defLoader;
  103. this.qProfileRules = qProfileRules;
  104. this.dbClient = dbClient;
  105. this.ruleIndexer = ruleIndexer;
  106. this.activeRuleIndexer = activeRuleIndexer;
  107. this.languages = languages;
  108. this.system2 = system2;
  109. this.webServerRuleFinder = webServerRuleFinder;
  110. this.uuidFactory = uuidFactory;
  111. this.metadataIndex = metadataIndex;
  112. this.ruleDescriptionSectionsGeneratorResolver = ruleDescriptionSectionsGeneratorResolver;
  113. }
  114. @Override
  115. public void start() {
  116. Profiler profiler = Profiler.create(LOG).startInfo("Register rules");
  117. try (DbSession dbSession = dbClient.openSession(true)) {
  118. RulesDefinition.Context ruleDefinitionContext = defLoader.load();
  119. List<RulesDefinition.Repository> repositories = ruleDefinitionContext.repositories();
  120. RegisterRulesContext registerRulesContext = createRegisterRulesContext(dbSession);
  121. verifyRuleKeyConsistency(repositories, registerRulesContext);
  122. for (RulesDefinition.ExtendedRepository repoDef : repositories) {
  123. if (languages.get(repoDef.language()) != null) {
  124. registerRules(registerRulesContext, repoDef.rules(), dbSession);
  125. dbSession.commit();
  126. }
  127. }
  128. processRemainingDbRules(registerRulesContext, dbSession);
  129. List<ActiveRuleChange> changes = removeActiveRulesOnStillExistingRepositories(dbSession, registerRulesContext, repositories);
  130. dbSession.commit();
  131. persistRepositories(dbSession, ruleDefinitionContext.repositories());
  132. // FIXME lack of resiliency, active rules index is corrupted if rule index fails
  133. // to be updated. Only a single DB commit should be executed.
  134. ruleIndexer.commitAndIndex(dbSession, registerRulesContext.getAllModified().map(RuleDto::getUuid).collect(toSet()));
  135. activeRuleIndexer.commitAndIndex(dbSession, changes);
  136. registerRulesContext.getRenamed().forEach(e -> LOG.info("Rule {} re-keyed to {}", e.getValue(), e.getKey().getKey()));
  137. profiler.stopDebug();
  138. if (!registerRulesContext.hasDbRules()) {
  139. Stream.concat(ruleIndexer.getIndexTypes().stream(), activeRuleIndexer.getIndexTypes().stream())
  140. .forEach(t -> metadataIndex.setInitialized(t, true));
  141. }
  142. webServerRuleFinder.startCaching();
  143. }
  144. }
  145. private RegisterRulesContext createRegisterRulesContext(DbSession dbSession) {
  146. Map<RuleKey, RuleDto> allRules = dbClient.ruleDao().selectAll(dbSession).stream()
  147. .collect(uniqueIndex(RuleDto::getKey));
  148. Map<String, Set<SingleDeprecatedRuleKey>> existingDeprecatedKeysById = loadDeprecatedRuleKeys(dbSession);
  149. Map<String, List<RuleParamDto>> ruleParamsByRuleUuid = loadAllRuleParameters(dbSession);
  150. return new RegisterRulesContext(allRules, existingDeprecatedKeysById, ruleParamsByRuleUuid);
  151. }
  152. private Map<String, List<RuleParamDto>> loadAllRuleParameters(DbSession dbSession) {
  153. return dbClient.ruleDao().selectAllRuleParams(dbSession).stream()
  154. .collect(Collectors.groupingBy(RuleParamDto::getRuleUuid));
  155. }
  156. private Map<String, Set<SingleDeprecatedRuleKey>> loadDeprecatedRuleKeys(DbSession dbSession) {
  157. return dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
  158. .map(SingleDeprecatedRuleKey::from)
  159. .collect(Collectors.groupingBy(SingleDeprecatedRuleKey::getRuleUuid, Collectors.toSet()));
  160. }
  161. private static class RegisterRulesContext {
  162. // initial immutable data
  163. private final Map<RuleKey, RuleDto> dbRules;
  164. private final Set<RuleDto> known;
  165. private final Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid;
  166. private final Map<String, List<RuleParamDto>> ruleParamsByRuleUuid;
  167. private final Map<RuleKey, RuleDto> dbRulesByDbDeprecatedKey;
  168. // mutable data
  169. private final Set<RuleDto> created = new HashSet<>();
  170. private final Map<RuleDto, RuleKey> renamed = new HashMap<>();
  171. private final Set<RuleDto> updated = new HashSet<>();
  172. private final Set<RuleDto> unchanged = new HashSet<>();
  173. private final Set<RuleDto> removed = new HashSet<>();
  174. private RegisterRulesContext(Map<RuleKey, RuleDto> dbRules, Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid,
  175. Map<String, List<RuleParamDto>> ruleParamsByRuleUuid) {
  176. this.dbRules = ImmutableMap.copyOf(dbRules);
  177. this.known = ImmutableSet.copyOf(dbRules.values());
  178. this.dbDeprecatedKeysByUuid = dbDeprecatedKeysByUuid;
  179. this.ruleParamsByRuleUuid = ruleParamsByRuleUuid;
  180. this.dbRulesByDbDeprecatedKey = buildDbRulesByDbDeprecatedKey(dbDeprecatedKeysByUuid, dbRules);
  181. }
  182. private static Map<RuleKey, RuleDto> buildDbRulesByDbDeprecatedKey(Map<String, Set<SingleDeprecatedRuleKey>> dbDeprecatedKeysByUuid,
  183. Map<RuleKey, RuleDto> dbRules) {
  184. Map<String, RuleDto> dbRulesByRuleUuid = dbRules.values().stream()
  185. .collect(uniqueIndex(RuleDto::getUuid));
  186. Map<RuleKey, RuleDto> rulesByKey = new LinkedHashMap<>();
  187. for (Map.Entry<String, Set<SingleDeprecatedRuleKey>> entry : dbDeprecatedKeysByUuid.entrySet()) {
  188. String ruleUuid = entry.getKey();
  189. RuleDto rule = dbRulesByRuleUuid.get(ruleUuid);
  190. if (rule == null) {
  191. LOG.warn("Could not retrieve rule with uuid %s referenced by a deprecated rule key. " +
  192. "The following deprecated rule keys seem to be referencing a non-existing rule",
  193. ruleUuid, entry.getValue());
  194. } else {
  195. entry.getValue().forEach(d -> rulesByKey.put(d.getOldRuleKeyAsRuleKey(), rule));
  196. }
  197. }
  198. return unmodifiableMap(rulesByKey);
  199. }
  200. private boolean hasDbRules() {
  201. return !dbRules.isEmpty();
  202. }
  203. private Optional<RuleDto> getDbRuleFor(RulesDefinition.Rule ruleDef) {
  204. RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
  205. Optional<RuleDto> res = Stream.concat(Stream.of(ruleKey), ruleDef.deprecatedRuleKeys().stream())
  206. .map(dbRules::get)
  207. .filter(Objects::nonNull)
  208. .findFirst();
  209. // may occur in case of plugin downgrade
  210. if (res.isEmpty()) {
  211. return Optional.ofNullable(dbRulesByDbDeprecatedKey.get(ruleKey));
  212. }
  213. return res;
  214. }
  215. private Map<RuleKey, SingleDeprecatedRuleKey> getDbDeprecatedKeysByOldRuleKey() {
  216. return dbDeprecatedKeysByUuid.values().stream()
  217. .flatMap(Collection::stream)
  218. .collect(uniqueIndex(SingleDeprecatedRuleKey::getOldRuleKeyAsRuleKey));
  219. }
  220. private Set<SingleDeprecatedRuleKey> getDBDeprecatedKeysFor(RuleDto rule) {
  221. return dbDeprecatedKeysByUuid.getOrDefault(rule.getUuid(), emptySet());
  222. }
  223. private List<RuleParamDto> getRuleParametersFor(String ruleUuid) {
  224. return ruleParamsByRuleUuid.getOrDefault(ruleUuid, emptyList());
  225. }
  226. private Stream<RuleDto> getRemaining() {
  227. Set<RuleDto> res = new HashSet<>(dbRules.values());
  228. res.removeAll(unchanged);
  229. res.removeAll(renamed.keySet());
  230. res.removeAll(updated);
  231. res.removeAll(removed);
  232. return res.stream();
  233. }
  234. private Stream<RuleDto> getRemoved() {
  235. return removed.stream();
  236. }
  237. public Stream<Map.Entry<RuleDto, RuleKey>> getRenamed() {
  238. return renamed.entrySet().stream();
  239. }
  240. private Stream<RuleDto> getAllModified() {
  241. return Stream.of(
  242. created.stream(),
  243. updated.stream(),
  244. removed.stream(),
  245. renamed.keySet().stream())
  246. .flatMap(s -> s);
  247. }
  248. private boolean isCreated(RuleDto ruleDto) {
  249. return created.contains(ruleDto);
  250. }
  251. private boolean isRenamed(RuleDto ruleDto) {
  252. return renamed.containsKey(ruleDto);
  253. }
  254. private boolean isUpdated(RuleDto ruleDto) {
  255. return updated.contains(ruleDto);
  256. }
  257. private void created(RuleDto ruleDto) {
  258. checkState(!known.contains(ruleDto), "known RuleDto can't be created");
  259. created.add(ruleDto);
  260. }
  261. private void renamed(RuleDto ruleDto) {
  262. ensureKnown(ruleDto);
  263. renamed.put(ruleDto, ruleDto.getKey());
  264. }
  265. private void updated(RuleDto ruleDto) {
  266. ensureKnown(ruleDto);
  267. updated.add(ruleDto);
  268. }
  269. private void removed(RuleDto ruleDto) {
  270. ensureKnown(ruleDto);
  271. removed.add(ruleDto);
  272. }
  273. private void unchanged(RuleDto ruleDto) {
  274. ensureKnown(ruleDto);
  275. unchanged.add(ruleDto);
  276. }
  277. private void ensureKnown(RuleDto ruleDto) {
  278. checkState(known.contains(ruleDto), "unknown RuleDto");
  279. }
  280. }
  281. private void persistRepositories(DbSession dbSession, List<RulesDefinition.Repository> repositories) {
  282. List<String> keys = repositories.stream().map(RulesDefinition.Repository::key).collect(toList(repositories.size()));
  283. Set<String> existingKeys = dbClient.ruleRepositoryDao().selectAllKeys(dbSession);
  284. Map<Boolean, List<RuleRepositoryDto>> dtos = repositories.stream()
  285. .map(r -> new RuleRepositoryDto(r.key(), r.language(), r.name()))
  286. .collect(Collectors.groupingBy(i -> existingKeys.contains(i.getKey())));
  287. dbClient.ruleRepositoryDao().update(dbSession, dtos.getOrDefault(true, emptyList()));
  288. dbClient.ruleRepositoryDao().insert(dbSession, dtos.getOrDefault(false, emptyList()));
  289. dbClient.ruleRepositoryDao().deleteIfKeyNotIn(dbSession, keys);
  290. dbSession.commit();
  291. }
  292. @Override
  293. public void stop() {
  294. // nothing
  295. }
  296. private void registerRules(RegisterRulesContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) {
  297. Map<RulesDefinition.Rule, RuleDto> dtos = new LinkedHashMap<>(ruleDefs.size());
  298. for (RulesDefinition.Rule ruleDef : ruleDefs) {
  299. RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
  300. RuleDto ruleDto = findOrCreateRuleDto(context, session, ruleDef);
  301. dtos.put(ruleDef, ruleDto);
  302. // we must detect renaming __before__ we modify the DTO
  303. if (!ruleDto.getKey().equals(ruleKey)) {
  304. context.renamed(ruleDto);
  305. ruleDto.setRuleKey(ruleKey);
  306. }
  307. if (anyMerge(ruleDef, ruleDto)) {
  308. context.updated(ruleDto);
  309. }
  310. if (context.isUpdated(ruleDto) || context.isRenamed(ruleDto)) {
  311. update(session, ruleDto);
  312. } else if (!context.isCreated(ruleDto)) {
  313. context.unchanged(ruleDto);
  314. }
  315. }
  316. for (Map.Entry<RulesDefinition.Rule, RuleDto> e : dtos.entrySet()) {
  317. mergeParams(context, e.getKey(), e.getValue(), session);
  318. updateDeprecatedKeys(context, e.getKey(), e.getValue(), session);
  319. }
  320. }
  321. @Nonnull
  322. private RuleDto findOrCreateRuleDto(RegisterRulesContext context, DbSession session, RulesDefinition.Rule ruleDef) {
  323. return context.getDbRuleFor(ruleDef)
  324. .orElseGet(() -> {
  325. RuleDto newRule = createRuleDto(ruleDef, session);
  326. context.created(newRule);
  327. return newRule;
  328. });
  329. }
  330. private RuleDto createRuleDto(RulesDefinition.Rule ruleDef, DbSession session) {
  331. RuleDto ruleDto = new RuleDto()
  332. .setUuid(uuidFactory.create())
  333. .setRuleKey(RuleKey.of(ruleDef.repository().key(), ruleDef.key()))
  334. .setPluginKey(ruleDef.pluginKey())
  335. .setIsTemplate(ruleDef.template())
  336. .setConfigKey(ruleDef.internalKey())
  337. .setLanguage(ruleDef.repository().language())
  338. .setName(ruleDef.name())
  339. .setSeverity(ruleDef.severity())
  340. .setStatus(ruleDef.status())
  341. .setGapDescription(ruleDef.gapDescription())
  342. .setSystemTags(ruleDef.tags())
  343. .setSecurityStandards(ruleDef.securityStandards())
  344. .setType(RuleType.valueOf(ruleDef.type().name()))
  345. .setScope(toDtoScope(ruleDef.scope()))
  346. .setIsExternal(ruleDef.repository().isExternal())
  347. .setIsAdHoc(false)
  348. .setCreatedAt(system2.now())
  349. .setUpdatedAt(system2.now());
  350. if (isNotEmpty(ruleDef.htmlDescription())) {
  351. ruleDto.setDescriptionFormat(Format.HTML);
  352. } else if (isNotEmpty(ruleDef.markdownDescription())) {
  353. ruleDto.setDescriptionFormat(Format.MARKDOWN);
  354. }
  355. generateRuleDescriptionSections(ruleDef)
  356. .forEach(ruleDto::addRuleDescriptionSectionDto);
  357. DebtRemediationFunction debtRemediationFunction = ruleDef.debtRemediationFunction();
  358. if (debtRemediationFunction != null) {
  359. ruleDto.setDefRemediationFunction(debtRemediationFunction.type().name());
  360. ruleDto.setDefRemediationGapMultiplier(debtRemediationFunction.gapMultiplier());
  361. ruleDto.setDefRemediationBaseEffort(debtRemediationFunction.baseEffort());
  362. ruleDto.setGapDescription(ruleDef.gapDescription());
  363. }
  364. dbClient.ruleDao().insert(session, ruleDto);
  365. return ruleDto;
  366. }
  367. private Set<RuleDescriptionSectionDto> generateRuleDescriptionSections(RulesDefinition.Rule ruleDef) {
  368. RuleDescriptionSectionsGenerator descriptionSectionGenerator = ruleDescriptionSectionsGeneratorResolver.getRuleDescriptionSectionsGenerator(ruleDef);
  369. return descriptionSectionGenerator.generateSections(ruleDef);
  370. }
  371. private static Scope toDtoScope(RuleScope scope) {
  372. switch (scope) {
  373. case ALL:
  374. return Scope.ALL;
  375. case MAIN:
  376. return Scope.MAIN;
  377. case TEST:
  378. return Scope.TEST;
  379. default:
  380. throw new IllegalArgumentException("Unknown rule scope: " + scope);
  381. }
  382. }
  383. private boolean anyMerge(RulesDefinition.Rule ruleDef, RuleDto ruleDto) {
  384. boolean ruleMerged = mergeRule(ruleDef, ruleDto);
  385. boolean debtDefinitionsMerged = mergeDebtDefinitions(ruleDef, ruleDto);
  386. boolean tagsMerged = mergeTags(ruleDef, ruleDto);
  387. boolean securityStandardsMerged = mergeSecurityStandards(ruleDef, ruleDto);
  388. return ruleMerged || debtDefinitionsMerged || tagsMerged || securityStandardsMerged;
  389. }
  390. private boolean mergeRule(RulesDefinition.Rule def, RuleDto dto) {
  391. boolean changed = false;
  392. if (!Objects.equals(dto.getName(), def.name())) {
  393. dto.setName(def.name());
  394. changed = true;
  395. }
  396. if (mergeDescription(def, dto)) {
  397. changed = true;
  398. }
  399. if (!Objects.equals(dto.getPluginKey(), def.pluginKey())) {
  400. dto.setPluginKey(def.pluginKey());
  401. changed = true;
  402. }
  403. if (!Objects.equals(dto.getConfigKey(), def.internalKey())) {
  404. dto.setConfigKey(def.internalKey());
  405. changed = true;
  406. }
  407. String severity = def.severity();
  408. if (!Objects.equals(dto.getSeverityString(), severity)) {
  409. dto.setSeverity(severity);
  410. changed = true;
  411. }
  412. boolean isTemplate = def.template();
  413. if (isTemplate != dto.isTemplate()) {
  414. dto.setIsTemplate(isTemplate);
  415. changed = true;
  416. }
  417. if (def.status() != dto.getStatus()) {
  418. dto.setStatus(def.status());
  419. changed = true;
  420. }
  421. if (!Objects.equals(dto.getScope().name(), def.scope().name())) {
  422. dto.setScope(toDtoScope(def.scope()));
  423. changed = true;
  424. }
  425. if (!Objects.equals(dto.getLanguage(), def.repository().language())) {
  426. dto.setLanguage(def.repository().language());
  427. changed = true;
  428. }
  429. RuleType type = RuleType.valueOf(def.type().name());
  430. if (!Objects.equals(dto.getType(), type.getDbConstant())) {
  431. dto.setType(type);
  432. changed = true;
  433. }
  434. if (dto.isAdHoc()) {
  435. dto.setIsAdHoc(false);
  436. changed = true;
  437. }
  438. return changed;
  439. }
  440. private boolean mergeDescription(RulesDefinition.Rule rule, RuleDto ruleDto) {
  441. Set<RuleDescriptionSectionDto> newRuleDescriptionSectionDtos = generateRuleDescriptionSections(rule);
  442. if (ruleDescriptionSectionsUnchanged(ruleDto, newRuleDescriptionSectionDtos)) {
  443. return false;
  444. }
  445. ruleDto.replaceRuleDescriptionSectionDtos(newRuleDescriptionSectionDtos);
  446. if (containsHtmlDescription(rule)) {
  447. ruleDto.setDescriptionFormat(Format.HTML);
  448. return true;
  449. } else if (isNotEmpty(rule.markdownDescription())) {
  450. ruleDto.setDescriptionFormat(Format.MARKDOWN);
  451. return true;
  452. }
  453. return false;
  454. }
  455. private static boolean containsHtmlDescription(RulesDefinition.Rule rule) {
  456. return isNotEmpty(rule.htmlDescription()) || !rule.ruleDescriptionSections().isEmpty();
  457. }
  458. private static boolean ruleDescriptionSectionsUnchanged(RuleDto ruleDto, Set<RuleDescriptionSectionDto> newRuleDescriptionSectionDtos) {
  459. Map<String, String> oldKeysToSections = toMap(ruleDto.getRuleDescriptionSectionDtos());
  460. Map<String, String> newKeysToSections = toMap(newRuleDescriptionSectionDtos);
  461. return oldKeysToSections.equals(newKeysToSections);
  462. }
  463. private static Map<String, String> toMap(Set<RuleDescriptionSectionDto> ruleDto) {
  464. return ruleDto
  465. .stream()
  466. .collect(Collectors.toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
  467. }
  468. private static boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDto dto) {
  469. // Debt definitions are set to null if the sub-characteristic and the remediation function are null
  470. DebtRemediationFunction debtRemediationFunction = def.debtRemediationFunction();
  471. boolean hasDebt = debtRemediationFunction != null;
  472. if (hasDebt) {
  473. return mergeDebtDefinitions(dto,
  474. debtRemediationFunction.type().name(),
  475. debtRemediationFunction.gapMultiplier(),
  476. debtRemediationFunction.baseEffort(),
  477. def.gapDescription());
  478. }
  479. return mergeDebtDefinitions(dto, null, null, null, null);
  480. }
  481. private static boolean mergeDebtDefinitions(RuleDto dto, @Nullable String remediationFunction,
  482. @Nullable String remediationCoefficient, @Nullable String remediationOffset, @Nullable String effortToFixDescription) {
  483. boolean changed = false;
  484. if (!Objects.equals(dto.getDefRemediationFunction(), remediationFunction)) {
  485. dto.setDefRemediationFunction(remediationFunction);
  486. changed = true;
  487. }
  488. if (!Objects.equals(dto.getDefRemediationGapMultiplier(), remediationCoefficient)) {
  489. dto.setDefRemediationGapMultiplier(remediationCoefficient);
  490. changed = true;
  491. }
  492. if (!Objects.equals(dto.getDefRemediationBaseEffort(), remediationOffset)) {
  493. dto.setDefRemediationBaseEffort(remediationOffset);
  494. changed = true;
  495. }
  496. if (!Objects.equals(dto.getGapDescription(), effortToFixDescription)) {
  497. dto.setGapDescription(effortToFixDescription);
  498. changed = true;
  499. }
  500. return changed;
  501. }
  502. private void mergeParams(RegisterRulesContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession session) {
  503. List<RuleParamDto> paramDtos = context.getRuleParametersFor(rule.getUuid());
  504. Map<String, RuleParamDto> existingParamsByName = new HashMap<>();
  505. Profiler profiler = Profiler.create(Loggers.get(getClass()));
  506. for (RuleParamDto paramDto : paramDtos) {
  507. RulesDefinition.Param paramDef = ruleDef.param(paramDto.getName());
  508. if (paramDef == null) {
  509. profiler.start();
  510. dbClient.activeRuleDao().deleteParamsByRuleParam(session, paramDto);
  511. profiler.stopDebug(format("Propagate deleted param with name %s to active rules of rule %s", paramDto.getName(), rule.getKey()));
  512. dbClient.ruleDao().deleteRuleParam(session, paramDto.getUuid());
  513. } else {
  514. if (mergeParam(paramDto, paramDef)) {
  515. dbClient.ruleDao().updateRuleParam(session, rule, paramDto);
  516. }
  517. existingParamsByName.put(paramDto.getName(), paramDto);
  518. }
  519. }
  520. // Create newly parameters
  521. for (RulesDefinition.Param param : ruleDef.params()) {
  522. RuleParamDto paramDto = existingParamsByName.get(param.key());
  523. if (paramDto != null) {
  524. continue;
  525. }
  526. paramDto = RuleParamDto.createFor(rule)
  527. .setName(param.key())
  528. .setDescription(param.description())
  529. .setDefaultValue(param.defaultValue())
  530. .setType(param.type().toString());
  531. dbClient.ruleDao().insertRuleParam(session, rule, paramDto);
  532. if (StringUtils.isEmpty(param.defaultValue())) {
  533. continue;
  534. }
  535. // Propagate the default value to existing active rule parameters
  536. profiler.start();
  537. for (ActiveRuleDto activeRule : dbClient.activeRuleDao().selectByRuleUuid(session, rule.getUuid())) {
  538. ActiveRuleParamDto activeParam = ActiveRuleParamDto.createFor(paramDto).setValue(param.defaultValue());
  539. dbClient.activeRuleDao().insertParam(session, activeRule, activeParam);
  540. }
  541. profiler.stopDebug(format("Propagate new param with name %s to active rules of rule %s", paramDto.getName(), rule.getKey()));
  542. }
  543. }
  544. private static boolean mergeParam(RuleParamDto paramDto, RulesDefinition.Param paramDef) {
  545. boolean changed = false;
  546. if (!Objects.equals(paramDto.getType(), paramDef.type().toString())) {
  547. paramDto.setType(paramDef.type().toString());
  548. changed = true;
  549. }
  550. if (!Objects.equals(paramDto.getDefaultValue(), paramDef.defaultValue())) {
  551. paramDto.setDefaultValue(paramDef.defaultValue());
  552. changed = true;
  553. }
  554. if (!Objects.equals(paramDto.getDescription(), paramDef.description())) {
  555. paramDto.setDescription(paramDef.description());
  556. changed = true;
  557. }
  558. return changed;
  559. }
  560. private void updateDeprecatedKeys(RegisterRulesContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession dbSession) {
  561. Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDefinition = SingleDeprecatedRuleKey.from(ruleDef);
  562. Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDB = context.getDBDeprecatedKeysFor(rule);
  563. // DeprecatedKeys that must be deleted
  564. List<String> uuidsToBeDeleted = difference(deprecatedRuleKeysFromDB, deprecatedRuleKeysFromDefinition).stream()
  565. .map(SingleDeprecatedRuleKey::getUuid)
  566. .collect(toList());
  567. dbClient.ruleDao().deleteDeprecatedRuleKeys(dbSession, uuidsToBeDeleted);
  568. // DeprecatedKeys that must be created
  569. Sets.SetView<SingleDeprecatedRuleKey> deprecatedRuleKeysToBeCreated = difference(deprecatedRuleKeysFromDefinition, deprecatedRuleKeysFromDB);
  570. deprecatedRuleKeysToBeCreated
  571. .forEach(r -> dbClient.ruleDao().insert(dbSession, new DeprecatedRuleKeyDto()
  572. .setUuid(uuidFactory.create())
  573. .setRuleUuid(rule.getUuid())
  574. .setOldRepositoryKey(r.getOldRepositoryKey())
  575. .setOldRuleKey(r.getOldRuleKey())
  576. .setCreatedAt(system2.now())));
  577. }
  578. private static boolean mergeTags(RulesDefinition.Rule ruleDef, RuleDto dto) {
  579. boolean changed = false;
  580. if (RuleStatus.REMOVED == ruleDef.status()) {
  581. dto.setSystemTags(emptySet());
  582. changed = true;
  583. } else if (dto.getSystemTags().size() != ruleDef.tags().size() ||
  584. !dto.getSystemTags().containsAll(ruleDef.tags())) {
  585. dto.setSystemTags(ruleDef.tags());
  586. changed = true;
  587. }
  588. return changed;
  589. }
  590. private static boolean mergeSecurityStandards(RulesDefinition.Rule ruleDef, RuleDto dto) {
  591. boolean changed = false;
  592. if (RuleStatus.REMOVED == ruleDef.status()) {
  593. dto.setSecurityStandards(emptySet());
  594. changed = true;
  595. } else if (dto.getSecurityStandards().size() != ruleDef.securityStandards().size() ||
  596. !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) {
  597. dto.setSecurityStandards(ruleDef.securityStandards());
  598. changed = true;
  599. }
  600. return changed;
  601. }
  602. private void processRemainingDbRules(RegisterRulesContext recorder, DbSession dbSession) {
  603. // custom rules check status of template, so they must be processed at the end
  604. List<RuleDto> customRules = new ArrayList<>();
  605. recorder.getRemaining().forEach(rule -> {
  606. if (rule.isCustomRule()) {
  607. customRules.add(rule);
  608. } else if (!rule.isAdHoc() && rule.getStatus() != RuleStatus.REMOVED) {
  609. removeRule(dbSession, recorder, rule);
  610. }
  611. });
  612. for (RuleDto customRule : customRules) {
  613. String templateUuid = customRule.getTemplateUuid();
  614. checkNotNull(templateUuid, "Template uuid of the custom rule '%s' is null", customRule);
  615. Optional<RuleDto> template = dbClient.ruleDao().selectByUuid(templateUuid, dbSession);
  616. if (template.isPresent() && template.get().getStatus() != RuleStatus.REMOVED) {
  617. if (updateCustomRuleFromTemplateRule(customRule, template.get())) {
  618. recorder.updated(customRule);
  619. update(dbSession, customRule);
  620. }
  621. } else {
  622. removeRule(dbSession, recorder, customRule);
  623. }
  624. }
  625. dbSession.commit();
  626. }
  627. private void removeRule(DbSession session, RegisterRulesContext recorder, RuleDto rule) {
  628. LOG.info(format("Disable rule %s", rule.getKey()));
  629. rule.setStatus(RuleStatus.REMOVED);
  630. rule.setSystemTags(emptySet());
  631. update(session, rule);
  632. // FIXME resetting the tags for all organizations must be handled a different way
  633. // rule.setTags(Collections.emptySet());
  634. // update(session, rule.getMetadata());
  635. recorder.removed(rule);
  636. if (recorder.getRemoved().count() % 100 == 0) {
  637. session.commit();
  638. }
  639. }
  640. private static boolean updateCustomRuleFromTemplateRule(RuleDto customRule, RuleDto templateRule) {
  641. boolean changed = false;
  642. if (!Objects.equals(customRule.getLanguage(), templateRule.getLanguage())) {
  643. customRule.setLanguage(templateRule.getLanguage());
  644. changed = true;
  645. }
  646. if (!Objects.equals(customRule.getConfigKey(), templateRule.getConfigKey())) {
  647. customRule.setConfigKey(templateRule.getConfigKey());
  648. changed = true;
  649. }
  650. if (!Objects.equals(customRule.getPluginKey(), templateRule.getPluginKey())) {
  651. customRule.setPluginKey(templateRule.getPluginKey());
  652. changed = true;
  653. }
  654. if (!Objects.equals(customRule.getDefRemediationFunction(), templateRule.getDefRemediationFunction())) {
  655. customRule.setDefRemediationFunction(templateRule.getDefRemediationFunction());
  656. changed = true;
  657. }
  658. if (!Objects.equals(customRule.getDefRemediationGapMultiplier(), templateRule.getDefRemediationGapMultiplier())) {
  659. customRule.setDefRemediationGapMultiplier(templateRule.getDefRemediationGapMultiplier());
  660. changed = true;
  661. }
  662. if (!Objects.equals(customRule.getDefRemediationBaseEffort(), templateRule.getDefRemediationBaseEffort())) {
  663. customRule.setDefRemediationBaseEffort(templateRule.getDefRemediationBaseEffort());
  664. changed = true;
  665. }
  666. if (!Objects.equals(customRule.getGapDescription(), templateRule.getGapDescription())) {
  667. customRule.setGapDescription(templateRule.getGapDescription());
  668. changed = true;
  669. }
  670. if (customRule.getStatus() != templateRule.getStatus()) {
  671. customRule.setStatus(templateRule.getStatus());
  672. changed = true;
  673. }
  674. if (!Objects.equals(customRule.getSeverityString(), templateRule.getSeverityString())) {
  675. customRule.setSeverity(templateRule.getSeverityString());
  676. changed = true;
  677. }
  678. if (!Objects.equals(customRule.getRepositoryKey(), templateRule.getRepositoryKey())) {
  679. customRule.setRepositoryKey(templateRule.getRepositoryKey());
  680. changed = true;
  681. }
  682. return changed;
  683. }
  684. /**
  685. * SONAR-4642
  686. * <p/>
  687. * Remove active rules on repositories that still exists.
  688. * <p/>
  689. * For instance, if the javascript repository do not provide anymore some rules, active rules related to this rules will be removed.
  690. * But if the javascript repository do not exists anymore, then related active rules will not be removed.
  691. * <p/>
  692. * The side effect of this approach is that extended repositories will not be managed the same way.
  693. * If an extended repository do not exists anymore, then related active rules will be removed.
  694. */
  695. private List<ActiveRuleChange> removeActiveRulesOnStillExistingRepositories(DbSession dbSession, RegisterRulesContext recorder, List<RulesDefinition.Repository> context) {
  696. List<String> repositoryKeys = context.stream()
  697. .map(RulesDefinition.ExtendedRepository::key)
  698. .collect(MoreCollectors.toList(context.size()));
  699. List<ActiveRuleChange> changes = new ArrayList<>();
  700. Profiler profiler = Profiler.create(Loggers.get(getClass()));
  701. recorder.getRemoved().forEach(rule -> {
  702. // SONAR-4642 Remove active rules only when repository still exists
  703. if (repositoryKeys.contains(rule.getRepositoryKey())) {
  704. profiler.start();
  705. changes.addAll(qProfileRules.deleteRule(dbSession, rule));
  706. profiler.stopDebug(format("Remove active rule for rule %s", rule.getKey()));
  707. }
  708. });
  709. return changes;
  710. }
  711. private void update(DbSession session, RuleDto rule) {
  712. rule.setUpdatedAt(system2.now());
  713. dbClient.ruleDao().update(session, rule);
  714. }
  715. private static void verifyRuleKeyConsistency(List<RulesDefinition.Repository> repositories, RegisterRulesContext registerRulesContext) {
  716. List<RulesDefinition.Rule> definedRules = repositories.stream()
  717. .flatMap(r -> r.rules().stream())
  718. .collect(toList());
  719. Set<RuleKey> definedRuleKeys = definedRules.stream()
  720. .map(r -> RuleKey.of(r.repository().key(), r.key()))
  721. .collect(toSet());
  722. List<RuleKey> definedDeprecatedRuleKeys = definedRules.stream()
  723. .flatMap(r -> r.deprecatedRuleKeys().stream())
  724. .collect(toList());
  725. // Find duplicates in declared deprecated rule keys
  726. Set<RuleKey> duplicates = findDuplicates(definedDeprecatedRuleKeys);
  727. checkState(duplicates.isEmpty(), "The following deprecated rule keys are declared at least twice [%s]",
  728. lazyToString(() -> duplicates.stream().map(RuleKey::toString).collect(Collectors.joining(","))));
  729. // Find rule keys that are both deprecated and used
  730. Set<RuleKey> intersection = intersection(new HashSet<>(definedRuleKeys), new HashSet<>(definedDeprecatedRuleKeys)).immutableCopy();
  731. checkState(intersection.isEmpty(), "The following rule keys are declared both as deprecated and used key [%s]",
  732. lazyToString(() -> intersection.stream().map(RuleKey::toString).collect(Collectors.joining(","))));
  733. // Find incorrect usage of deprecated keys
  734. Map<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey = registerRulesContext.getDbDeprecatedKeysByOldRuleKey();
  735. Set<String> incorrectRuleKeyMessage = definedRules.stream()
  736. .flatMap(r -> filterInvalidDeprecatedRuleKeys(dbDeprecatedRuleKeysByOldRuleKey, r))
  737. .filter(Objects::nonNull)
  738. .collect(Collectors.toSet());
  739. checkState(incorrectRuleKeyMessage.isEmpty(), "An incorrect state of deprecated rule keys has been detected.\n %s",
  740. lazyToString(() -> String.join("\n", incorrectRuleKeyMessage)));
  741. }
  742. private static Stream<String> filterInvalidDeprecatedRuleKeys(Map<RuleKey, SingleDeprecatedRuleKey> dbDeprecatedRuleKeysByOldRuleKey, RulesDefinition.Rule rule) {
  743. return rule.deprecatedRuleKeys().stream()
  744. .map(rk -> {
  745. SingleDeprecatedRuleKey singleDeprecatedRuleKey = dbDeprecatedRuleKeysByOldRuleKey.get(rk);
  746. if (singleDeprecatedRuleKey == null) {
  747. // new deprecated rule key : OK
  748. return null;
  749. }
  750. RuleKey parentRuleKey = RuleKey.of(rule.repository().key(), rule.key());
  751. if (parentRuleKey.equals(singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey())) {
  752. // same parent : OK
  753. return null;
  754. }
  755. if (rule.deprecatedRuleKeys().contains(singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey())) {
  756. // the new rule is deprecating the old parentRuleKey : OK
  757. return null;
  758. }
  759. return format("The deprecated rule key [%s] was previously deprecated by [%s]. [%s] should be a deprecated key of [%s],",
  760. rk.toString(),
  761. singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey().toString(),
  762. singleDeprecatedRuleKey.getNewRuleKeyAsRuleKey().toString(),
  763. RuleKey.of(rule.repository().key(), rule.key()).toString());
  764. });
  765. }
  766. private static Object lazyToString(Supplier<String> toString) {
  767. return new Object() {
  768. @Override
  769. public String toString() {
  770. return toString.get();
  771. }
  772. };
  773. }
  774. private static <T> Set<T> findDuplicates(Collection<T> list) {
  775. Set<T> duplicates = new HashSet<>();
  776. Set<T> uniques = new HashSet<>();
  777. list.forEach(t -> {
  778. if (!uniques.add(t)) {
  779. duplicates.add(t);
  780. }
  781. });
  782. return duplicates;
  783. }
  784. }