3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.qualityprofile;
22 import java.io.Reader;
23 import java.io.Writer;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.List;
28 import java.util.Objects;
29 import java.util.function.Function;
30 import java.util.stream.Collectors;
31 import javax.annotation.Nullable;
32 import org.apache.commons.lang.builder.CompareToBuilder;
33 import org.sonar.api.rule.RuleKey;
34 import org.sonar.api.rule.RuleStatus;
35 import org.sonar.api.rules.RuleType;
36 import org.sonar.api.server.ServerSide;
37 import org.sonar.core.util.stream.MoreCollectors;
38 import org.sonar.db.DbClient;
39 import org.sonar.db.DbSession;
40 import org.sonar.db.organization.OrganizationDto;
41 import org.sonar.db.qualityprofile.ExportRuleDto;
42 import org.sonar.db.qualityprofile.QProfileDto;
43 import org.sonar.db.rule.RuleDefinitionDto;
44 import org.sonar.server.rule.NewCustomRule;
45 import org.sonar.server.rule.RuleCreator;
47 import static com.google.common.base.Preconditions.checkArgument;
50 public class QProfileBackuperImpl implements QProfileBackuper {
52 private final DbClient db;
53 private final QProfileReset profileReset;
54 private final QProfileFactory profileFactory;
55 private final RuleCreator ruleCreator;
56 private final QProfileParser qProfileParser;
58 public QProfileBackuperImpl(DbClient db, QProfileReset profileReset, QProfileFactory profileFactory,
59 RuleCreator ruleCreator, QProfileParser qProfileParser) {
61 this.profileReset = profileReset;
62 this.profileFactory = profileFactory;
63 this.ruleCreator = ruleCreator;
64 this.qProfileParser = qProfileParser;
68 public void backup(DbSession dbSession, QProfileDto profile, Writer writer) {
69 List<ExportRuleDto> rulesToExport = db.qualityProfileExportDao().selectRulesByProfile(dbSession, profile);
70 rulesToExport.sort(BackupActiveRuleComparator.INSTANCE);
71 qProfileParser.writeXml(writer, profile, rulesToExport.iterator());
75 public QProfileRestoreSummary restore(DbSession dbSession, Reader backup, OrganizationDto organization, @Nullable String overriddenProfileName) {
76 return restore(dbSession, backup, nameInBackup -> {
77 QProfileName targetName = nameInBackup;
78 if (overriddenProfileName != null) {
79 targetName = new QProfileName(nameInBackup.getLanguage(), overriddenProfileName);
81 return profileFactory.getOrCreateCustom(dbSession, organization, targetName);
86 public QProfileRestoreSummary restore(DbSession dbSession, Reader backup, QProfileDto profile) {
87 return restore(dbSession, backup, nameInBackup -> {
88 checkArgument(profile.getLanguage().equals(nameInBackup.getLanguage()),
89 "Can't restore %s backup on %s profile with key [%s]. Languages are different.", nameInBackup.getLanguage(), profile.getLanguage(), profile.getKee());
94 private QProfileRestoreSummary restore(DbSession dbSession, Reader backup, Function<QProfileName, QProfileDto> profileLoader) {
95 ImportedQProfile qProfile = qProfileParser.readXml(backup);
97 QProfileName targetName = new QProfileName(qProfile.getProfileLang(), qProfile.getProfileName());
98 QProfileDto targetProfile = profileLoader.apply(targetName);
100 List<ImportedRule> importedRules = qProfile.getRules();
102 Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey = getImportedRulesDefinitions(dbSession, importedRules);
103 checkIfRulesFromExternalEngines(ruleDefinitionsByKey);
105 Map<RuleKey, RuleDefinitionDto> customRulesDefinitions = createCustomRulesIfNotExist(dbSession, importedRules, ruleDefinitionsByKey);
106 ruleDefinitionsByKey.putAll(customRulesDefinitions);
108 List<RuleActivation> ruleActivations = toRuleActivations(importedRules, ruleDefinitionsByKey);
110 BulkChangeResult changes = profileReset.reset(dbSession, targetProfile, ruleActivations);
111 return new QProfileRestoreSummary(targetProfile, changes);
114 private Map<RuleKey, RuleDefinitionDto> getImportedRulesDefinitions(DbSession dbSession, List<ImportedRule> rules) {
115 List<RuleKey> ruleKeys = rules.stream()
116 .map(ImportedRule::getRuleKey)
117 .collect(MoreCollectors.toList());
118 return db.ruleDao().selectDefinitionByKeys(dbSession, ruleKeys)
120 .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity()));
123 private static void checkIfRulesFromExternalEngines(Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey) {
124 List<RuleDefinitionDto> externalRules = ruleDefinitionsByKey.values().stream()
125 .filter(RuleDefinitionDto::isExternal)
126 .collect(Collectors.toList());
128 if (!externalRules.isEmpty()) {
129 throw new IllegalArgumentException("The quality profile cannot be restored as it contains rules from external rule engines: "
130 + externalRules.stream().map(r -> r.getKey().toString()).collect(Collectors.joining(", ")));
134 private Map<RuleKey, RuleDefinitionDto> createCustomRulesIfNotExist(DbSession dbSession, List<ImportedRule> rules, Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey) {
135 List<NewCustomRule> customRulesToCreate = rules.stream()
136 .filter(r -> ruleDefinitionsByKey.get(r.getRuleKey()) == null && r.isCustomRule())
137 .map(QProfileBackuperImpl::importedRuleToNewCustomRule)
138 .collect(Collectors.toList());
140 if (!customRulesToCreate.isEmpty()) {
141 return db.ruleDao().selectDefinitionByKeys(dbSession, ruleCreator.create(dbSession, customRulesToCreate))
143 .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity()));
145 return Collections.emptyMap();
148 private static NewCustomRule importedRuleToNewCustomRule(ImportedRule r) {
149 return NewCustomRule.createForCustomRule(r.getRuleKey().rule(), r.getTemplateKey())
150 .setName(r.getName())
151 .setMarkdownDescription(r.getDescription())
152 .setSeverity(r.getSeverity())
153 .setStatus(RuleStatus.READY)
154 .setPreventReactivation(true)
155 .setType(RuleType.valueOf(r.getType()))
156 .setParameters(r.getParameters());
159 private static List<RuleActivation> toRuleActivations(List<ImportedRule> rules, Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey) {
160 return rules.stream()
162 RuleDefinitionDto ruleDefinition = ruleDefinitionsByKey.get(r.getRuleKey());
163 if (ruleDefinition == null) {
166 return RuleActivation.create(ruleDefinition.getId(), r.getSeverity(), r.getParameters());
168 .filter(Objects::nonNull)
169 .collect(MoreCollectors.toList(rules.size()));
172 private enum BackupActiveRuleComparator implements Comparator<ExportRuleDto> {
176 public int compare(ExportRuleDto o1, ExportRuleDto o2) {
177 return new CompareToBuilder()
178 .append(o1.getRuleKey().repository(), o2.getRuleKey().repository())
179 .append(o1.getRuleKey().rule(), o2.getRuleKey().rule())