@@ -135,7 +135,7 @@ public class ComputeEngineContainerImplTest { | |||
assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize( | |||
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION | |||
+ 23 // level 1 | |||
+ 45 // content of DaoModule | |||
+ 46 // content of DaoModule | |||
+ 3 // content of EsSearchModule | |||
+ 56 // content of CorePropertyDefinitions | |||
); |
@@ -52,6 +52,7 @@ public final class SqTables { | |||
"ce_queue", | |||
"ce_task_input", | |||
"ce_scanner_context", | |||
"default_qprofiles", | |||
"duplications_index", | |||
"events", | |||
"file_sources", |
@@ -61,6 +61,17 @@ CREATE TABLE "RULES_PROFILES" ( | |||
CREATE UNIQUE INDEX "UNIQ_QPROF_KEY" ON "RULES_PROFILES" ("KEE"); | |||
CREATE TABLE "DEFAULT_QPROFILES" ( | |||
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL, | |||
"LANGUAGE" VARCHAR(20) NOT NULL, | |||
"QPROFILE_UUID" VARCHAR(40) NOT NULL, | |||
"CREATED_AT" BIGINT NOT NULL, | |||
"UPDATED_AT" BIGINT NOT NULL | |||
); | |||
CREATE PRIMARY KEY ON "DEFAULT_QPROFILES" ("ORGANIZATION_UUID", "LANGUAGE"); | |||
CREATE UNIQUE INDEX "UNIQ_DEFAULT_QPROFILES_UUID" ON "DEFAULT_QPROFILES" ("QPROFILE_UUID"); | |||
CREATE TABLE "PROJECT_QPROFILES" ( | |||
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), | |||
"PROJECT_UUID" VARCHAR(50) NOT NULL, |
@@ -54,6 +54,7 @@ import org.sonar.db.qualitygate.ProjectQgateAssociationDao; | |||
import org.sonar.db.qualitygate.QualityGateConditionDao; | |||
import org.sonar.db.qualitygate.QualityGateDao; | |||
import org.sonar.db.qualityprofile.ActiveRuleDao; | |||
import org.sonar.db.qualityprofile.DefaultQProfileDao; | |||
import org.sonar.db.qualityprofile.QProfileChangeDao; | |||
import org.sonar.db.qualityprofile.QualityProfileDao; | |||
import org.sonar.db.rule.RuleDao; | |||
@@ -83,6 +84,7 @@ public class DaoModule extends Module { | |||
ComponentKeyUpdaterDao.class, | |||
ComponentLinkDao.class, | |||
CustomMeasureDao.class, | |||
DefaultQProfileDao.class, | |||
DuplicationDao.class, | |||
EventDao.class, | |||
FileSourceDao.class, |
@@ -53,6 +53,7 @@ import org.sonar.db.qualitygate.ProjectQgateAssociationDao; | |||
import org.sonar.db.qualitygate.QualityGateConditionDao; | |||
import org.sonar.db.qualitygate.QualityGateDao; | |||
import org.sonar.db.qualityprofile.ActiveRuleDao; | |||
import org.sonar.db.qualityprofile.DefaultQProfileDao; | |||
import org.sonar.db.qualityprofile.QProfileChangeDao; | |||
import org.sonar.db.qualityprofile.QualityProfileDao; | |||
import org.sonar.db.rule.RuleDao; | |||
@@ -116,6 +117,7 @@ public class DbClient { | |||
private final QProfileChangeDao qProfileChangeDao; | |||
private final UserPermissionDao userPermissionDao; | |||
private final WebhookDeliveryDao webhookDeliveryDao; | |||
private final DefaultQProfileDao defaultQProfileDao; | |||
public DbClient(Database database, MyBatis myBatis, Dao... daos) { | |||
this.database = database; | |||
@@ -170,6 +172,7 @@ public class DbClient { | |||
qProfileChangeDao = getDao(map, QProfileChangeDao.class); | |||
userPermissionDao = getDao(map, UserPermissionDao.class); | |||
webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class); | |||
defaultQProfileDao = getDao(map, DefaultQProfileDao.class); | |||
} | |||
public DbSession openSession(boolean batch) { | |||
@@ -360,6 +363,10 @@ public class DbClient { | |||
return webhookDeliveryDao; | |||
} | |||
public DefaultQProfileDao defaultQProfileDao() { | |||
return defaultQProfileDao; | |||
} | |||
protected <K extends Dao> K getDao(Map<Class, Dao> map, Class<K> clazz) { | |||
return (K) map.get(clazz); | |||
} |
@@ -95,6 +95,7 @@ import org.sonar.db.qualitygate.QualityGateMapper; | |||
import org.sonar.db.qualityprofile.ActiveRuleDto; | |||
import org.sonar.db.qualityprofile.ActiveRuleMapper; | |||
import org.sonar.db.qualityprofile.ActiveRuleParamDto; | |||
import org.sonar.db.qualityprofile.DefaultQProfileMapper; | |||
import org.sonar.db.qualityprofile.QProfileChangeMapper; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import org.sonar.db.qualityprofile.QualityProfileMapper; | |||
@@ -196,6 +197,7 @@ public class MyBatis implements Startable { | |||
ComponentLinkMapper.class, | |||
ComponentMapper.class, | |||
CustomMeasureMapper.class, | |||
DefaultQProfileMapper.class, | |||
DuplicationMapper.class, | |||
EventMapper.class, | |||
FileSourceMapper.class, |
@@ -0,0 +1,63 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.Set; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.Dao; | |||
import org.sonar.db.DatabaseUtils; | |||
import org.sonar.db.DbSession; | |||
import static java.util.Collections.singletonList; | |||
public class DefaultQProfileDao implements Dao { | |||
private final System2 system2; | |||
public DefaultQProfileDao(System2 system2) { | |||
this.system2 = system2; | |||
} | |||
public void insertOrUpdate(DbSession dbSession, DefaultQProfileDto dto) { | |||
long now = system2.now(); | |||
DefaultQProfileMapper mapper = mapper(dbSession); | |||
if (mapper.update(dto, now) == 0) { | |||
mapper.insert(dto, now); | |||
} | |||
} | |||
public void deleteByQProfileUuids(DbSession dbSession, Collection<String> qProfileUuids) { | |||
DefaultQProfileMapper mapper = mapper(dbSession); | |||
DatabaseUtils.executeLargeUpdates(qProfileUuids, mapper::deleteByQProfileUuids); | |||
} | |||
public Set<String> selectExistingQProfileUuids(DbSession dbSession, String organizationUuid, Collection<String> qProfileUuids) { | |||
return mapper(dbSession).selectExistingQProfileUuids(organizationUuid, qProfileUuids); | |||
} | |||
public boolean isDefault(DbSession dbSession, String organizationUuid, String qProfileUuid) { | |||
return selectExistingQProfileUuids(dbSession, organizationUuid, singletonList(qProfileUuid)).contains(qProfileUuid); | |||
} | |||
private static DefaultQProfileMapper mapper(DbSession dbSession) { | |||
return dbSession.getMapper(DefaultQProfileMapper.class); | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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 DefaultQProfileDto { | |||
private String organizationUuid; | |||
private String language; | |||
private String qProfileUuid; | |||
public String getOrganizationUuid() { | |||
return organizationUuid; | |||
} | |||
public DefaultQProfileDto setOrganizationUuid(String s) { | |||
this.organizationUuid = s; | |||
return this; | |||
} | |||
public String getLanguage() { | |||
return language; | |||
} | |||
public DefaultQProfileDto setLanguage(String s) { | |||
this.language = s; | |||
return this; | |||
} | |||
public String getQProfileUuid() { | |||
return qProfileUuid; | |||
} | |||
public DefaultQProfileDto setQProfileUuid(String s) { | |||
this.qProfileUuid = s; | |||
return this; | |||
} | |||
public static DefaultQProfileDto from(QualityProfileDto profile) { | |||
return new DefaultQProfileDto() | |||
.setOrganizationUuid(profile.getOrganizationUuid()) | |||
.setLanguage(profile.getLanguage()) | |||
.setQProfileUuid(profile.getKee()); | |||
} | |||
@Override | |||
public String toString() { | |||
StringBuilder sb = new StringBuilder("DefaultQProfileDto{"); | |||
sb.append("organizationUuid='").append(organizationUuid).append('\''); | |||
sb.append(", language='").append(language).append('\''); | |||
sb.append(", qProfileUuid='").append(qProfileUuid).append('\''); | |||
sb.append('}'); | |||
return sb.toString(); | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.Set; | |||
import org.apache.ibatis.annotations.Param; | |||
public interface DefaultQProfileMapper { | |||
void insert(@Param("dto") DefaultQProfileDto dto, @Param("now") long now); | |||
int update(@Param("dto") DefaultQProfileDto dto, @Param("now") long now); | |||
void deleteByQProfileUuids(@Param("qProfileUuids") Collection<String> qProfileUuids); | |||
Set<String> selectExistingQProfileUuids( | |||
@Param("organizationUuid") String organizationUuid, | |||
@Param("qProfileUuids") Collection<String> qProfileUuids); | |||
} |
@@ -0,0 +1,52 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
<mapper namespace="org.sonar.db.qualityprofile.DefaultQProfileMapper"> | |||
<insert id="insert" useGeneratedKeys="false" parameterType="map"> | |||
insert into default_qprofiles | |||
( | |||
organization_uuid, | |||
language, | |||
qprofile_uuid, | |||
created_at, | |||
updated_at | |||
) values ( | |||
#{dto.organizationUuid, jdbcType=VARCHAR}, | |||
#{dto.language, jdbcType=VARCHAR}, | |||
#{dto.qProfileUuid, jdbcType=VARCHAR}, | |||
#{now, jdbcType=BIGINT}, | |||
#{now, jdbcType=BIGINT} | |||
) | |||
</insert> | |||
<update id="update" parameterType="map"> | |||
update default_qprofiles | |||
set | |||
qprofile_uuid = #{dto.qProfileUuid, jdbcType=VARCHAR}, | |||
updated_at = #{now, jdbcType=BIGINT} | |||
where | |||
organization_uuid = #{dto.organizationUuid, jdbcType=VARCHAR} | |||
and language = #{dto.language, jdbcType=VARCHAR} | |||
</update> | |||
<delete id="deleteByQProfileUuids" parameterType="String"> | |||
delete from default_qprofiles | |||
where qprofile_uuid in | |||
<foreach collection="qProfileUuids" open="(" close=")" item="qProfileUuid" separator=","> | |||
#{qProfileUuid, jdbcType=VARCHAR} | |||
</foreach> | |||
</delete> | |||
<select id="selectExistingQProfileUuids" parameterType="map" resultType="String"> | |||
select qprofile_uuid | |||
from default_qprofiles | |||
where | |||
organization_uuid = #{organizationUuid, jdbcType=VARCHAR} | |||
and qprofile_uuid in | |||
<foreach collection="qProfileUuids" open="(" close=")" item="qProfileUuid" separator=","> | |||
#{qProfileUuid, jdbcType=VARCHAR} | |||
</foreach> | |||
</select> | |||
</mapper> | |||
@@ -71,12 +71,12 @@ | |||
order by created_at desc | |||
</sql> | |||
<update id="deleteByProfileKeys" parameterType="String"> | |||
<delete id="deleteByProfileKeys" parameterType="String"> | |||
delete from qprofile_changes | |||
where qprofile_key in | |||
<foreach collection="profileKeys" open="(" close=")" item="profileKey" separator=","> | |||
#{profileKey, jdbcType=VARCHAR} | |||
</foreach> | |||
</update> | |||
</delete> | |||
</mapper> | |||
@@ -29,6 +29,6 @@ public class DaoModuleTest { | |||
public void verify_count_of_added_components() { | |||
ComponentContainer container = new ComponentContainer(); | |||
new DaoModule().configure(container); | |||
assertThat(container.size()).isEqualTo(2 + 45); | |||
assertThat(container.size()).isEqualTo(2 + 46); | |||
} | |||
} |
@@ -31,7 +31,7 @@ public class ActiveRuleParamDtoTest { | |||
@Test | |||
public void groupByKey() { | |||
assertThat(ActiveRuleParamDto.groupByKey(Collections.<ActiveRuleParamDto>emptyList())).isEmpty(); | |||
assertThat(ActiveRuleParamDto.groupByKey(Collections.emptyList())).isEmpty(); | |||
Collection<ActiveRuleParamDto> dtos = Arrays.asList( | |||
new ActiveRuleParamDto().setKey("foo"), new ActiveRuleParamDto().setKey("bar") |
@@ -0,0 +1,126 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.List; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.util.Uuids; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import static java.util.Arrays.asList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class DefaultQProfileDaoTest { | |||
@Rule | |||
public DbTester dbTester = DbTester.create(System2.INSTANCE); | |||
private DbSession dbSession = dbTester.getSession(); | |||
private DefaultQProfileDao underTest = dbTester.getDbClient().defaultQProfileDao(); | |||
@Test | |||
public void insertOrUpdate_inserts_row_when_does_not_exist() { | |||
OrganizationDto org = dbTester.organizations().insert(); | |||
QualityProfileDto profile = dbTester.qualityProfiles().insert(org); | |||
DefaultQProfileDto dto = DefaultQProfileDto.from(profile); | |||
underTest.insertOrUpdate(dbSession, dto); | |||
dbSession.commit(); | |||
assertThat(countRows()).isEqualTo(1); | |||
assertThatIsDefault(org, profile); | |||
} | |||
@Test | |||
public void insertOrUpdate_updates_row_when_exists() { | |||
OrganizationDto org = dbTester.organizations().insert(); | |||
String previousQProfileUuid = Uuids.create(); | |||
DefaultQProfileDto dto = new DefaultQProfileDto() | |||
.setLanguage("java") | |||
.setOrganizationUuid(org.getUuid()) | |||
.setQProfileUuid(previousQProfileUuid); | |||
underTest.insertOrUpdate(dbSession, dto); | |||
dbSession.commit(); | |||
String newQProfileUuid = Uuids.create(); | |||
dto.setQProfileUuid(newQProfileUuid); | |||
underTest.insertOrUpdate(dbSession, dto); | |||
dbSession.commit(); | |||
assertThat(countRows()).isEqualTo(1); | |||
assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org, dto.getLanguage())).hasValue(newQProfileUuid); | |||
} | |||
@Test | |||
public void deleteByQProfileUuids_deletes_rows_related_to_specified_profile() { | |||
OrganizationDto org1 = dbTester.organizations().insert(); | |||
OrganizationDto org2 = dbTester.organizations().insert(); | |||
underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org1.getUuid()).setLanguage("java").setQProfileUuid("u1")); | |||
underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org1.getUuid()).setLanguage("js").setQProfileUuid("u2")); | |||
underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org2.getUuid()).setLanguage("java").setQProfileUuid("u3")); | |||
underTest.insertOrUpdate(dbSession, new DefaultQProfileDto().setOrganizationUuid(org2.getUuid()).setLanguage("js").setQProfileUuid("u4")); | |||
underTest.deleteByQProfileUuids(dbSession, asList("u1", "u3")); | |||
dbSession.commit(); | |||
assertThat(countRows()).isEqualTo(2); | |||
assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org1, "java")).isEmpty(); | |||
assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org1, "js")).hasValue("u2"); | |||
assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org2, "java")).isEmpty(); | |||
assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org2, "js")).hasValue("u4"); | |||
} | |||
@Test | |||
public void selectExistingQProfileUuids_filters_defaults() { | |||
OrganizationDto org = dbTester.organizations().insert(); | |||
QualityProfileDto profile1 = dbTester.qualityProfiles().insert(org); | |||
QualityProfileDto profile2 = dbTester.qualityProfiles().insert(org); | |||
dbTester.qualityProfiles().markAsDefault(profile1); | |||
List<String> profileUuids = asList(profile1.getKee(), profile2.getKee(), "other"); | |||
assertThat(underTest.selectExistingQProfileUuids(dbSession, org.getUuid(), profileUuids)) | |||
.containsExactly(profile1.getKee()); | |||
} | |||
@Test | |||
public void isDefault_returns_true_if_profile_is_marked_as_default() { | |||
OrganizationDto org = dbTester.organizations().insert(); | |||
QualityProfileDto profile1 = dbTester.qualityProfiles().insert(org); | |||
QualityProfileDto profile2 = dbTester.qualityProfiles().insert(org); | |||
dbTester.qualityProfiles().markAsDefault(profile1); | |||
assertThat(underTest.isDefault(dbSession, org.getUuid(), profile1.getKee())).isTrue(); | |||
assertThat(underTest.isDefault(dbSession, org.getUuid(), profile2.getKee())).isFalse(); | |||
assertThat(underTest.isDefault(dbSession, org.getUuid(), "does_not_exist")).isFalse(); | |||
} | |||
private void assertThatIsDefault(OrganizationDto org, QualityProfileDto profile) { | |||
assertThat(dbTester.qualityProfiles().selectUuidOfDefaultProfile(org, profile.getLanguage())).hasValue(profile.getKee()); | |||
assertThat(underTest.isDefault(dbSession, org.getUuid(), profile.getKee())).isTrue(); | |||
} | |||
private int countRows() { | |||
return dbTester.countRowsOfTable("default_qprofiles"); | |||
} | |||
} |
@@ -34,12 +34,14 @@ import static org.sonar.api.rule.Severity.MAJOR; | |||
import static org.sonar.db.qualityprofile.ActiveRuleDto.createFor; | |||
public class QualityProfileDbTester { | |||
private final DbTester dbTester; | |||
private final DbClient dbClient; | |||
private final DbSession dbSession; | |||
public QualityProfileDbTester(DbTester db) { | |||
this.dbClient = db.getDbClient(); | |||
this.dbSession = db.getSession(); | |||
public QualityProfileDbTester(DbTester dbTester) { | |||
this.dbTester = dbTester; | |||
this.dbClient = dbTester.getDbClient(); | |||
this.dbSession = dbTester.getSession(); | |||
} | |||
public Optional<QualityProfileDto> selectByKey(String key) { | |||
@@ -98,4 +100,22 @@ public class QualityProfileDbTester { | |||
dbSession.commit(); | |||
return activeRule; | |||
} | |||
public void markAsDefault(QualityProfileDto profile) { | |||
DefaultQProfileDto dto = new DefaultQProfileDto() | |||
.setOrganizationUuid(profile.getOrganizationUuid()) | |||
.setLanguage(profile.getLanguage()) | |||
.setQProfileUuid(profile.getKee()); | |||
dbClient.defaultQProfileDao().insertOrUpdate(dbSession, dto); | |||
dbSession.commit(); | |||
} | |||
public Optional<String> selectUuidOfDefaultProfile(OrganizationDto org, String language) { | |||
return dbTester.select("select qprofile_uuid as \"profileUuid\" " + | |||
" from default_qprofiles " + | |||
" where organization_uuid='" + org.getUuid() + "' and language='" + language + "'") | |||
.stream() | |||
.findFirst() | |||
.map(m -> (String)m.get("profileUuid")); | |||
} | |||
} |
@@ -0,0 +1,79 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.platform.db.migration.version.v65; | |||
import java.sql.SQLException; | |||
import org.sonar.db.Database; | |||
import org.sonar.server.platform.db.migration.def.VarcharColumnDef; | |||
import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; | |||
import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; | |||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||
import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; | |||
import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; | |||
import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; | |||
public class CreateTableDefaultQProfiles extends DdlChange { | |||
public CreateTableDefaultQProfiles(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
public void execute(Context context) throws SQLException { | |||
VarcharColumnDef profileUuidColumn = newVarcharColumnDefBuilder() | |||
.setColumnName("qprofile_uuid") | |||
.setLimit(UUID_SIZE) | |||
.setIsNullable(false) | |||
.setIgnoreOracleUnit(true) | |||
.build(); | |||
context.execute( | |||
new CreateTableBuilder(getDialect(), "default_qprofiles") | |||
.addPkColumn(newVarcharColumnDefBuilder() | |||
.setColumnName("organization_uuid") | |||
.setLimit(UUID_SIZE) | |||
.setIsNullable(false) | |||
.setIgnoreOracleUnit(true) | |||
.build()) | |||
.addPkColumn(newVarcharColumnDefBuilder() | |||
.setColumnName("language") | |||
.setLimit(20) | |||
.setIsNullable(false) | |||
.setIgnoreOracleUnit(true) | |||
.build()) | |||
.addColumn(profileUuidColumn) | |||
.addColumn(newBigIntegerColumnDefBuilder() | |||
.setColumnName("created_at") | |||
.setIsNullable(false) | |||
.build()) | |||
.addColumn(newBigIntegerColumnDefBuilder() | |||
.setColumnName("updated_at") | |||
.setIsNullable(false) | |||
.build()) | |||
.build()); | |||
context.execute( | |||
new CreateIndexBuilder(getDialect()) | |||
.setTable("default_qprofiles") | |||
.setName("uniq_default_qprofiles_uuid") | |||
.addColumn(profileUuidColumn) | |||
.setUnique(true) | |||
.build()); | |||
} | |||
} |
@@ -44,6 +44,8 @@ public class DbVersion65 implements DbVersion { | |||
.add(1715, "Add rules_profiles.is_built_in", AddBuiltInFlagToRulesProfiles.class) | |||
.add(1716, "Set rules_profiles.is_built_in to false", SetRulesProfilesIsBuiltInToFalse.class) | |||
.add(1717, "Make rules_profiles.is_built_in not null", MakeRulesProfilesIsBuiltInNotNullable.class) | |||
.add(1718, "Delete unused loaded_templates on quality profiles", DeleteLoadedTemplatesOnQProfiles.class); | |||
.add(1718, "Delete unused loaded_templates on quality profiles", DeleteLoadedTemplatesOnQProfiles.class) | |||
.add(1719, "Create table default_qprofiles", CreateTableDefaultQProfiles.class) | |||
.add(1720, "Populate table default_qprofiles", PopulateTableDefaultQProfiles.class); | |||
} | |||
} |
@@ -0,0 +1,126 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.platform.db.migration.version.v65; | |||
import java.sql.SQLException; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.Database; | |||
import org.sonar.server.platform.db.migration.step.DataChange; | |||
import org.sonar.server.platform.db.migration.step.MassUpdate; | |||
import org.sonar.server.platform.db.migration.step.Select; | |||
/** | |||
* Move the list of profiles marked as "default" from rules_profiles.is_default | |||
* to the table default_qprofiles. | |||
*/ | |||
public class PopulateTableDefaultQProfiles extends DataChange { | |||
private final System2 system2; | |||
public PopulateTableDefaultQProfiles(Database db, System2 system2) { | |||
super(db); | |||
this.system2 = system2; | |||
} | |||
@Override | |||
protected void execute(Context context) throws SQLException { | |||
Set<OrgLang> buggyOrgs = selectOrgsWithMultipleDefaultProfiles(context); | |||
Set<OrgLang> processedBuggyOrgs = new HashSet<>(); | |||
long now = system2.now(); | |||
MassUpdate massUpdate = context.prepareMassUpdate(); | |||
massUpdate.select("select p.organization_uuid, p.language, p.kee from rules_profiles p " + | |||
" where p.is_default = ? " + | |||
" and not exists (select 1 from default_qprofiles dp where dp.organization_uuid = p.organization_uuid and dp.language = p.language)" + | |||
" order by id") | |||
.setBoolean(1, true); | |||
massUpdate.update("insert into default_qprofiles" + | |||
" (organization_uuid, language, qprofile_uuid, created_at, updated_at) values (?, ?, ?, ?, ?)"); | |||
massUpdate.rowPluralName("default_qprofiles"); | |||
massUpdate.execute((row, update) -> { | |||
OrgLang pk = new OrgLang(row.getString(1), row.getString(2)); | |||
String profileUuid = row.getString(3); | |||
boolean isBuggy = buggyOrgs.contains(pk); | |||
if (isBuggy && processedBuggyOrgs.contains(pk)) { | |||
// profile is ignored. There's already one marked as default. | |||
return false; | |||
} | |||
update.setString(1, pk.orgUuid); | |||
update.setString(2, pk.language); | |||
update.setString(3, profileUuid); | |||
update.setLong(4, now); | |||
update.setLong(5, now); | |||
if (isBuggy) { | |||
processedBuggyOrgs.add(pk); | |||
} | |||
return true; | |||
}); | |||
} | |||
/** | |||
* By design the table rules_profiles does not enforce to have a single | |||
* profile marked as default for an organization and language. | |||
* This method returns the buggy rows. | |||
*/ | |||
private Set<OrgLang> selectOrgsWithMultipleDefaultProfiles(Context context) throws SQLException { | |||
Select rows = context.prepareSelect("select organization_uuid, language from rules_profiles " + | |||
" where is_default = ? " + | |||
" group by organization_uuid, language " + | |||
" having count(kee) > 1 ") | |||
.setBoolean(1, true); | |||
return new HashSet<>(rows.list(row -> new OrgLang(row.getString(1), row.getString(2)))); | |||
} | |||
private static class OrgLang { | |||
private final String orgUuid; | |||
private final String language; | |||
private OrgLang(String orgUuid, String language) { | |||
this.orgUuid = orgUuid; | |||
this.language = language; | |||
} | |||
@Override | |||
public boolean equals(Object o) { | |||
if (this == o) { | |||
return true; | |||
} | |||
if (o == null || getClass() != o.getClass()) { | |||
return false; | |||
} | |||
OrgLang orgLang = (OrgLang) o; | |||
if (!orgUuid.equals(orgLang.orgUuid)) { | |||
return false; | |||
} | |||
return language.equals(orgLang.language); | |||
} | |||
@Override | |||
public int hashCode() { | |||
int result = orgUuid.hashCode(); | |||
result = 31 * result + language.hashCode(); | |||
return result; | |||
} | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.platform.db.migration.version.v65; | |||
import java.sql.SQLException; | |||
import java.sql.Types; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.db.CoreDbTester; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class CreateTableDefaultQProfilesTest { | |||
private static final String TABLE = "default_qprofiles"; | |||
@Rule | |||
public final CoreDbTester db = CoreDbTester.createForSchema(CreateTableDefaultQProfilesTest.class, "empty.sql"); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private CreateTableDefaultQProfiles underTest = new CreateTableDefaultQProfiles(db.database()); | |||
@Test | |||
public void creates_table_on_empty_db() throws SQLException { | |||
underTest.execute(); | |||
assertThat(db.countRowsOfTable(TABLE)).isEqualTo(0); | |||
db.assertColumnDefinition(TABLE, "organization_uuid", Types.VARCHAR, 40, false); | |||
db.assertColumnDefinition(TABLE, "language", Types.VARCHAR, 20, false); | |||
db.assertColumnDefinition(TABLE, "qprofile_uuid", Types.VARCHAR, 40, false); | |||
db.assertColumnDefinition(TABLE, "created_at", Types.BIGINT, null, false); | |||
db.assertColumnDefinition(TABLE, "updated_at", Types.BIGINT, null, false); | |||
db.assertPrimaryKey(TABLE, "pk_" + TABLE, "organization_uuid", "language"); | |||
db.assertUniqueIndex(TABLE, "uniq_default_qprofiles_uuid", "qprofile_uuid"); | |||
} | |||
@Test | |||
public void migration_is_not_reentrant() throws SQLException { | |||
underTest.execute(); | |||
expectedException.expect(IllegalStateException.class); | |||
underTest.execute(); | |||
} | |||
} |
@@ -35,6 +35,6 @@ public class DbVersion65Test { | |||
@Test | |||
public void verify_migration_count() { | |||
verifyMigrationCount(underTest, 19); | |||
verifyMigrationCount(underTest, 21); | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.platform.db.migration.version.v65; | |||
import java.sql.SQLException; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; | |||
import org.sonar.db.CoreDbTester; | |||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; | |||
public class PopulateTableDefaultQProfilesTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public CoreDbTester db = CoreDbTester.createForSchema(PopulateTableDefaultQProfilesTest.class, "initial.sql"); | |||
private System2 system2 = new AlwaysIncreasingSystem2(); | |||
private PopulateTableDefaultQProfiles underTest = new PopulateTableDefaultQProfiles(db.database(), system2); | |||
@Test | |||
public void migration_is_reentrant() throws SQLException { | |||
insertRulesProfile("ORG_1", "java", "u1", true); | |||
insertRulesProfile("ORG_2", "js", "u2", true); | |||
// org1 is already processed | |||
insertDefaultQProfile("ORG_1", "java", "u1"); | |||
underTest.execute(); | |||
assertThat(countRows()).isEqualTo(2); | |||
assertThat(selectDefaultProfile("ORG_1", "java")).isEqualTo("u1"); | |||
assertThat(selectDefaultProfile("ORG_2", "js")).isEqualTo("u2"); | |||
} | |||
@Test | |||
public void DEFAULT_QPROFILES_is_populated_by_copying_the_RULES_PROFILES_marked_as_default() throws SQLException { | |||
insertRulesProfile("ORG_1", "java", "u1", false); | |||
insertRulesProfile("ORG_1", "java", "u2", true); | |||
insertRulesProfile("ORG_1", "js", "u3", true); | |||
underTest.execute(); | |||
assertThat(countRows()).isEqualTo(2); | |||
assertThat(selectDefaultProfile("ORG_1", "java")).isEqualTo("u2"); | |||
assertThat(selectDefaultProfile("ORG_1", "js")).isEqualTo("u3"); | |||
} | |||
@Test | |||
public void duplicated_rows_of_table_RULES_PROFILES_are_ignored() throws SQLException { | |||
// two java profiles are marked as default. | |||
// The second one (as ordered by id) is ignored. | |||
insertRulesProfile("ORG_1", "java", "u1", false); | |||
insertRulesProfile("ORG_1", "java", "u2", true); | |||
insertRulesProfile("ORG_1", "java", "u3", true); | |||
underTest.execute(); | |||
assertThat(countRows()).isEqualTo(1); | |||
assertThat(selectDefaultProfile("ORG_1", "java")).isEqualTo("u2"); | |||
} | |||
private int countRows() { | |||
return db.countRowsOfTable("default_qprofiles"); | |||
} | |||
private void insertRulesProfile(String orgUuid, String language, String uuid, boolean isDefault) { | |||
db.executeInsert("RULES_PROFILES", | |||
"NAME", "name_" + uuid, | |||
"KEE", uuid, | |||
"ORGANIZATION_UUID", orgUuid, | |||
"LANGUAGE", language, | |||
"IS_DEFAULT", isDefault, | |||
"IS_BUILT_IN", true); | |||
} | |||
private void insertDefaultQProfile(String orgUuid, String language, String uuid) { | |||
db.executeInsert("DEFAULT_QPROFILES", | |||
"ORGANIZATION_UUID", orgUuid, | |||
"LANGUAGE", language, | |||
"QPROFILE_UUID", uuid); | |||
} | |||
private String selectDefaultProfile(String orgUuid, String language) { | |||
return (String)db.selectFirst("select qprofile_uuid as QPROFILE_UUID from default_qprofiles where organization_uuid='" + orgUuid + "' and language='" + language + "'") | |||
.get("QPROFILE_UUID"); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
CREATE TABLE "RULES_PROFILES" ( | |||
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), | |||
"NAME" VARCHAR(100) NOT NULL, | |||
"LANGUAGE" VARCHAR(20), | |||
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL, | |||
"KEE" VARCHAR(255) NOT NULL, | |||
"PARENT_KEE" VARCHAR(255), | |||
"RULES_UPDATED_AT" VARCHAR(100), | |||
"IS_DEFAULT" BOOLEAN NOT NULL DEFAULT FALSE, | |||
"CREATED_AT" TIMESTAMP, | |||
"UPDATED_AT" TIMESTAMP, | |||
"LAST_USED" BIGINT, | |||
"USER_UPDATED_AT" BIGINT, | |||
"IS_BUILT_IN" BOOLEAN NOT NULL | |||
); | |||
CREATE UNIQUE INDEX "UNIQ_QPROF_KEY" ON "RULES_PROFILES" ("KEE"); | |||
CREATE TABLE "DEFAULT_QPROFILES" ( | |||
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL, | |||
"LANGUAGE" VARCHAR(20) NOT NULL, | |||
"QPROFILE_UUID" VARCHAR(40) NOT NULL, | |||
"CREATED_AT" BIGINT, | |||
"UPDATED_AT" BIGINT | |||
); | |||
CREATE PRIMARY KEY ON "DEFAULT_QPROFILES" ("ORGANIZATION_UUID", "LANGUAGE"); |
@@ -46,6 +46,7 @@ import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.qualityprofile.ActiveRuleDto; | |||
import org.sonar.db.qualityprofile.ActiveRuleKey; | |||
import org.sonar.db.qualityprofile.ActiveRuleParamDto; | |||
import org.sonar.db.qualityprofile.DefaultQProfileDto; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.rule.RuleParamDto; | |||
@@ -90,7 +91,7 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert { | |||
} | |||
} | |||
private QualityProfileDto insertQualityProfile(DbSession session, BuiltInQProfile builtInQProfile, OrganizationDto organization, Date now) { | |||
private QualityProfileDto insertQualityProfile(DbSession dbSession, BuiltInQProfile builtInQProfile, OrganizationDto organization, Date now) { | |||
QualityProfileDto profileDto = QualityProfileDto.createFor(uuidFactory.create()) | |||
.setName(builtInQProfile.getName()) | |||
.setOrganizationUuid(organization.getUuid()) | |||
@@ -98,7 +99,10 @@ public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert { | |||
.setDefault(builtInQProfile.isDefault()) | |||
.setIsBuiltIn(true) | |||
.setRulesUpdatedAtAsDate(now); | |||
dbClient.qualityProfileDao().insert(session, profileDto); | |||
dbClient.qualityProfileDao().insert(dbSession, profileDto); | |||
if (builtInQProfile.isDefault()) { | |||
dbClient.defaultQProfileDao().insertOrUpdate(dbSession, DefaultQProfileDto.from(profileDto)); | |||
} | |||
return profileDto; | |||
} | |||
@@ -29,6 +29,7 @@ import org.sonar.core.util.UuidFactory; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.qualityprofile.DefaultQProfileDto; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; | |||
@@ -102,6 +103,9 @@ public class QProfileFactory { | |||
.setIsBuiltIn(isBuiltIn) | |||
.setRulesUpdatedAtAsDate(now); | |||
db.qualityProfileDao().insert(dbSession, dto); | |||
if (isDefault) { | |||
db.defaultQProfileDao().insertOrUpdate(dbSession, DefaultQProfileDto.from(dto)); | |||
} | |||
return dto; | |||
} | |||
@@ -119,6 +123,7 @@ public class QProfileFactory { | |||
db.activeRuleDao().deleteParametersByProfileKeys(dbSession, profileKeys); | |||
db.activeRuleDao().deleteByProfileKeys(dbSession, profileKeys); | |||
db.qProfileChangeDao().deleteByProfileKeys(dbSession, profileKeys); | |||
db.defaultQProfileDao().deleteByQProfileUuids(dbSession, profileKeys); | |||
db.qualityProfileDao().deleteByKeys(dbSession, profileKeys); | |||
dbSession.commit(); | |||
activeRuleIndexer.deleteByProfileKeys(profileKeys); |
@@ -83,6 +83,7 @@ public class CopyAction implements QProfileWsAction { | |||
userSession.checkPermission(OrganizationPermission.ADMINISTER_QUALITY_PROFILES, sourceProfile.getOrganizationUuid()); | |||
QualityProfileDto copiedProfile = profileCopier.copyToName(dbSession, sourceProfile, newName); | |||
boolean isDefault = dbClient.defaultQProfileDao().isDefault(dbSession, copiedProfile.getOrganizationUuid(), copiedProfile.getKee()); | |||
String languageKey = copiedProfile.getLanguage(); | |||
Language language = languages.get(copiedProfile.getLanguage()); | |||
@@ -93,7 +94,7 @@ public class CopyAction implements QProfileWsAction { | |||
.prop("name", copiedProfile.getName()) | |||
.prop("language", languageKey) | |||
.prop("languageName", language == null ? null : language.getName()) | |||
.prop("isDefault", copiedProfile.isDefault()) | |||
.prop("isDefault", isDefault) | |||
.prop("isInherited", parentKey != null) | |||
.prop("parentKey", parentKey) | |||
.endObject().close(); |
@@ -19,7 +19,9 @@ | |||
*/ | |||
package org.sonar.server.qualityprofile.ws; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import java.util.stream.Stream; | |||
import org.sonar.api.resources.Languages; | |||
import org.sonar.api.server.ws.Request; | |||
@@ -74,7 +76,7 @@ public class DeleteAction implements QProfileWsAction { | |||
userSession.checkPermission(ADMINISTER_QUALITY_PROFILES, profile.getOrganizationUuid()); | |||
List<QualityProfileDto> descendants = selectDescendants(dbSession, profile); | |||
ensureNoneIsMarkedAsDefault(profile, descendants); | |||
ensureNoneIsMarkedAsDefault(dbSession, profile, descendants); | |||
profileFactory.deleteByKeys(dbSession, toKeys(profile, descendants)); | |||
dbSession.commit(); | |||
@@ -86,10 +88,16 @@ public class DeleteAction implements QProfileWsAction { | |||
return dbClient.qualityProfileDao().selectDescendants(dbSession, profile.getKey()); | |||
} | |||
private static void ensureNoneIsMarkedAsDefault(QualityProfileDto profile, List<QualityProfileDto> descendants) { | |||
checkArgument(!profile.isDefault(), "Profile '%s' cannot be deleted because it is marked as default", profile.getName()); | |||
private void ensureNoneIsMarkedAsDefault(DbSession dbSession, QualityProfileDto profile, List<QualityProfileDto> descendants) { | |||
Set<String> allUuids = new HashSet<>(); | |||
allUuids.add(profile.getKee()); | |||
descendants.forEach(p -> allUuids.add(p.getKee())); | |||
Set<String> uuidsOfDefaultProfiles = dbClient.defaultQProfileDao().selectExistingQProfileUuids(dbSession, profile.getOrganizationUuid(), allUuids); | |||
checkArgument(!uuidsOfDefaultProfiles.contains(profile.getKee()), "Profile '%s' cannot be deleted because it is marked as default", profile.getName()); | |||
descendants.stream() | |||
.filter(QualityProfileDto::isDefault) | |||
.filter(p -> uuidsOfDefaultProfiles.contains(p.getKee())) | |||
.findFirst() | |||
.ifPresent(p -> { | |||
throw new IllegalArgumentException(String.format("Profile '%s' cannot be deleted because its descendant named '%s' is marked as default", profile.getName(), p.getName())); |
@@ -27,6 +27,7 @@ import org.sonar.api.server.ws.WebService.NewAction; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.qualityprofile.DefaultQProfileDto; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import org.sonar.server.user.UserSession; | |||
@@ -77,11 +78,12 @@ public class SetDefaultAction implements QProfileWsAction { | |||
response.noContent(); | |||
} | |||
public void setDefault(DbSession session, OrganizationDto organization, QualityProfileDto qualityProfile) { | |||
QualityProfileDto previousDefault = dbClient.qualityProfileDao().selectDefaultProfile(session, organization, qualityProfile.getLanguage()); | |||
public void setDefault(DbSession dbSession, OrganizationDto organization, QualityProfileDto profile) { | |||
QualityProfileDto previousDefault = dbClient.qualityProfileDao().selectDefaultProfile(dbSession, organization, profile.getLanguage()); | |||
if (previousDefault != null) { | |||
dbClient.qualityProfileDao().update(session, previousDefault.setDefault(false)); | |||
dbClient.qualityProfileDao().update(dbSession, previousDefault.setDefault(false)); | |||
} | |||
dbClient.qualityProfileDao().update(session, qualityProfile.setDefault(true)); | |||
dbClient.qualityProfileDao().update(dbSession, profile.setDefault(true)); | |||
dbClient.defaultQProfileDao().insertOrUpdate(dbSession, DefaultQProfileDto.from(profile)); | |||
} | |||
} |
@@ -144,7 +144,7 @@ public class CopyActionTest { | |||
" \"name\": \"" + targetProfile.getName() + "\"," + | |||
" \"language\": \"lang1\"," + | |||
" \"languageName\": \"Lang1\"," + | |||
" \"isDefault\": " + targetProfile.isDefault() + "," + | |||
" \"isDefault\": false," + | |||
" \"isInherited\": false" + | |||
"}"); | |||
QualityProfileDto loadedProfile = db.getDbClient().qualityProfileDao().selectByKey(db.getSession(), targetProfile.getKey()); |
@@ -240,7 +240,8 @@ public class DeleteActionTest { | |||
@Test | |||
public void throw_ISE_if_deleting_default_profile() { | |||
OrganizationDto organization = dbTester.organizations().insert(); | |||
QualityProfileDto profile = createDefaultProfile(organization); | |||
QualityProfileDto profile = createProfile(organization); | |||
dbTester.qualityProfiles().markAsDefault(profile); | |||
logInAsQProfileAdministrator(organization); | |||
expectedException.expect(IllegalArgumentException.class); | |||
@@ -256,8 +257,9 @@ public class DeleteActionTest { | |||
public void throw_ISE_if_a_descendant_is_marked_as_default() { | |||
OrganizationDto organization = dbTester.organizations().insert(); | |||
QualityProfileDto parentProfile = createProfile(organization); | |||
QualityProfileDto childProfile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE), p -> p.setDefault(true), | |||
QualityProfileDto childProfile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE), | |||
p -> p.setParentKee(parentProfile.getKey())); | |||
dbTester.qualityProfiles().markAsDefault(childProfile); | |||
logInAsQProfileAdministrator(organization); | |||
expectedException.expect(IllegalArgumentException.class); | |||
@@ -286,10 +288,6 @@ public class DeleteActionTest { | |||
} | |||
private QualityProfileDto createProfile(OrganizationDto organization) { | |||
return dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE), p -> p.setDefault(false)); | |||
} | |||
private QualityProfileDto createDefaultProfile(OrganizationDto organization) { | |||
return dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE), p -> p.setDefault(true)); | |||
return dbTester.qualityProfiles().insert(organization, p -> p.setLanguage(A_LANGUAGE)); | |||
} | |||
} |