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.

RuleCreator.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 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.base.Splitter;
  22. import com.google.common.base.Strings;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Optional;
  27. import java.util.Set;
  28. import java.util.function.Function;
  29. import java.util.stream.Collectors;
  30. import javax.annotation.CheckForNull;
  31. import javax.annotation.Nullable;
  32. import org.sonar.api.rule.RuleKey;
  33. import org.sonar.api.rule.RuleStatus;
  34. import org.sonar.api.rule.Severity;
  35. import org.sonar.api.server.ServerSide;
  36. import org.sonar.api.server.rule.RuleParamType;
  37. import org.sonar.api.utils.System2;
  38. import org.sonar.db.DbClient;
  39. import org.sonar.db.DbSession;
  40. import org.sonar.db.organization.OrganizationDto;
  41. import org.sonar.db.rule.RuleDefinitionDto;
  42. import org.sonar.db.rule.RuleDto;
  43. import org.sonar.db.rule.RuleDto.Format;
  44. import org.sonar.db.rule.RuleMetadataDto;
  45. import org.sonar.db.rule.RuleParamDto;
  46. import org.sonar.server.exceptions.BadRequestException;
  47. import org.sonar.server.organization.DefaultOrganizationProvider;
  48. import org.sonar.server.rule.index.RuleIndexer;
  49. import org.sonar.server.util.TypeValidations;
  50. import static com.google.common.base.Preconditions.checkArgument;
  51. import static com.google.common.collect.Lists.newArrayList;
  52. import static java.lang.String.format;
  53. import static org.sonar.server.exceptions.BadRequestException.checkRequest;
  54. @ServerSide
  55. public class RuleCreator {
  56. private static final String TEMPLATE_KEY_NOT_EXIST_FORMAT = "The template key doesn't exist: %s";
  57. private final System2 system2;
  58. private final RuleIndexer ruleIndexer;
  59. private final DbClient dbClient;
  60. private final TypeValidations typeValidations;
  61. private final DefaultOrganizationProvider defaultOrganizationProvider;
  62. public RuleCreator(System2 system2, RuleIndexer ruleIndexer, DbClient dbClient, TypeValidations typeValidations, DefaultOrganizationProvider defaultOrganizationProvider) {
  63. this.system2 = system2;
  64. this.ruleIndexer = ruleIndexer;
  65. this.dbClient = dbClient;
  66. this.typeValidations = typeValidations;
  67. this.defaultOrganizationProvider = defaultOrganizationProvider;
  68. }
  69. public RuleKey create(DbSession dbSession, NewCustomRule newRule) {
  70. RuleKey templateKey = newRule.templateKey();
  71. checkArgument(templateKey != null, "Rule template key should not be null");
  72. String defaultOrganizationUuid = defaultOrganizationProvider.get().getUuid();
  73. OrganizationDto defaultOrganization = dbClient.organizationDao().selectByUuid(dbSession, defaultOrganizationUuid)
  74. .orElseThrow(() -> new IllegalStateException(format("Could not find default organization for uuid '%s'", defaultOrganizationUuid)));
  75. RuleDto templateRule = dbClient.ruleDao().selectByKey(dbSession, defaultOrganization.getUuid(), templateKey)
  76. .orElseThrow(() -> new IllegalArgumentException(format(TEMPLATE_KEY_NOT_EXIST_FORMAT, templateKey)));
  77. checkArgument(templateRule.isTemplate(), "This rule is not a template rule: %s", templateKey.toString());
  78. checkArgument(templateRule.getStatus() != RuleStatus.REMOVED, TEMPLATE_KEY_NOT_EXIST_FORMAT, templateKey.toString());
  79. validateCustomRule(newRule, dbSession, templateKey);
  80. RuleKey customRuleKey = RuleKey.of(templateRule.getRepositoryKey(), newRule.ruleKey());
  81. Optional<RuleDefinitionDto> definition = loadRule(dbSession, customRuleKey);
  82. int customRuleId = definition.map(d -> updateExistingRule(d, newRule, dbSession))
  83. .orElseGet(() -> createCustomRule(customRuleKey, newRule, templateRule, dbSession));
  84. ruleIndexer.commitAndIndex(dbSession, customRuleId);
  85. return customRuleKey;
  86. }
  87. public List<RuleKey> create(DbSession dbSession, List<NewCustomRule> newRules) {
  88. String defaultOrganizationUuid = defaultOrganizationProvider.get().getUuid();
  89. OrganizationDto defaultOrganization = dbClient.organizationDao().selectByUuid(dbSession, defaultOrganizationUuid)
  90. .orElseThrow(() -> new IllegalStateException(format("Could not find default organization for uuid '%s'", defaultOrganizationUuid)));
  91. Set<RuleKey> templateKeys = newRules.stream().map(NewCustomRule::templateKey).collect(Collectors.toSet());
  92. Map<RuleKey, RuleDto> templateRules = dbClient.ruleDao().selectByKeys(dbSession, defaultOrganization.getUuid(), templateKeys)
  93. .stream()
  94. .collect(Collectors.toMap(
  95. RuleDto::getKey,
  96. Function.identity())
  97. );
  98. checkArgument(!templateRules.isEmpty() && templateKeys.size() == templateRules.size(), "Rule template keys should exists for each custom rule!");
  99. templateRules.values().forEach(ruleDto -> {
  100. checkArgument(ruleDto.isTemplate(), "This rule is not a template rule: %s", ruleDto.getKey().toString());
  101. checkArgument(ruleDto.getStatus() != RuleStatus.REMOVED, TEMPLATE_KEY_NOT_EXIST_FORMAT, ruleDto.getKey().toString());
  102. }
  103. );
  104. List<Integer> customRuleIds = newRules.stream()
  105. .map(newCustomRule -> {
  106. RuleDto templateRule = templateRules.get(newCustomRule.templateKey());
  107. validateCustomRule(newCustomRule, dbSession, templateRule.getKey());
  108. RuleKey customRuleKey = RuleKey.of(templateRule.getRepositoryKey(), newCustomRule.ruleKey());
  109. return createCustomRule(customRuleKey, newCustomRule, templateRule, dbSession);
  110. }
  111. )
  112. .collect(Collectors.toList());
  113. ruleIndexer.commitAndIndex(dbSession, customRuleIds);
  114. return newRules.stream()
  115. .map(newCustomRule -> {
  116. RuleDto templateRule = templateRules.get(newCustomRule.templateKey());
  117. return RuleKey.of(templateRule.getRepositoryKey(), newCustomRule.ruleKey());
  118. }
  119. )
  120. .collect(Collectors.toList());
  121. }
  122. private void validateCustomRule(NewCustomRule newRule, DbSession dbSession, RuleKey templateKey) {
  123. List<String> errors = new ArrayList<>();
  124. validateRuleKey(errors, newRule.ruleKey());
  125. validateName(errors, newRule);
  126. validateDescription(errors, newRule);
  127. String severity = newRule.severity();
  128. if (Strings.isNullOrEmpty(severity)) {
  129. errors.add("The severity is missing");
  130. } else if (!Severity.ALL.contains(severity)) {
  131. errors.add(format("Severity \"%s\" is invalid", severity));
  132. }
  133. if (newRule.status() == null) {
  134. errors.add("The status is missing");
  135. }
  136. for (RuleParamDto ruleParam : dbClient.ruleDao().selectRuleParamsByRuleKey(dbSession, templateKey)) {
  137. try {
  138. validateParam(ruleParam, newRule.parameter(ruleParam.getName()));
  139. } catch (BadRequestException validationError) {
  140. errors.addAll(validationError.errors());
  141. }
  142. }
  143. checkRequest(errors.isEmpty(), errors);
  144. }
  145. @CheckForNull
  146. private void validateParam(RuleParamDto ruleParam, @Nullable String value) {
  147. if (value != null) {
  148. RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType());
  149. if (ruleParamType.multiple()) {
  150. List<String> values = newArrayList(Splitter.on(",").split(value));
  151. typeValidations.validate(values, ruleParamType.type(), ruleParamType.values());
  152. } else {
  153. typeValidations.validate(value, ruleParamType.type(), ruleParamType.values());
  154. }
  155. }
  156. }
  157. private static void validateName(List<String> errors, NewCustomRule newRule) {
  158. if (Strings.isNullOrEmpty(newRule.name())) {
  159. errors.add("The name is missing");
  160. }
  161. }
  162. private static void validateDescription(List<String> errors, NewCustomRule newRule) {
  163. if (Strings.isNullOrEmpty(newRule.htmlDescription()) && Strings.isNullOrEmpty(newRule.markdownDescription())) {
  164. errors.add("The description is missing");
  165. }
  166. }
  167. private static void validateRuleKey(List<String> errors, String ruleKey) {
  168. if (!ruleKey.matches("^[\\w]+$")) {
  169. errors.add(format("The rule key \"%s\" is invalid, it should only contain: a-z, 0-9, \"_\"", ruleKey));
  170. }
  171. }
  172. private Optional<RuleDefinitionDto> loadRule(DbSession dbSession, RuleKey ruleKey) {
  173. return dbClient.ruleDao().selectDefinitionByKey(dbSession, ruleKey);
  174. }
  175. private int createCustomRule(RuleKey ruleKey, NewCustomRule newRule, RuleDto templateRuleDto, DbSession dbSession) {
  176. RuleDefinitionDto ruleDefinition = new RuleDefinitionDto()
  177. .setRuleKey(ruleKey)
  178. .setPluginKey(templateRuleDto.getPluginKey())
  179. .setTemplateId(templateRuleDto.getId())
  180. .setConfigKey(templateRuleDto.getConfigKey())
  181. .setName(newRule.name())
  182. .setDescription(newRule.markdownDescription())
  183. .setDescriptionFormat(Format.MARKDOWN)
  184. .setSeverity(newRule.severity())
  185. .setStatus(newRule.status())
  186. .setType(newRule.type() == null ? templateRuleDto.getType() : newRule.type().getDbConstant())
  187. .setLanguage(templateRuleDto.getLanguage())
  188. .setDefRemediationFunction(templateRuleDto.getDefRemediationFunction())
  189. .setDefRemediationGapMultiplier(templateRuleDto.getDefRemediationGapMultiplier())
  190. .setDefRemediationBaseEffort(templateRuleDto.getDefRemediationBaseEffort())
  191. .setGapDescription(templateRuleDto.getGapDescription())
  192. .setScope(templateRuleDto.getScope())
  193. .setSystemTags(templateRuleDto.getSystemTags())
  194. .setSecurityStandards(templateRuleDto.getSecurityStandards())
  195. .setIsExternal(false)
  196. .setIsAdHoc(false)
  197. .setCreatedAt(system2.now())
  198. .setUpdatedAt(system2.now());
  199. dbClient.ruleDao().insert(dbSession, ruleDefinition);
  200. Set<String> tags = templateRuleDto.getTags();
  201. if (!tags.isEmpty()) {
  202. RuleMetadataDto ruleMetadata = new RuleMetadataDto()
  203. .setOrganizationUuid(defaultOrganizationProvider.get().getUuid())
  204. .setRuleId(ruleDefinition.getId())
  205. .setTags(tags)
  206. .setCreatedAt(system2.now())
  207. .setUpdatedAt(system2.now());
  208. dbClient.ruleDao().insertOrUpdate(dbSession, ruleMetadata);
  209. }
  210. for (RuleParamDto templateRuleParamDto : dbClient.ruleDao().selectRuleParamsByRuleKey(dbSession, templateRuleDto.getKey())) {
  211. String customRuleParamValue = Strings.emptyToNull(newRule.parameter(templateRuleParamDto.getName()));
  212. createCustomRuleParams(customRuleParamValue, ruleDefinition, templateRuleParamDto, dbSession);
  213. }
  214. return ruleDefinition.getId();
  215. }
  216. private void createCustomRuleParams(@Nullable String paramValue, RuleDefinitionDto ruleDto, RuleParamDto templateRuleParam, DbSession dbSession) {
  217. RuleParamDto ruleParamDto = RuleParamDto.createFor(ruleDto)
  218. .setName(templateRuleParam.getName())
  219. .setType(templateRuleParam.getType())
  220. .setDescription(templateRuleParam.getDescription())
  221. .setDefaultValue(paramValue);
  222. dbClient.ruleDao().insertRuleParam(dbSession, ruleDto, ruleParamDto);
  223. }
  224. private int updateExistingRule(RuleDefinitionDto ruleDto, NewCustomRule newRule, DbSession dbSession) {
  225. if (ruleDto.getStatus().equals(RuleStatus.REMOVED)) {
  226. if (newRule.isPreventReactivation()) {
  227. throw new ReactivationException(format("A removed rule with the key '%s' already exists", ruleDto.getKey().rule()), ruleDto.getKey());
  228. } else {
  229. ruleDto.setStatus(RuleStatus.READY)
  230. .setUpdatedAt(system2.now());
  231. dbClient.ruleDao().update(dbSession, ruleDto);
  232. }
  233. } else {
  234. throw new IllegalArgumentException(format("A rule with the key '%s' already exists", ruleDto.getKey().rule()));
  235. }
  236. return ruleDto.getId();
  237. }
  238. }