aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java92
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleParamDto.java38
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportDao.java56
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportMapper.java30
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml47
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java2
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java227
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedQProfile.java47
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java101
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java249
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java216
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java43
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java121
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/BackupActionTest.java3
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java49
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java8
20 files changed, 1153 insertions, 191 deletions
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
index 4de5acb9b21..6b8ee0ac168 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
@@ -128,7 +128,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
+ 26 // level 1
- + 61 // content of DaoModule
+ + 62 // content of DaoModule
+ 3 // content of EsModule
+ 52 // content of CorePropertyDefinitions
+ 1 // StopFlagContainer
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
index 3f833346883..93a5d217467 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
@@ -71,6 +71,7 @@ import org.sonar.db.qualityprofile.QProfileChangeDao;
import org.sonar.db.qualityprofile.QProfileEditGroupsDao;
import org.sonar.db.qualityprofile.QProfileEditUsersDao;
import org.sonar.db.qualityprofile.QualityProfileDao;
+import org.sonar.db.qualityprofile.QualityProfileExportDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
import org.sonar.db.schemamigration.SchemaMigrationDao;
@@ -139,6 +140,7 @@ public class DaoModule extends Module {
QualityGateConditionDao.class,
QualityGateDao.class,
QualityProfileDao.class,
+ QualityProfileExportDao.class,
RoleDao.class,
RuleDao.class,
RuleRepositoryDao.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
index eb220ed54c0..27f1832a2bb 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
@@ -69,6 +69,7 @@ import org.sonar.db.qualityprofile.QProfileChangeDao;
import org.sonar.db.qualityprofile.QProfileEditGroupsDao;
import org.sonar.db.qualityprofile.QProfileEditUsersDao;
import org.sonar.db.qualityprofile.QualityProfileDao;
+import org.sonar.db.qualityprofile.QualityProfileExportDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
import org.sonar.db.schemamigration.SchemaMigrationDao;
@@ -94,6 +95,7 @@ public class DbClient {
private final OrganizationDao organizationDao;
private final OrganizationMemberDao organizationMemberDao;
private final QualityProfileDao qualityProfileDao;
+ private final QualityProfileExportDao qualityProfileExportDao;
private final PropertiesDao propertiesDao;
private final AlmAppInstallDao almAppInstallDao;
private final ProjectAlmBindingDao projectAlmBindingDao;
@@ -167,6 +169,7 @@ public class DbClient {
organizationDao = getDao(map, OrganizationDao.class);
organizationMemberDao = getDao(map, OrganizationMemberDao.class);
qualityProfileDao = getDao(map, QualityProfileDao.class);
+ qualityProfileExportDao = getDao(map, QualityProfileExportDao.class);
propertiesDao = getDao(map, PropertiesDao.class);
internalPropertiesDao = getDao(map, InternalPropertiesDao.class);
snapshotDao = getDao(map, SnapshotDao.class);
@@ -267,6 +270,10 @@ public class DbClient {
return qualityProfileDao;
}
+ public QualityProfileExportDao qualityProfileExportDao() {
+ return qualityProfileExportDao;
+ }
+
public PropertiesDao propertiesDao() {
return propertiesDao;
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
index f449a0eeabf..391ee1c0933 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
@@ -48,10 +48,10 @@ import org.sonar.db.ce.CeTaskMessageMapper;
import org.sonar.db.component.AnalysisPropertiesMapper;
import org.sonar.db.component.BranchMapper;
import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentWithModuleUuidDto;
import org.sonar.db.component.ComponentDtoWithSnapshotId;
import org.sonar.db.component.ComponentKeyUpdaterMapper;
import org.sonar.db.component.ComponentMapper;
+import org.sonar.db.component.ComponentWithModuleUuidDto;
import org.sonar.db.component.FilePathWithHashDto;
import org.sonar.db.component.KeyWithUuidDto;
import org.sonar.db.component.ProjectLinkMapper;
@@ -121,6 +121,7 @@ import org.sonar.db.qualityprofile.DefaultQProfileMapper;
import org.sonar.db.qualityprofile.QProfileChangeMapper;
import org.sonar.db.qualityprofile.QProfileEditGroupsMapper;
import org.sonar.db.qualityprofile.QProfileEditUsersMapper;
+import org.sonar.db.qualityprofile.QualityProfileExportMapper;
import org.sonar.db.qualityprofile.QualityProfileMapper;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleMapper;
@@ -270,6 +271,7 @@ public class MyBatis implements Startable {
QualityGateConditionMapper.class,
QualityGateMapper.class,
QualityProfileMapper.class,
+ QualityProfileExportMapper.class,
RoleMapper.class,
RuleMapper.class,
RuleRepositoryMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java
new file mode 100644
index 00000000000..9e0c123cef5
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java
@@ -0,0 +1,92 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+import org.sonar.db.rule.SeverityUtil;
+
+public class ExportRuleDto {
+ private Integer activeRuleId;
+ private String repository;
+ private String rule;
+ private String name;
+ private String description;
+ private String extendedDescription;
+ private String template;
+ private Integer severity;
+ private Integer type;
+ private String tags;
+
+ private List<ExportRuleParamDto> params;
+
+ public boolean isCustomRule() {
+ return template != null;
+ }
+
+ public Integer getActiveRuleId() {
+ return activeRuleId;
+ }
+
+ public RuleKey getRuleKey() {
+ return RuleKey.of(repository, rule);
+ }
+
+ public RuleKey getTemplateRuleKey() {
+ return RuleKey.of(repository, template);
+ }
+
+ public String getSeverityString() {
+ return SeverityUtil.getSeverityFromOrdinal(severity);
+ }
+
+ public String getExtendedDescription() {
+ return extendedDescription;
+ }
+
+ public RuleType getRuleType() {
+ return RuleType.valueOf(type);
+ }
+
+ public String getTags() {
+ return tags;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<ExportRuleParamDto> getParams() {
+ if (params == null) {
+ params = new LinkedList<>();
+ }
+ return params;
+ }
+
+ void setParams(List<ExportRuleParamDto> params) {
+ this.params = params;
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleParamDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleParamDto.java
new file mode 100644
index 00000000000..1222480dd5a
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleParamDto.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+public class ExportRuleParamDto {
+ private Integer activeRuleId;
+ private String kee;
+ private String value;
+
+ public Integer getActiveRuleId() {
+ return activeRuleId;
+ }
+
+ public String getKey() {
+ return kee;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportDao.java
new file mode 100644
index 00000000000..5f2ff75ff8f
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportDao.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+import static org.sonar.db.DatabaseUtils.executeLargeInputs;
+
+public class QualityProfileExportDao implements Dao {
+
+ public List<ExportRuleDto> selectRulesByProfile(DbSession dbSession, QProfileDto profile) {
+ List<ExportRuleDto> exportRules = mapper(dbSession).selectByProfileUuid(profile.getKee());
+
+ Set<Integer> activeRuleIds = exportRules.stream().map(ExportRuleDto::getActiveRuleId).collect(Collectors.toSet());
+
+ Map<Integer, List<ExportRuleParamDto>> activeRulesParam = selectParamsByActiveRuleIds(dbSession, activeRuleIds);
+
+ exportRules.forEach(exportRuleDto ->
+ exportRuleDto.setParams(activeRulesParam.get(exportRuleDto.getActiveRuleId()))
+ );
+ return exportRules;
+ }
+
+ private Map<Integer, List<ExportRuleParamDto>> selectParamsByActiveRuleIds(DbSession dbSession, Collection<Integer> activeRuleIds) {
+ return executeLargeInputs(activeRuleIds, ids -> mapper(dbSession).selectParamsByActiveRuleIds(ids))
+ .stream()
+ .collect(Collectors.groupingBy(ExportRuleParamDto::getActiveRuleId));
+ }
+
+ private static QualityProfileExportMapper mapper(DbSession dbSession) {
+ return dbSession.getMapper(QualityProfileExportMapper.class);
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportMapper.java
new file mode 100644
index 00000000000..3a2bb304b07
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileExportMapper.java
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface QualityProfileExportMapper {
+ List<ExportRuleDto> selectByProfileUuid(String uuid);
+
+ List<ExportRuleParamDto> selectParamsByActiveRuleIds(@Param("activeRuleIds") Collection<Integer> activeRuleIds);
+}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml
new file mode 100644
index 00000000000..ec8fe7a81e3
--- /dev/null
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.qualityprofile.QualityProfileExportMapper">
+
+ <sql id="exportRuleColumns">
+ a.id as "activeRuleId",
+ a.failure_level as "severity",
+ r.plugin_rule_key as "rule",
+ r.plugin_name as "repository",
+ r.priority as "defaultSeverity",
+ r.name,
+ r.description,
+ r.description_format as "descriptionFormat",
+ r.rule_type as "type",
+ rt.plugin_rule_key as "template",
+ rm.note_data as "extendedDescription",
+ rm.tags
+ </sql>
+
+ <sql id="exportRuleParamColumns">
+ p.active_rule_id as activeRuleId,
+ p.rules_parameter_key as kee,
+ p.value as value
+ </sql>
+
+ <select id="selectByProfileUuid" parameterType="string" resultType="org.sonar.db.qualityprofile.ExportRuleDto">
+ select
+ <include refid="exportRuleColumns"/>
+ from active_rules a
+ inner join rules_profiles rp on rp.id = a.profile_id
+ inner join org_qprofiles oqp on oqp.rules_profile_uuid = rp.kee
+ inner join rules r on r.id = a.rule_id and r.status != 'REMOVED'
+ left join rules rt on rt.id = r.template_id
+ left join rules_metadata rm on rm.rule_id = r.id
+ where oqp.uuid = #{id, jdbcType=VARCHAR}
+ </select>
+
+ <select id="selectParamsByActiveRuleIds" parameterType="map" resultType="org.sonar.db.qualityprofile.ExportRuleParamDto">
+ select
+ <include refid="exportRuleParamColumns"/>
+ from active_rule_parameters p
+ <where>
+ p.active_rule_id in <foreach collection="activeRuleIds" open="(" close=")" item="activeRuleId" separator=",">#{activeRuleId,jdbcType=INTEGER}</foreach>
+ </where>
+ </select>
+</mapper>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
index 184f56ad80d..cb8424da879 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
@@ -30,6 +30,6 @@ public class DaoModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new DaoModule().configure(container);
- assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 61);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 62);
}
}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java
new file mode 100644
index 00000000000..7b33fb41526
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java
@@ -0,0 +1,227 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleMetadataDto;
+import org.sonar.db.rule.RuleParamDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QualityProfileExportDaoTest {
+
+ @Rule
+ public DbTester db = DbTester.create(AlwaysIncreasingSystem2.INSTANCE);
+
+ private DbSession dbSession = db.getSession();
+ private QualityProfileExportDao underTest = db.getDbClient().qualityProfileExportDao();
+
+ @Test
+ public void selectRulesByProfile_ready_rules_only() {
+ String language = "java";
+ RuleDefinitionDto rule1 = createRule(language);
+ RuleDefinitionDto rule2 = createRule(language);
+ RuleDefinitionDto rule3 = createRule(language);
+ createRule(language, RuleStatus.REMOVED);
+
+ QProfileDto profile = createProfile(language);
+
+ activate(profile, rule1, rule2, rule3);
+
+ List<ExportRuleDto> results = underTest.selectRulesByProfile(dbSession, profile);
+ assertThat(results).isNotNull();
+ assertThat(results).asList()
+ .extracting("ruleKey")
+ .containsOnly(rule1.getKey(), rule2.getKey(), rule3.getKey());
+ }
+
+ @Test
+ public void selectRulesByProfile_verify_columns() {
+ String language = "java";
+ RuleDefinitionDto ruleTemplate = createRule(language);
+ RuleDefinitionDto rule = createRule(language, RuleStatus.READY, ruleTemplate.getId());
+ RuleMetadataDto ruleMetadata = createRuleMetadata(new RuleMetadataDto()
+ .setRuleId(rule.getId())
+ .setOrganizationUuid(db.getDefaultOrganization().getUuid())
+ .setNoteData("Extended description")
+ .setTags(Sets.newHashSet("tag1", "tag2", "tag3")));
+
+ QProfileDto profile = createProfile(language);
+
+ List<ActiveRuleDto> activeRules = activate(profile, rule);
+
+ List<ExportRuleDto> results = underTest.selectRulesByProfile(dbSession, profile);
+ assertThat(results).isNotNull();
+ assertThat(results).asList().isNotEmpty();
+
+ ExportRuleDto exportRuleDto = results.get(0);
+ assertThat(exportRuleDto).isNotNull();
+ assertThat(exportRuleDto.getParams()).asList().isEmpty();
+ assertThat(exportRuleDto.getDescription()).isEqualTo(rule.getDescription());
+ assertThat(exportRuleDto.getExtendedDescription()).isEqualTo(ruleMetadata.getNoteData());
+ assertThat(exportRuleDto.getName()).isEqualTo(rule.getName());
+ assertThat(exportRuleDto.getRuleKey()).isEqualTo(rule.getKey());
+ assertThat(exportRuleDto.getRuleType()).isEqualTo(RuleType.valueOf(rule.getType()));
+ assertThat(exportRuleDto.getSeverityString()).isEqualTo(activeRules.get(0).getSeverityString());
+ assertThat(exportRuleDto.getTags()).isEqualTo(String.join(",", ruleMetadata.getTags()));
+ assertThat(exportRuleDto.getTemplateRuleKey()).isEqualTo(ruleTemplate.getKey());
+ }
+
+ @Test
+ public void selectRulesByProfile_verify_rows_over_1000() {
+ String language = "java";
+ int numberOfParamsToCreate = 1005;
+ RuleDefinitionDto rule = createRule(language);
+ List<RuleParamDto> ruleParams = addParamsToRule(rule, numberOfParamsToCreate);
+
+ QProfileDto profile = createProfile(language);
+ ActiveRuleDto activatedRule = activate(profile, rule, ruleParams);
+
+ List<ExportRuleDto> results = underTest.selectRulesByProfile(dbSession, profile);
+
+ assertThat(results)
+ .extracting("activeRuleId")
+ .containsOnly(activatedRule.getId());
+
+ assertThat(results.get(0).getParams())
+ .extracting("key")
+ .containsOnly(ruleParams.stream().map(RuleParamDto::getName).toArray());
+ }
+
+ @Test
+ public void selectRulesByProfile_params_assigned_correctly() {
+ String language = "java";
+ RuleDefinitionDto firstRule = createRule(language);
+ List<RuleParamDto> ruleParamsOfFirstRule = addParamsToRule(firstRule, 2);
+
+ RuleDefinitionDto secondRule = createRule(language);
+ List<RuleParamDto> ruleParamsOfSecondRule = addParamsToRule(secondRule, 3);
+
+ String otherLanguage = "js";
+ RuleDefinitionDto thirdRule = createRule(otherLanguage);
+ List<RuleParamDto> ruleParamsOfThirdRule = addParamsToRule(thirdRule, 4);
+
+ QProfileDto profile = createProfile(language);
+ QProfileDto otherProfile = createProfile(otherLanguage);
+
+ ActiveRuleDto firstActivatedRule = activate(profile, firstRule, ruleParamsOfFirstRule);
+ ActiveRuleDto secondActivatedRule = activate(profile, secondRule, ruleParamsOfSecondRule);
+
+ ActiveRuleDto thirdActivatedRule = activate(otherProfile, thirdRule, ruleParamsOfThirdRule);
+
+ List<ExportRuleDto> results = underTest.selectRulesByProfile(dbSession, profile);
+ List<ExportRuleDto> otherProfileResults = underTest.selectRulesByProfile(dbSession, otherProfile);
+
+ assertThat(results)
+ .extracting("activeRuleId")
+ .containsOnly(firstActivatedRule.getId(), secondActivatedRule.getId());
+
+ assertThat(otherProfileResults).asList()
+ .extracting("activeRuleId")
+ .containsOnly(thirdActivatedRule.getId());
+
+ ExportRuleDto firstExportedRule = findExportedRuleById(firstActivatedRule.getId(), results);
+ assertThat(firstExportedRule.getParams())
+ .extracting("key")
+ .containsOnly(ruleParamsOfFirstRule.stream().map(RuleParamDto::getName).toArray());
+
+ ExportRuleDto secondExportedRule = findExportedRuleById(secondActivatedRule.getId(), results);
+ assertThat(secondExportedRule.getParams())
+ .extracting("key")
+ .containsOnly(ruleParamsOfSecondRule.stream().map(RuleParamDto::getName).toArray());
+
+ ExportRuleDto thirdExportedRule = findExportedRuleById(thirdActivatedRule.getId(), otherProfileResults);
+ assertThat(thirdExportedRule.getParams())
+ .extracting("key")
+ .containsOnly(ruleParamsOfThirdRule.stream().map(RuleParamDto::getName).toArray());
+ }
+
+
+ private ExportRuleDto findExportedRuleById(Integer id, List<ExportRuleDto> results) {
+ Optional<ExportRuleDto> found = results.stream().filter(exportRuleDto -> id.equals(exportRuleDto.getActiveRuleId())).findFirst();
+ if (!found.isPresent()) {
+ Assert.fail();
+ }
+ return found.get();
+ }
+
+ private List<RuleParamDto> addParamsToRule(RuleDefinitionDto firstRule, int numberOfParams) {
+ return IntStream.range(0, numberOfParams)
+ .mapToObj(value -> db.rules().insertRuleParam(firstRule,
+ ruleParamDto -> ruleParamDto.setName("name_" + firstRule.getId() + "_" + value)))
+ .collect(Collectors.toList());
+ }
+
+ private RuleDefinitionDto createRule(String language) {
+ return createRule(language, RuleStatus.READY);
+ }
+
+ private RuleDefinitionDto createRule(String language, RuleStatus status) {
+ return createRule(language, status, null);
+ }
+
+ private RuleDefinitionDto createRule(String language, RuleStatus status, @Nullable Integer templateId) {
+ return db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto.setRepositoryKey("repoKey").setLanguage(language).setStatus(status).setTemplateId(templateId));
+ }
+
+ private RuleMetadataDto createRuleMetadata(RuleMetadataDto metadataDto) {
+ return db.rules().insertOrUpdateMetadata(metadataDto);
+ }
+
+ private QProfileDto createProfile(String lanugage) {
+ return db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(lanugage));
+ }
+
+ private List<ActiveRuleDto> activate(QProfileDto profile, RuleDefinitionDto... rules) {
+ return Stream.of(rules)
+ .map(ruleDefinitionDto -> db.qualityProfiles().activateRule(profile, ruleDefinitionDto))
+ .collect(Collectors.toList());
+ }
+
+ private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, Collection<RuleParamDto> params) {
+ ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
+
+ params.forEach(ruleParamDto -> {
+ ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(ruleParamDto)
+ .setKey(ruleParamDto.getName())
+ .setValue("20")
+ .setActiveRuleId(activeRule.getId());
+ db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
+ });
+
+ return activeRule;
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedQProfile.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedQProfile.java
new file mode 100644
index 00000000000..be10bdbc503
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedQProfile.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualityprofile;
+
+import java.util.List;
+
+class ImportedQProfile {
+ private final String profileName;
+ private final String profileLang;
+
+ private final List<ImportedRule> rules;
+
+ public ImportedQProfile(String profileName, String profileLang, List<ImportedRule> rules) {
+ this.profileName = profileName;
+ this.profileLang = profileLang;
+ this.rules = rules;
+ }
+
+ public String getProfileName() {
+ return profileName;
+ }
+
+ public String getProfileLang() {
+ return profileLang;
+ }
+
+ public List<ImportedRule> getRules() {
+ return rules;
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java
new file mode 100644
index 00000000000..484bc00c207
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualityprofile;
+
+import java.util.Map;
+import org.sonar.api.rule.RuleKey;
+
+class ImportedRule {
+ private RuleKey ruleKey;
+ private RuleKey templateKey;
+ private String name;
+ private String type;
+ private String severity;
+ private String description;
+
+ public Map<String, String> getParameters() {
+ return parameters;
+ }
+
+ private Map<String, String> parameters;
+
+ public RuleKey getRuleKey() {
+ return ruleKey;
+ }
+
+ public RuleKey getTemplateKey() {
+ return templateKey;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getSeverity() {
+ return severity;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ ImportedRule setRuleKey(RuleKey ruleKey) {
+ this.ruleKey = ruleKey;
+ return this;
+ }
+
+ ImportedRule setTemplateKey(RuleKey templateKey) {
+ this.templateKey = templateKey;
+ return this;
+ }
+
+ ImportedRule setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ ImportedRule setSeverity(String severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ ImportedRule setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ ImportedRule setParameters(Map<String, String> parameters) {
+ this.parameters = parameters;
+ return this;
+ }
+
+ ImportedRule setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ boolean isCustomRule() {
+ return templateKey != null;
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java
index 48658b6afd0..4a63cba654b 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java
@@ -19,105 +19,56 @@
*/
package org.sonar.server.qualityprofile;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
import java.io.Reader;
import java.io.Writer;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
-import org.codehaus.staxmate.SMInputFactory;
-import org.codehaus.staxmate.in.SMHierarchicCursor;
-import org.codehaus.staxmate.in.SMInputCursor;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.text.XmlWriter;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.OrgActiveRuleDto;
+import org.sonar.db.qualityprofile.ExportRuleDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.rule.NewCustomRule;
+import org.sonar.server.rule.RuleCreator;
import static com.google.common.base.Preconditions.checkArgument;
@ServerSide
public class QProfileBackuperImpl implements QProfileBackuper {
- private static final Joiner RULE_KEY_JOINER = Joiner.on(", ").skipNulls();
-
- private static final String ATTRIBUTE_PROFILE = "profile";
- private static final String ATTRIBUTE_NAME = "name";
- private static final String ATTRIBUTE_LANGUAGE = "language";
-
- private static final String ATTRIBUTE_RULES = "rules";
- private static final String ATTRIBUTE_RULE = "rule";
- private static final String ATTRIBUTE_REPOSITORY_KEY = "repositoryKey";
- private static final String ATTRIBUTE_KEY = "key";
- private static final String ATTRIBUTE_PRIORITY = "priority";
-
- private static final String ATTRIBUTE_PARAMETERS = "parameters";
- private static final String ATTRIBUTE_PARAMETER = "parameter";
- private static final String ATTRIBUTE_PARAMETER_KEY = "key";
- private static final String ATTRIBUTE_PARAMETER_VALUE = "value";
-
private final DbClient db;
private final QProfileReset profileReset;
private final QProfileFactory profileFactory;
+ private final RuleCreator ruleCreator;
+ private final QProfileParser qProfileParser;
- public QProfileBackuperImpl(DbClient db, QProfileReset profileReset, QProfileFactory profileFactory) {
+ public QProfileBackuperImpl(DbClient db, QProfileReset profileReset, QProfileFactory profileFactory,
+ RuleCreator ruleCreator, QProfileParser qProfileParser) {
this.db = db;
this.profileReset = profileReset;
this.profileFactory = profileFactory;
+ this.ruleCreator = ruleCreator;
+ this.qProfileParser = qProfileParser;
}
@Override
public void backup(DbSession dbSession, QProfileDto profile, Writer writer) {
- List<OrgActiveRuleDto> activeRules = db.activeRuleDao().selectByProfile(dbSession, profile);
- activeRules.sort(BackupActiveRuleComparator.INSTANCE);
- writeXml(dbSession, writer, profile, activeRules.iterator());
- }
-
- private void writeXml(DbSession dbSession, Writer writer, QProfileDto profile, Iterator<OrgActiveRuleDto> activeRules) {
- XmlWriter xml = XmlWriter.of(writer).declaration();
- xml.begin(ATTRIBUTE_PROFILE);
- xml.prop(ATTRIBUTE_NAME, profile.getName());
- xml.prop(ATTRIBUTE_LANGUAGE, profile.getLanguage());
- xml.begin(ATTRIBUTE_RULES);
- while (activeRules.hasNext()) {
- ActiveRuleDto activeRule = activeRules.next();
- xml.begin(ATTRIBUTE_RULE);
- xml.prop(ATTRIBUTE_REPOSITORY_KEY, activeRule.getRuleKey().repository());
- xml.prop(ATTRIBUTE_KEY, activeRule.getRuleKey().rule());
- xml.prop(ATTRIBUTE_PRIORITY, activeRule.getSeverityString());
- xml.begin(ATTRIBUTE_PARAMETERS);
- for (ActiveRuleParamDto param : db.activeRuleDao().selectParamsByActiveRuleId(dbSession, activeRule.getId())) {
- xml
- .begin(ATTRIBUTE_PARAMETER)
- .prop(ATTRIBUTE_PARAMETER_KEY, param.getKey())
- .prop(ATTRIBUTE_PARAMETER_VALUE, param.getValue())
- .end();
- }
- xml.end(ATTRIBUTE_PARAMETERS);
- xml.end(ATTRIBUTE_RULE);
- }
- xml.end(ATTRIBUTE_RULES).end(ATTRIBUTE_PROFILE).close();
+ List<ExportRuleDto> rulesToExport = db.qualityProfileExportDao().selectRulesByProfile(dbSession, profile);
+ rulesToExport.sort(BackupActiveRuleComparator.INSTANCE);
+ qProfileParser.writeXml(writer, profile, rulesToExport.iterator());
}
@Override
@@ -141,49 +92,35 @@ public class QProfileBackuperImpl implements QProfileBackuper {
}
private QProfileRestoreSummary restore(DbSession dbSession, Reader backup, Function<QProfileName, QProfileDto> profileLoader) {
- try {
- String profileLang = null;
- String profileName = null;
- List<Rule> rules = Lists.newArrayList();
- SMInputFactory inputFactory = initStax();
- SMHierarchicCursor rootC = inputFactory.rootElementCursor(backup);
- rootC.advance(); // <profile>
- if (!ATTRIBUTE_PROFILE.equals(rootC.getLocalName())) {
- throw new IllegalArgumentException("Backup XML is not valid. Root element must be <profile>.");
- }
- SMInputCursor cursor = rootC.childElementCursor();
- while (cursor.getNext() != null) {
- String nodeName = cursor.getLocalName();
- if (StringUtils.equals(ATTRIBUTE_NAME, nodeName)) {
- profileName = StringUtils.trim(cursor.collectDescendantText(false));
+ ImportedQProfile qProfile = qProfileParser.readXml(backup);
- } else if (StringUtils.equals(ATTRIBUTE_LANGUAGE, nodeName)) {
- profileLang = StringUtils.trim(cursor.collectDescendantText(false));
+ QProfileName targetName = new QProfileName(qProfile.getProfileLang(), qProfile.getProfileName());
+ QProfileDto targetProfile = profileLoader.apply(targetName);
- } else if (StringUtils.equals(ATTRIBUTE_RULES, nodeName)) {
- SMInputCursor rulesCursor = cursor.childElementCursor("rule");
- rules = parseRuleActivations(rulesCursor);
- }
- }
+ List<ImportedRule> importedRules = qProfile.getRules();
- QProfileName targetName = new QProfileName(profileLang, profileName);
- QProfileDto targetProfile = profileLoader.apply(targetName);
- List<RuleActivation> ruleActivations = toRuleActivations(dbSession, rules);
- BulkChangeResult changes = profileReset.reset(dbSession, targetProfile, ruleActivations);
- return new QProfileRestoreSummary(targetProfile, changes);
- } catch (XMLStreamException e) {
- throw new IllegalArgumentException("Fail to restore Quality profile backup, XML document is not well formed", e);
- }
+ Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey = getImportedRulesDefinitions(dbSession, importedRules);
+ checkIfRulesFromExternalEngines(ruleDefinitionsByKey);
+
+ Map<RuleKey, RuleDefinitionDto> customRulesDefinitions = createCustomRulesIfNotExist(dbSession, importedRules, ruleDefinitionsByKey);
+ ruleDefinitionsByKey.putAll(customRulesDefinitions);
+
+ List<RuleActivation> ruleActivations = toRuleActivations(importedRules, ruleDefinitionsByKey);
+
+ BulkChangeResult changes = profileReset.reset(dbSession, targetProfile, ruleActivations);
+ return new QProfileRestoreSummary(targetProfile, changes);
}
- private List<RuleActivation> toRuleActivations(DbSession dbSession, List<Rule> rules) {
+ private Map<RuleKey, RuleDefinitionDto> getImportedRulesDefinitions(DbSession dbSession, List<ImportedRule> rules) {
List<RuleKey> ruleKeys = rules.stream()
- .map(r -> r.ruleKey)
+ .map(ImportedRule::getRuleKey)
.collect(MoreCollectors.toList());
- Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey = db.ruleDao().selectDefinitionByKeys(dbSession, ruleKeys)
+ return db.ruleDao().selectDefinitionByKeys(dbSession, ruleKeys)
.stream()
- .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getKey));
+ .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity()));
+ }
+ private void checkIfRulesFromExternalEngines(Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey) {
List<RuleDefinitionDto> externalRules = ruleDefinitionsByKey.values().stream()
.filter(RuleDefinitionDto::isExternal)
.collect(Collectors.toList());
@@ -192,105 +129,51 @@ public class QProfileBackuperImpl implements QProfileBackuper {
throw new IllegalArgumentException("The quality profile cannot be restored as it contains rules from external rule engines: "
+ externalRules.stream().map(r -> r.getKey().toString()).collect(Collectors.joining(", ")));
}
-
- return rules.stream()
- .map(r -> {
- RuleDefinitionDto ruleDefinition = ruleDefinitionsByKey.get(r.ruleKey);
- if (ruleDefinition == null) {
- return null;
- }
- return RuleActivation.create(ruleDefinition.getId(), r.severity, r.parameters);
- })
- .filter(Objects::nonNull)
- .collect(MoreCollectors.toList(rules.size()));
}
- private static final class Rule {
- private final RuleKey ruleKey;
- private final String severity;
- private final Map<String, String> parameters;
+ private Map<RuleKey, RuleDefinitionDto> createCustomRulesIfNotExist(DbSession dbSession, List<ImportedRule> rules, Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey) {
+ List<NewCustomRule> customRulesToCreate = rules.stream()
+ .filter(r -> ruleDefinitionsByKey.get(r.getRuleKey()) == null && r.isCustomRule())
+ .map(QProfileBackuperImpl::importedRuleToNewCustomRule)
+ .collect(Collectors.toList());
- private Rule(RuleKey ruleKey, String severity, Map<String, String> parameters) {
- this.ruleKey = ruleKey;
- this.severity = severity;
- this.parameters = parameters;
+ if (!customRulesToCreate.isEmpty()) {
+ return db.ruleDao().selectDefinitionByKeys(dbSession, ruleCreator.create(dbSession, customRulesToCreate))
+ .stream()
+ .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity()));
}
+ return Collections.emptyMap();
}
- private static List<Rule> parseRuleActivations(SMInputCursor rulesCursor) throws XMLStreamException {
- List<Rule> activations = new ArrayList<>();
- Set<RuleKey> activatedKeys = new HashSet<>();
- List<RuleKey> duplicatedKeys = new ArrayList<>();
- while (rulesCursor.getNext() != null) {
- SMInputCursor ruleCursor = rulesCursor.childElementCursor();
- String repositoryKey = null;
- String key = null;
- String severity = null;
- Map<String, String> parameters = new HashMap<>();
- while (ruleCursor.getNext() != null) {
- String nodeName = ruleCursor.getLocalName();
- if (StringUtils.equals(ATTRIBUTE_REPOSITORY_KEY, nodeName)) {
- repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
-
- } else if (StringUtils.equals(ATTRIBUTE_KEY, nodeName)) {
- key = StringUtils.trim(ruleCursor.collectDescendantText(false));
-
- } else if (StringUtils.equals(ATTRIBUTE_PRIORITY, nodeName)) {
- severity = StringUtils.trim(ruleCursor.collectDescendantText(false));
-
- } else if (StringUtils.equals(ATTRIBUTE_PARAMETERS, nodeName)) {
- SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_PARAMETER);
- readParameters(propsCursor, parameters);
- }
- }
- RuleKey ruleKey = RuleKey.of(repositoryKey, key);
- if (activatedKeys.contains(ruleKey)) {
- duplicatedKeys.add(ruleKey);
- }
- activatedKeys.add(ruleKey);
- activations.add(new Rule(ruleKey, severity, parameters));
- }
- if (!duplicatedKeys.isEmpty()) {
- throw new IllegalArgumentException("The quality profile cannot be restored as it contains duplicates for the following rules: " +
- RULE_KEY_JOINER.join(duplicatedKeys));
- }
- return activations;
+ private static NewCustomRule importedRuleToNewCustomRule(ImportedRule r) {
+ return NewCustomRule.createForCustomRule(r.getRuleKey().rule(), r.getTemplateKey())
+ .setName(r.getName())
+ .setMarkdownDescription(r.getDescription())
+ .setSeverity(r.getSeverity())
+ .setStatus(RuleStatus.READY)
+ .setPreventReactivation(true)
+ .setType(RuleType.valueOf(r.getType()))
+ .setParameters(r.getParameters());
}
- private static void readParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException {
- while (propsCursor.getNext() != null) {
- SMInputCursor propCursor = propsCursor.childElementCursor();
- String key = null;
- String value = null;
- while (propCursor.getNext() != null) {
- String nodeName = propCursor.getLocalName();
- if (StringUtils.equals(ATTRIBUTE_PARAMETER_KEY, nodeName)) {
- key = StringUtils.trim(propCursor.collectDescendantText(false));
- } else if (StringUtils.equals(ATTRIBUTE_PARAMETER_VALUE, nodeName)) {
- value = StringUtils.trim(propCursor.collectDescendantText(false));
+ private List<RuleActivation> toRuleActivations(List<ImportedRule> rules, Map<RuleKey, RuleDefinitionDto> ruleDefinitionsByKey) {
+ return rules.stream()
+ .map(r -> {
+ RuleDefinitionDto ruleDefinition = ruleDefinitionsByKey.get(r.getRuleKey());
+ if (ruleDefinition == null) {
+ return null;
}
- }
- if (key != null) {
- parameters.put(key, value);
- }
- }
- }
-
- private static SMInputFactory initStax() {
- XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
- xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
- xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
- // just so it won't try to load DTD in if there's DOCTYPE
- xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
- xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
- return new SMInputFactory(xmlFactory);
+ return RuleActivation.create(ruleDefinition.getId(), r.getSeverity(), r.getParameters());
+ })
+ .filter(Objects::nonNull)
+ .collect(MoreCollectors.toList(rules.size()));
}
- private enum BackupActiveRuleComparator implements Comparator<ActiveRuleDto> {
+ private enum BackupActiveRuleComparator implements Comparator<ExportRuleDto> {
INSTANCE;
@Override
- public int compare(ActiveRuleDto o1, ActiveRuleDto o2) {
+ public int compare(ExportRuleDto o1, ExportRuleDto o2) {
return new CompareToBuilder()
.append(o1.getRuleKey().repository(), o2.getRuleKey().repository())
.append(o1.getRuleKey().rule(), o2.getRuleKey().rule())
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java
new file mode 100644
index 00000000000..fad9bd08447
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java
@@ -0,0 +1,216 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualityprofile;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.staxmate.SMInputFactory;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.text.XmlWriter;
+import org.sonar.db.qualityprofile.ExportRuleDto;
+import org.sonar.db.qualityprofile.ExportRuleParamDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+
+@ServerSide
+public class QProfileParser {
+ private static final Joiner RULE_KEY_JOINER = Joiner.on(", ").skipNulls();
+
+ private static final String ATTRIBUTE_PROFILE = "profile";
+ private static final String ATTRIBUTE_NAME = "name";
+ private static final String ATTRIBUTE_LANGUAGE = "language";
+
+ private static final String ATTRIBUTE_RULES = "rules";
+ private static final String ATTRIBUTE_RULE = "rule";
+ private static final String ATTRIBUTE_REPOSITORY_KEY = "repositoryKey";
+ private static final String ATTRIBUTE_KEY = "key";
+ private static final String ATTRIBUTE_PRIORITY = "priority";
+ private static final String ATTRIBUTE_TEMPLATE_KEY = "templateKey";
+ private static final String ATTRIBUTE_TYPE = "type";
+ private static final String ATTRIBUTE_DESCRIPTION = "description";
+
+ private static final String ATTRIBUTE_PARAMETERS = "parameters";
+ private static final String ATTRIBUTE_PARAMETER = "parameter";
+ private static final String ATTRIBUTE_PARAMETER_KEY = "key";
+ private static final String ATTRIBUTE_PARAMETER_VALUE = "value";
+
+ public QProfileParser() {
+ }
+
+ public void writeXml(Writer writer, QProfileDto profile, Iterator<ExportRuleDto> rulesToExport) {
+ XmlWriter xml = XmlWriter.of(writer).declaration();
+ xml.begin(ATTRIBUTE_PROFILE);
+ xml.prop(ATTRIBUTE_NAME, profile.getName());
+ xml.prop(ATTRIBUTE_LANGUAGE, profile.getLanguage());
+ xml.begin(ATTRIBUTE_RULES);
+ while (rulesToExport.hasNext()) {
+ ExportRuleDto ruleToExport = rulesToExport.next();
+ xml.begin(ATTRIBUTE_RULE);
+ xml.prop(ATTRIBUTE_REPOSITORY_KEY, ruleToExport.getRuleKey().repository());
+ xml.prop(ATTRIBUTE_KEY, ruleToExport.getRuleKey().rule());
+ xml.prop(ATTRIBUTE_TYPE, ruleToExport.getRuleType().name());
+ xml.prop(ATTRIBUTE_PRIORITY, ruleToExport.getSeverityString());
+
+ if (ruleToExport.isCustomRule()) {
+ xml.prop(ATTRIBUTE_NAME, ruleToExport.getName());
+ xml.prop(ATTRIBUTE_TEMPLATE_KEY, ruleToExport.getTemplateRuleKey().rule());
+ xml.prop(ATTRIBUTE_DESCRIPTION, ruleToExport.getDescription());
+ }
+
+ xml.begin(ATTRIBUTE_PARAMETERS);
+ for (ExportRuleParamDto param : ruleToExport.getParams()) {
+ xml
+ .begin(ATTRIBUTE_PARAMETER)
+ .prop(ATTRIBUTE_PARAMETER_KEY, param.getKey())
+ .prop(ATTRIBUTE_PARAMETER_VALUE, param.getValue())
+ .end();
+ }
+ xml.end(ATTRIBUTE_PARAMETERS);
+ xml.end(ATTRIBUTE_RULE);
+ }
+ xml.end(ATTRIBUTE_RULES).end(ATTRIBUTE_PROFILE).close();
+ }
+
+ public ImportedQProfile readXml(Reader reader) {
+ List<ImportedRule> rules = Lists.newArrayList();
+ String profileName = null;
+ String profileLang = null;
+ try {
+ SMInputFactory inputFactory = initStax();
+ SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
+ rootC.advance(); // <profile>
+ if (!ATTRIBUTE_PROFILE.equals(rootC.getLocalName())) {
+ throw new IllegalArgumentException("Backup XML is not valid. Root element must be <profile>.");
+ }
+ SMInputCursor cursor = rootC.childElementCursor();
+
+ while (cursor.getNext() != null) {
+ String nodeName = cursor.getLocalName();
+ if (StringUtils.equals(ATTRIBUTE_NAME, nodeName)) {
+ profileName = StringUtils.trim(cursor.collectDescendantText(false));
+ } else if (StringUtils.equals(ATTRIBUTE_LANGUAGE, nodeName)) {
+ profileLang = StringUtils.trim(cursor.collectDescendantText(false));
+ } else if (StringUtils.equals(ATTRIBUTE_RULES, nodeName)) {
+ SMInputCursor rulesCursor = cursor.childElementCursor("rule");
+ rules = parseRuleActivations(rulesCursor);
+ }
+ }
+ } catch (XMLStreamException e) {
+ throw new IllegalArgumentException("Fail to restore Quality profile backup, XML document is not well formed", e);
+ }
+ return new ImportedQProfile(profileName, profileLang, rules);
+ }
+
+ private static SMInputFactory initStax() {
+ XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
+ xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
+ xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
+ // just so it won't try to load DTD in if there's DOCTYPE
+ xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+ xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
+ return new SMInputFactory(xmlFactory);
+ }
+
+ private static List<ImportedRule> parseRuleActivations(SMInputCursor rulesCursor) throws XMLStreamException {
+ List<ImportedRule> activations = new ArrayList<>();
+ Set<RuleKey> activatedKeys = new HashSet<>();
+ List<RuleKey> duplicatedKeys = new ArrayList<>();
+ while (rulesCursor.getNext() != null) {
+ SMInputCursor ruleCursor = rulesCursor.childElementCursor();
+ Map<String, String> parameters = new HashMap<>();
+ String repositoryKey = null;
+ String key = null;
+ String templateKey = null;
+ ImportedRule rule = new ImportedRule();
+ while (ruleCursor.getNext() != null) {
+ String nodeName = ruleCursor.getLocalName();
+ if (StringUtils.equals(ATTRIBUTE_REPOSITORY_KEY, nodeName)) {
+ repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
+ } else if (StringUtils.equals(ATTRIBUTE_KEY, nodeName)) {
+ key = StringUtils.trim(ruleCursor.collectDescendantText(false));
+ } else if (StringUtils.equals(ATTRIBUTE_TEMPLATE_KEY, nodeName)) {
+ templateKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
+ } else if (StringUtils.equals(ATTRIBUTE_NAME, nodeName)) {
+ rule.setName(StringUtils.trim(ruleCursor.collectDescendantText(false)));
+ } else if (StringUtils.equals(ATTRIBUTE_TYPE, nodeName)) {
+ rule.setType(StringUtils.trim(ruleCursor.collectDescendantText(false)));
+ } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION, nodeName)) {
+ rule.setDescription(StringUtils.trim(ruleCursor.collectDescendantText(false)));
+ } else if (StringUtils.equals(ATTRIBUTE_PRIORITY, nodeName)) {
+ rule.setSeverity(StringUtils.trim(ruleCursor.collectDescendantText(false)));
+ } else if (StringUtils.equals(ATTRIBUTE_PARAMETERS, nodeName)) {
+ SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_PARAMETER);
+ readParameters(propsCursor, parameters);
+ rule.setParameters(parameters);
+ }
+ }
+ RuleKey ruleKey = RuleKey.of(repositoryKey, key);
+ rule.setRuleKey(ruleKey);
+
+ if (templateKey != null) {
+ rule.setTemplateKey(RuleKey.of(repositoryKey, templateKey));
+ }
+
+ if (activatedKeys.contains(ruleKey)) {
+ duplicatedKeys.add(ruleKey);
+ }
+ activatedKeys.add(ruleKey);
+ activations.add(rule);
+ }
+ if (!duplicatedKeys.isEmpty()) {
+ throw new IllegalArgumentException("The quality profile cannot be restored as it contains duplicates for the following rules: " +
+ RULE_KEY_JOINER.join(duplicatedKeys));
+ }
+ return activations;
+ }
+
+ private static void readParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException {
+ while (propsCursor.getNext() != null) {
+ SMInputCursor propCursor = propsCursor.childElementCursor();
+ String key = null;
+ String value = null;
+ while (propCursor.getNext() != null) {
+ String nodeName = propCursor.getLocalName();
+ if (StringUtils.equals(ATTRIBUTE_PARAMETER_KEY, nodeName)) {
+ key = StringUtils.trim(propCursor.collectDescendantText(false));
+ } else if (StringUtils.equals(ATTRIBUTE_PARAMETER_VALUE, nodeName)) {
+ value = StringUtils.trim(propCursor.collectDescendantText(false));
+ }
+ }
+ if (key != null) {
+ parameters.put(key, value);
+ }
+ }
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java
index 5a4074a20bd..e841491dde7 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java
@@ -23,8 +23,11 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleKey;
@@ -89,6 +92,46 @@ public class RuleCreator {
return customRuleKey;
}
+ public List<RuleKey> create(DbSession dbSession, List<NewCustomRule> newRules) {
+ String defaultOrganizationUuid = defaultOrganizationProvider.get().getUuid();
+ OrganizationDto defaultOrganization = dbClient.organizationDao().selectByUuid(dbSession, defaultOrganizationUuid)
+ .orElseThrow(() -> new IllegalStateException(format("Could not find default organization for uuid '%s'", defaultOrganizationUuid)));
+
+ Set<RuleKey> templateKeys = newRules.stream().map(NewCustomRule::templateKey).collect(Collectors.toSet());
+ Map<RuleKey, RuleDto> templateRules = dbClient.ruleDao().selectByKeys(dbSession, defaultOrganization.getUuid(), templateKeys)
+ .stream()
+ .collect(Collectors.toMap(
+ RuleDto::getKey,
+ Function.identity())
+ );
+
+ checkArgument(!templateRules.isEmpty() && templateKeys.size() == templateRules.size(), "Rule template keys should exists for each custom rule!");
+ templateRules.values().forEach(ruleDto -> {
+ checkArgument(ruleDto.isTemplate(), "This rule is not a template rule: %s", ruleDto.getKey().toString());
+ checkArgument(ruleDto.getStatus() != RuleStatus.REMOVED, "The template key doesn't exist: %s", ruleDto.getKey().toString());
+ }
+ );
+
+ List<Integer> customRuleIds = newRules.stream()
+ .map(newCustomRule -> {
+ RuleDto templateRule = templateRules.get(newCustomRule.templateKey());
+ validateCustomRule(newCustomRule, dbSession, templateRule.getKey());
+ RuleKey customRuleKey = RuleKey.of(templateRule.getRepositoryKey(), newCustomRule.ruleKey());
+ return createCustomRule(customRuleKey, newCustomRule, templateRule, dbSession);
+ }
+ )
+ .collect(Collectors.toList());
+
+ ruleIndexer.commitAndIndex(dbSession, customRuleIds);
+ return newRules.stream()
+ .map(newCustomRule -> {
+ RuleDto templateRule = templateRules.get(newCustomRule.templateKey());
+ return RuleKey.of(templateRule.getRepositoryKey(), newCustomRule.ruleKey());
+ }
+ )
+ .collect(Collectors.toList());
+ }
+
private void validateCustomRule(NewCustomRule newRule, DbSession dbSession, RuleKey templateKey) {
List<String> errors = new ArrayList<>();
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java
index 9e27015bc5f..942de489af8 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java
@@ -25,12 +25,15 @@ import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
@@ -40,12 +43,18 @@ import org.sonar.db.qualityprofile.ActiveRuleParamDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.QualityProfileTesting;
import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleMetadataDto;
import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.rule.RuleCreator;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.junit.rules.ExpectedException.none;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class QProfileBackuperImplTest {
@@ -64,7 +73,9 @@ public class QProfileBackuperImplTest {
private DummyReset reset = new DummyReset();
private QProfileFactory profileFactory = new DummyProfileFactory();
- private QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory);
+ private RuleCreator ruleCreator = mock(RuleCreator.class);
+
+ private QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
@Test
public void backup_generates_xml_file() {
@@ -82,6 +93,7 @@ public class QProfileBackuperImplTest {
"<rule>" +
"<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
"<key>" + rule.getRuleKey() + "</key>" +
+ "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
"<priority>" + activeRule.getSeverityString() + "</priority>" +
"<parameters/>" +
"</rule>" +
@@ -103,6 +115,7 @@ public class QProfileBackuperImplTest {
"<rule>" +
"<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
"<key>" + rule.getRuleKey() + "</key>" +
+ "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
"<priority>" + activeRule.getSeverityString() + "</priority>" +
"<parameters><parameter>" +
"<key>" + param.getName() + "</key>" +
@@ -127,6 +140,41 @@ public class QProfileBackuperImplTest {
}
@Test
+ public void backup_custom_rules_with_params() {
+ RuleDefinitionDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
+ .setIsTemplate(true));
+ RuleDefinitionDto rule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
+ .setDescription("custom rule description")
+ .setName("custom rule name")
+ .setStatus(RuleStatus.READY)
+ .setTemplateId(templateRule.getId()));
+ RuleParamDto param = db.rules().insertRuleParam(rule);
+ QProfileDto profile = createProfile(rule);
+ ActiveRuleDto activeRule = activate(profile, rule, param);
+
+ StringWriter writer = new StringWriter();
+ underTest.backup(db.getSession(), profile, writer);
+
+ assertThat(writer.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
+ "<profile>" +
+ "<name>" + profile.getName() + "</name>" +
+ "<language>" + profile.getLanguage() + "</language>" +
+ "<rules><rule>" +
+ "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
+ "<key>" + rule.getKey().rule() + "</key>" +
+ "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
+ "<priority>" + activeRule.getSeverityString() + "</priority>" +
+ "<name>" + rule.getName() + "</name>" +
+ "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
+ "<description>" + rule.getDescription() + "</description>" +
+ "<parameters><parameter>" +
+ "<key>" + param.getName() + "</key>" +
+ "<value>20</value>" +
+ "</parameter></parameters>" +
+ "</rule></rules></profile>");
+ }
+
+ @Test
public void restore_backup_on_the_profile_specified_in_backup() {
OrganizationDto organization = db.organizations().insert();
Reader backup = new StringReader(EMPTY_BACKUP);
@@ -183,6 +231,72 @@ public class QProfileBackuperImplTest {
}
@Test
+ public void restore_custom_rule() {
+ when(ruleCreator.create(any(), anyList())).then(invocation ->
+ Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
+
+ OrganizationDto organization = db.organizations().insert();
+ Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
+ "<profile>" +
+ "<name>custom rule</name>" +
+ "<language>js</language>" +
+ "<rules><rule>" +
+ "<repositoryKey>sonarjs</repositoryKey>" +
+ "<key>s001</key>" +
+ "<type>CODE_SMELL</type>" +
+ "<priority>CRITICAL</priority>" +
+ "<name>custom rule name</name>" +
+ "<templateKey>rule_mc8</templateKey>" +
+ "<description>custom rule description</description>" +
+ "<parameters><parameter>" +
+ "<key>bar</key>" +
+ "<value>baz</value>" +
+ "</parameter>" +
+ "</parameters>" +
+ "</rule></rules></profile>");
+
+ underTest.restore(db.getSession(), backup, organization, null);
+
+ assertThat(reset.calledActivations).hasSize(1);
+ RuleActivation activation = reset.calledActivations.get(0);
+ assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
+ assertThat(activation.getParameter("bar")).isEqualTo("baz");
+ }
+
+ @Test
+ public void restore_skips_rule_without_template_key_and_db_definition() {
+ Integer ruleId = db.rules().insert(RuleKey.of("sonarjs", "s001")).getId();
+ OrganizationDto organization = db.organizations().insert();
+ Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
+ "<profile><name>foo</name>" +
+ "<language>js</language>" +
+ "<rules>" +
+ "<rule>" +
+ "<repositoryKey>sonarjs</repositoryKey>" +
+ "<key>s001</key>" +
+ "<priority>BLOCKER</priority>" +
+ "<parameters>" +
+ "<parameter><key>bar</key><value>baz</value></parameter>" +
+ "</parameters>" +
+ "</rule>" +
+ "<rule>" +
+ "<repositoryKey>sonarjs</repositoryKey>" +
+ "<key>s002</key>" +
+ "<priority>MAJOR</priority>" +
+ "</rule>" +
+ "</rules>" +
+ "</profile>");
+
+ underTest.restore(db.getSession(), backup, organization, null);
+
+ assertThat(reset.calledActivations).hasSize(1);
+ RuleActivation activation = reset.calledActivations.get(0);
+ assertThat(activation.getRuleId()).isEqualTo(ruleId);
+ assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
+ assertThat(activation.getParameter("bar")).isEqualTo("baz");
+ }
+
+ @Test
public void fail_to_restore_if_bad_xml_format() {
OrganizationDto organization = db.organizations().insert();
try {
@@ -256,6 +370,10 @@ public class QProfileBackuperImplTest {
return db.rules().insert();
}
+ private RuleMetadataDto createRuleMetadata(RuleMetadataDto metadataDto) {
+ return db.rules().insertOrUpdateMetadata(metadataDto);
+ }
+
private QProfileDto createProfile(RuleDefinitionDto rule) {
return db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(rule.getLanguage()));
}
@@ -284,6 +402,7 @@ public class QProfileBackuperImplTest {
return new BulkChangeResult();
}
}
+
private static class DummyProfileFactory implements QProfileFactory {
@Override
public QProfileDto getOrCreateCustom(DbSession dbSession, OrganizationDto organization, QProfileName key) {
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/BackupActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/BackupActionTest.java
index 19630f1fd14..1d9cd581d36 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/BackupActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/BackupActionTest.java
@@ -36,6 +36,7 @@ import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.qualityprofile.QProfileBackuper;
import org.sonar.server.qualityprofile.QProfileBackuperImpl;
+import org.sonar.server.qualityprofile.QProfileParser;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
@@ -57,7 +58,7 @@ public class BackupActionTest {
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
- private QProfileBackuper backuper = new QProfileBackuperImpl(db.getDbClient(), null, null);
+ private QProfileBackuper backuper = new QProfileBackuperImpl(db.getDbClient(), null, null, null, new QProfileParser());
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private QProfileWsSupport wsSupport = new QProfileWsSupport(db.getDbClient(), userSession, defaultOrganizationProvider);
private Languages languages = LanguageTesting.newLanguages(A_LANGUAGE);
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java
index 45bef460214..dca3d48b90e 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java
@@ -21,6 +21,8 @@ package org.sonar.server.rule;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.assertj.core.api.Fail;
@@ -187,6 +189,53 @@ public class RuleCreatorTest {
}
@Test
+ public void batch_create_custom_rules() {
+ // insert template rule
+ RuleDefinitionDto templateRule = createTemplateRuleWithIntArrayParam();
+
+ NewCustomRule firstRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_1", templateRule.getKey())
+ .setName("My custom")
+ .setHtmlDescription("Some description")
+ .setSeverity(Severity.MAJOR)
+ .setStatus(RuleStatus.READY);
+
+ NewCustomRule secondRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_2", templateRule.getKey())
+ .setName("My custom")
+ .setHtmlDescription("Some description")
+ .setSeverity(Severity.MAJOR)
+ .setStatus(RuleStatus.READY);
+
+ List<RuleKey> customRuleKeys = underTest.create(dbSession, Arrays.asList(firstRule, secondRule));
+
+ List<RuleDto> rules = dbTester.getDbClient().ruleDao().selectByKeys(dbSession, dbTester.getDefaultOrganization(), customRuleKeys);
+
+ assertThat(rules).hasSize(2);
+ assertThat(rules).asList()
+ .extracting("ruleKey")
+ .containsOnly("CUSTOM_RULE_1", "CUSTOM_RULE_2");
+ }
+
+ @Test
+ public void fail_to_create_custom_rules_when_wrong_rule_template() {
+ // insert rule
+ RuleDefinitionDto rule = newRule(RuleKey.of("java", "S001")).setIsTemplate(false);
+ dbTester.rules().insert(rule);
+ dbSession.commit();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("This rule is not a template rule: java:S001");
+
+ // Create custom rule with unknown template rule
+ NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey())
+ .setName("My custom")
+ .setHtmlDescription("Some description")
+ .setSeverity(Severity.MAJOR)
+ .setStatus(RuleStatus.READY)
+ .setParameters(ImmutableMap.of("regex", "a.*"));
+ underTest.create(dbSession, Collections.singletonList(newRule));
+ }
+
+ @Test
public void fail_to_create_custom_rule_with_invalid_parameter() {
// insert template rule
RuleDefinitionDto templateRule = createTemplateRuleWithIntArrayParam();
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index c9d39d14d9d..c00cf3852c9 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -115,7 +115,7 @@ import org.sonar.server.permission.ws.PermissionsWsModule;
import org.sonar.server.platform.BackendCleanup;
import org.sonar.server.platform.ClusterVerification;
import org.sonar.server.platform.PersistentSettings;
-import org.sonar.server.setting.SettingsChangeNotifier;
+import org.sonar.server.platform.SystemInfoWriterModule;
import org.sonar.server.platform.WebCoreExtensionsInstaller;
import org.sonar.server.platform.web.DeprecatedPropertiesWsFilter;
import org.sonar.server.platform.web.WebServiceFilter;
@@ -126,7 +126,6 @@ import org.sonar.server.platform.ws.HealthCheckerModule;
import org.sonar.server.platform.ws.L10nWs;
import org.sonar.server.platform.ws.ServerWs;
import org.sonar.server.platform.ws.SystemWsModule;
-import org.sonar.server.platform.SystemInfoWriterModule;
import org.sonar.server.plugins.PluginDownloader;
import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.plugins.ServerExtensionInstaller;
@@ -159,6 +158,7 @@ import org.sonar.server.qualityprofile.QProfileComparison;
import org.sonar.server.qualityprofile.QProfileCopier;
import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonar.server.qualityprofile.QProfileFactoryImpl;
+import org.sonar.server.qualityprofile.QProfileParser;
import org.sonar.server.qualityprofile.QProfileResetImpl;
import org.sonar.server.qualityprofile.QProfileRulesImpl;
import org.sonar.server.qualityprofile.QProfileTreeImpl;
@@ -181,8 +181,9 @@ import org.sonar.server.rule.ws.RuleQueryFactory;
import org.sonar.server.rule.ws.RuleWsSupport;
import org.sonar.server.rule.ws.RulesWs;
import org.sonar.server.rule.ws.TagsAction;
-import org.sonar.server.setting.ws.SettingsWsModule;
import org.sonar.server.setting.ProjectConfigurationLoaderImpl;
+import org.sonar.server.setting.SettingsChangeNotifier;
+import org.sonar.server.setting.ws.SettingsWsModule;
import org.sonar.server.source.ws.SourceWsModule;
import org.sonar.server.startup.LogServerId;
import org.sonar.server.telemetry.TelemetryClient;
@@ -286,6 +287,7 @@ public class PlatformLevel4 extends PlatformLevel {
QProfileFactoryImpl.class,
QProfileCopier.class,
QProfileBackuperImpl.class,
+ QProfileParser.class,
QProfileResetImpl.class,
QProfilesWsModule.class,