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 34KB

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