Browse Source

SONAR-9304 share built-in profiles among organizations

tags/6.5-M1
Simon Brandhof 7 years ago
parent
commit
bab99c01f6
30 changed files with 848 additions and 29 deletions
  1. 1
    1
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  2. 1
    0
      server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
  3. 11
    0
      server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
  4. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
  5. 7
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
  6. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
  7. 63
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/DefaultQProfileDao.java
  8. 71
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/DefaultQProfileDto.java
  9. 36
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/DefaultQProfileMapper.java
  10. 52
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/DefaultQProfileMapper.xml
  11. 2
    2
      server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileChangeMapper.xml
  12. 1
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
  13. 1
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/ActiveRuleParamDtoTest.java
  14. 126
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/DefaultQProfileDaoTest.java
  15. 23
    3
      server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileDbTester.java
  16. 79
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfiles.java
  17. 3
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65.java
  18. 126
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfiles.java
  19. 66
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest.java
  20. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65Test.java
  21. 111
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest.java
  22. 0
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest/empty.sql
  23. 26
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest/initial.sql
  24. 6
    2
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java
  25. 5
    0
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java
  26. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/CopyAction.java
  27. 12
    4
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/DeleteAction.java
  28. 6
    4
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SetDefaultAction.java
  29. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/CopyActionTest.java
  30. 5
    7
      server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeleteActionTest.java

+ 1
- 1
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java View File

@@ -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
);

+ 1
- 0
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java View File

@@ -52,6 +52,7 @@ public final class SqTables {
"ce_queue",
"ce_task_input",
"ce_scanner_context",
"default_qprofiles",
"duplications_index",
"events",
"file_sources",

+ 11
- 0
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl View File

@@ -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,

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java View File

@@ -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,

+ 7
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java View File

@@ -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);
}

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java View File

@@ -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,

+ 63
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/DefaultQProfileDao.java View File

@@ -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);
}
}

+ 71
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/DefaultQProfileDto.java View File

@@ -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();
}
}

+ 36
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/DefaultQProfileMapper.java View File

@@ -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);
}

+ 52
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/DefaultQProfileMapper.xml View File

@@ -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>


+ 2
- 2
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileChangeMapper.xml View File

@@ -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>


+ 1
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java View File

@@ -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);
}
}

+ 1
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/ActiveRuleParamDtoTest.java View File

@@ -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")

+ 126
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/DefaultQProfileDaoTest.java View File

@@ -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");
}
}

+ 23
- 3
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileDbTester.java View File

@@ -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"));
}
}

+ 79
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfiles.java View File

@@ -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());
}
}

+ 3
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65.java View File

@@ -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);
}
}

+ 126
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfiles.java View File

@@ -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;
}
}
}

+ 66
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest.java View File

@@ -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();
}

}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/DbVersion65Test.java View File

@@ -35,6 +35,6 @@ public class DbVersion65Test {

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 19);
verifyMigrationCount(underTest, 21);
}
}

+ 111
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest.java View File

@@ -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
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/CreateTableDefaultQProfilesTest/empty.sql View File


+ 26
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v65/PopulateTableDefaultQProfilesTest/initial.sql View File

@@ -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");

+ 6
- 2
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java View File

@@ -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;
}


+ 5
- 0
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java View File

@@ -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);

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/CopyAction.java View File

@@ -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();

+ 12
- 4
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/DeleteAction.java View File

@@ -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()));

+ 6
- 4
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SetDefaultAction.java View File

@@ -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));
}
}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/CopyActionTest.java View File

@@ -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());

+ 5
- 7
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeleteActionTest.java View File

@@ -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));
}
}

Loading…
Cancel
Save