diff options
58 files changed, 3090 insertions, 48 deletions
diff --git a/build.gradle b/build.gradle index 767692f9a90..b1c5a7ed5aa 100644 --- a/build.gradle +++ b/build.gradle @@ -295,6 +295,8 @@ subprojects { dependency 'javax.xml.bind:jaxb-api:2.3.0' dependency 'junit:junit:4.13' dependency 'org.junit.jupiter:junit-jupiter-api:5.6.0' + dependency 'org.xmlunit:xmlunit-core:2.6.4' + dependency 'org.xmlunit:xmlunit-matchers:2.6.4' dependency 'net.jpountz.lz4:lz4:1.3.0' dependency 'net.lightbody.bmp:littleproxy:1.1.0-beta-bmp-17' dependency 'org.awaitility:awaitility:4.0.2' diff --git a/server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeTaskSubmit.java b/server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeTaskSubmit.java index 71e7b38bf5f..9d7db56caf3 100644 --- a/server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeTaskSubmit.java +++ b/server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeTaskSubmit.java @@ -25,7 +25,9 @@ import java.util.Optional; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; +import org.sonar.db.project.ProjectDto; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Strings.emptyToNull; @@ -128,6 +130,18 @@ public final class CeTaskSubmit { return new Component(uuid, firstNonNull(dto.getMainBranchProjectUuid(), uuid)); } + public static Component fromDto(ProjectDto dto) { + return new Component(dto.getUuid(), dto.getUuid()); + } + + public static Component fromDto(BranchDto dto) { + return new Component(dto.getUuid(), dto.getProjectUuid()); + } + + public static Component fromProjectUuid(String projectUuid) { + return new Component(projectUuid, projectUuid); + } + public String getUuid() { return uuid; } diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index 4c705ec451d..3cb3fff44c5 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -39,6 +39,8 @@ public final class SqTables { "alm_settings", "alm_pats", "analysis_properties", + "app_branch_project_branch", + "app_projects", "ce_activity", "ce_queue", "ce_task_characteristics", diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index 205d02d18e2..a76ef07ffc6 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -35,6 +35,7 @@ import org.sonar.db.ce.CeTaskCharacteristicDao; import org.sonar.db.ce.CeTaskInputDao; import org.sonar.db.ce.CeTaskMessageDao; import org.sonar.db.component.AnalysisPropertiesDao; +import org.sonar.db.component.ApplicationProjectsDao; import org.sonar.db.component.BranchDao; import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentKeyUpdaterDao; @@ -101,6 +102,7 @@ public class DaoModule extends Module { ActiveRuleDao.class, AnalysisPropertiesDao.class, AuthorizationDao.class, + ApplicationProjectsDao.class, BranchDao.class, CeActivityDao.class, CeQueueDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 78d717373ed..bd6c3c878d6 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -33,6 +33,7 @@ import org.sonar.db.ce.CeTaskCharacteristicDao; import org.sonar.db.ce.CeTaskInputDao; import org.sonar.db.ce.CeTaskMessageDao; import org.sonar.db.component.AnalysisPropertiesDao; +import org.sonar.db.component.ApplicationProjectsDao; import org.sonar.db.component.BranchDao; import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentKeyUpdaterDao; @@ -166,6 +167,7 @@ public class DbClient { private final SessionTokensDao sessionTokensDao; private final SamlMessageIdDao samlMessageIdDao; private final UserDismissedMessagesDao userDismissedMessagesDao; + private final ApplicationProjectsDao applicationProjectsDao; public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) { this.database = database; @@ -245,6 +247,7 @@ public class DbClient { sessionTokensDao = getDao(map, SessionTokensDao.class); samlMessageIdDao = getDao(map, SamlMessageIdDao.class); userDismissedMessagesDao = getDao(map, UserDismissedMessagesDao.class); + applicationProjectsDao = getDao(map, ApplicationProjectsDao.class); } public DbSession openSession(boolean batch) { @@ -267,6 +270,10 @@ public class DbClient { return almPatDao; } + public ApplicationProjectsDao applicationProjectsDao() { + return applicationProjectsDao; + } + public ProjectAlmSettingDao projectAlmSettingDao() { return projectAlmSettingDao; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 8c1f4a3b487..77205277bb5 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -47,6 +47,7 @@ import org.sonar.db.ce.CeTaskCharacteristicMapper; import org.sonar.db.ce.CeTaskInputMapper; import org.sonar.db.ce.CeTaskMessageMapper; import org.sonar.db.component.AnalysisPropertiesMapper; +import org.sonar.db.component.ApplicationProjectsMapper; import org.sonar.db.component.BranchMapper; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDtoWithSnapshotId; @@ -231,6 +232,7 @@ public class MyBatis implements Startable { AlmPatMapper.class, AlmSettingMapper.class, AnalysisPropertiesMapper.class, + ApplicationProjectsMapper.class, AuthorizationMapper.class, BranchMapper.class, CeActivityMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ApplicationProjectsDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ApplicationProjectsDao.java new file mode 100644 index 00000000000..713d67dd5dd --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ApplicationProjectsDao.java @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.component; + +import java.util.Collection; +import java.util.Set; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; +import org.sonar.db.project.ProjectDto; + +public class ApplicationProjectsDao implements Dao { + private final System2 system2; + private final UuidFactory uuidFactory; + + public ApplicationProjectsDao(System2 system2, UuidFactory uuidFactory) { + this.system2 = system2; + this.uuidFactory = uuidFactory; + } + + public void addProject(DbSession dbSession, String applicationUuid, String projectUuid) { + getMapper(dbSession).addProject(uuidFactory.create(), applicationUuid, projectUuid, system2.now()); + } + + public void removeApplicationProjectsByApplicationAndProject(DbSession dbSession, String applicationUuid, String projectUuid) { + getMapper(dbSession).removeApplicationBranchProjectBranchesByApplicationAndProject(applicationUuid, projectUuid); + getMapper(dbSession).removeApplicationProjectsByApplicationAndProject(applicationUuid, projectUuid); + } + + public int countApplicationProjects(DbSession dbSession, String applicationUuid) { + return getMapper(dbSession).countApplicationProjects(applicationUuid); + } + + public Set<ProjectDto> selectProjects(DbSession dbSession, String applicationUuid) { + return getMapper(dbSession).selectProjects(applicationUuid); + } + + public void remove(DbSession dbSession, String applicationUuid) { + getMapper(dbSession).removeApplicationBranchProjectBranchesByApplication(applicationUuid); + getMapper(dbSession).removeApplicationProjectsByApplication(applicationUuid); + } + + public void addProjectBranchToAppBranch(DbSession dbSession, BranchDto applicationBranch, BranchDto projectBranch) { + getMapper(dbSession).addProjectBranchToAppBranch( + uuidFactory.create(), + applicationBranch.getProjectUuid(), + applicationBranch.getUuid(), + projectBranch.getProjectUuid(), + projectBranch.getUuid(), + system2.now()); + } + + public void addProjectBranchToAppBranch(DbSession dbSession, String applicationUuid, String applicationBranchUuid, String projectUuid, String projectBranchUuid) { + getMapper(dbSession).addProjectBranchToAppBranch( + uuidFactory.create(), + applicationUuid, + applicationBranchUuid, + projectUuid, + projectBranchUuid, + system2.now()); + } + + public void removeProjectBranchFromAppBranch(DbSession dbSession, String applicationBranchUuid, String projectBranchUuid) { + getMapper(dbSession).removeProjectBranchFromAppBranch(applicationBranchUuid, projectBranchUuid); + } + + public Set<BranchDto> selectProjectBranchesFromAppBranch(DbSession dbSession, String applicationBranchUuid) { + return getMapper(dbSession).selectProjectBranchesFromAppBranch(applicationBranchUuid); + } + + public Set<ProjectDto> selectApplicationsFromProjectBranch(DbSession dbSession, String projectUuid, String branchKey) { + return getMapper(dbSession).selectApplicationsFromProjectBranch(projectUuid, branchKey); + } + + public Set<ProjectDto> selectApplicationsFromProjects(DbSession dbSession, Collection<String> projectUuids) { + return getMapper(dbSession).selectApplicationsFromProjects(projectUuids); + } + + private static ApplicationProjectsMapper getMapper(DbSession session) { + return session.getMapper(ApplicationProjectsMapper.class); + } + + public void updateApplicationBranchName(DbSession dbSession, String applicationBranchUuid, String newName) { + getMapper(dbSession).updateApplicationBranchName(applicationBranchUuid, newName); + } + + public void removeAllProjectBranchesOfAppBranch(DbSession dbSession, String applicationBranchUuid) { + getMapper(dbSession).removeAllProjectBranchesOfAppBranch(applicationBranchUuid); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ApplicationProjectsMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ApplicationProjectsMapper.java new file mode 100644 index 00000000000..a45cb6529bc --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ApplicationProjectsMapper.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.component; + +import java.util.Collection; +import java.util.Set; +import org.apache.ibatis.annotations.Param; +import org.sonar.db.project.ProjectDto; + +public interface ApplicationProjectsMapper { + void addProject( + @Param("uuid") String uuid, + @Param("applicationUuid") String applicationUuid, + @Param("projectUuid") String projectUuid, + @Param("now") long now); + + void removeApplicationBranchProjectBranchesByApplicationAndProject( + @Param("applicationUuid") String applicationUuid, + @Param("projectUuid") String projectUuid); + + void removeApplicationProjectsByApplicationAndProject( + @Param("applicationUuid") String applicationUuid, + @Param("projectUuid") String projectUuid); + + Set<ProjectDto> selectProjects(@Param("applicationUuid") String applicationUuid); + + void removeApplicationProjectsByApplication(String applicationUuid); + + void removeApplicationBranchProjectBranchesByApplication(String applicationUuid); + + void addProjectBranchToAppBranch( + @Param("uuid") String uuid, + @Param("applicationUuid") String applicationUuid, + @Param("applicationBranchUuid") String applicationBranchUuid, + @Param("projectUuid") String projectUuid, + @Param("projectBranchUuid") String projectBranchUuid, + @Param("now") long now); + + void removeProjectBranchFromAppBranch(@Param("applicationBranchUuid") String applicationBranchUuid, @Param("projectBranchUuid") String projectBranchUuid); + + Set<BranchDto> selectProjectBranchesFromAppBranch(@Param("applicationBranchUuid") String applicationBranchUuid); + + int countApplicationProjects(@Param("applicationUuid") String applicationUuid); + + void updateApplicationBranchName(@Param("uuid") String uuid, @Param("newName") String newName); + + Set<ProjectDto> selectApplicationsFromProjectBranch(@Param("projectUuid") String projectUuid, @Param("branchKey") String branchKey); + + Set<ProjectDto> selectApplicationsFromProjects(@Param("projectUuids") Collection<String> projectUuids); + + void removeAllProjectBranchesOfAppBranch(@Param("applicationBranchUuid") String applicationBranchUuid); +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java index eea97147e49..3abeac13c01 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java @@ -20,7 +20,6 @@ package org.sonar.db.component; import java.util.Collection; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -30,6 +29,7 @@ import org.sonar.db.Dao; import org.sonar.db.DbSession; import org.sonar.db.project.ProjectDto; +import static java.util.Collections.emptyList; import static org.sonar.db.DatabaseUtils.executeLargeInputs; public class BranchDao implements Dao { @@ -68,7 +68,7 @@ public class BranchDao implements Dao { public List<BranchDto> selectByBranchKeys(DbSession dbSession, Map<String, String> branchKeyByProjectUuid) { if (branchKeyByProjectUuid.isEmpty()) { - return Collections.emptyList(); + return emptyList(); } return mapper(dbSession).selectByBranchKeys(branchKeyByProjectUuid); } @@ -94,6 +94,9 @@ public class BranchDao implements Dao { } public List<BranchDto> selectByUuids(DbSession session, Collection<String> uuids) { + if (uuids.isEmpty()) { + return emptyList(); + } return executeLargeInputs(uuids, mapper(session)::selectByUuids); } @@ -103,7 +106,7 @@ public class BranchDao implements Dao { public List<String> selectProjectUuidsWithIssuesNeedSync(DbSession session, Collection<String> uuids) { if (uuids.isEmpty()) { - return Collections.emptyList(); + return emptyList(); } return executeLargeInputs(uuids, mapper(session)::selectProjectUuidsWithIssuesNeedSync); @@ -146,6 +149,10 @@ public class BranchDao implements Dao { return mapper(dbSession).updateNeedIssueSync(branchUuid, needIssueSync, now); } + public void deleteBranch(DbSession dbSession, String projectUuid, String branchKey) { + mapper(dbSession).deleteBranch(projectUuid, branchKey); + } + public boolean doAnyOfComponentsNeedIssueSync(DbSession session, List<String> components) { if (!components.isEmpty()) { List<Boolean> result = new LinkedList<>(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java index c44507015fd..db9667426f7 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java @@ -68,4 +68,5 @@ public interface BranchMapper { short doAnyOfComponentsNeedIssueSync(@Param("componentKeys") List<String> components); + void deleteBranch(@Param("projectUuid") String projectUuid, @Param("branchKey") String branchKey); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/SelectionMode.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SelectionMode.java new file mode 100644 index 00000000000..6a636de8542 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SelectionMode.java @@ -0,0 +1,27 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.component; + +public enum SelectionMode { + MANUAL_MEASURE, + REGEXP, + TAGS, + REMAINING_PROJECTS +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java index f70760d6541..0f62ef98e7d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java @@ -34,7 +34,6 @@ import org.sonar.db.Dao; import org.sonar.db.DbSession; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.FluentIterable.from; import static java.util.Objects.requireNonNull; import static org.sonar.db.DatabaseUtils.executeLargeInputs; @@ -125,9 +124,7 @@ public class SnapshotDao implements Dao { */ @CheckForNull public ViewsSnapshotDto selectSnapshotBefore(String componentUuid, long date, DbSession dbSession) { - return from(mapper(dbSession).selectSnapshotBefore(componentUuid, date)) - .first() - .orNull(); + return mapper(dbSession).selectSnapshotBefore(componentUuid, date).stream().findFirst().orElse(null); } private static SnapshotMapper mapper(DbSession session) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDao.java index 46dd8218e9a..8d2509cf449 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDao.java @@ -50,6 +50,10 @@ public class ProjectDao implements Dao { return Optional.ofNullable(mapper(session).selectProjectOrAppByKey(key)); } + public List<ProjectDto> selectAllApplications(DbSession session){ + return mapper(session).selectAllApplications(); + } + public List<ProjectDto> selectProjectsByKeys(DbSession session, Set<String> keys) { if (keys.isEmpty()) { return Collections.emptyList(); @@ -57,6 +61,13 @@ public class ProjectDao implements Dao { return mapper(session).selectProjectsByKeys(keys); } + public List<ProjectDto> selectApplicationsByKeys(DbSession session, Set<String> keys) { + if (keys.isEmpty()) { + return Collections.emptyList(); + } + return mapper(session).selectApplicationsByKeys(keys); + } + public List<ProjectDto> selectProjects(DbSession session) { return mapper(session).selectProjects(); } @@ -99,4 +110,5 @@ public class ProjectDao implements Dao { private static ProjectMapper mapper(DbSession session) { return session.getMapper(ProjectMapper.class); } + } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDto.java index e01902c2eb5..656fe8e67ae 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDto.java @@ -181,4 +181,5 @@ public class ProjectDto { public int hashCode() { return uuid != null ? uuid.hashCode() : 0; } + } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectMapper.java index 01ff1c11e97..c2fafe6a8f3 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectMapper.java @@ -57,4 +57,8 @@ public interface ProjectMapper { List<ProjectDto> selectProjectsByOrganizationUuid(String organizationUuid); void updateVisibility(@Param("uuid") String uuid, @Param("isPrivate") boolean isPrivate, @Param("updatedAt") long updatedAt); + + List<ProjectDto> selectAllApplications(); + + List<ProjectDto> selectApplicationsByKeys(@Param("kees") Collection<String> kees); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java index b11c7febd7f..6ab82d82e1b 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java @@ -49,7 +49,6 @@ class PurgeCommands { this.system2 = system2; } - @VisibleForTesting PurgeCommands(DbSession session, PurgeProfiler profiler, System2 system2) { this(session, session.getMapper(PurgeMapper.class), profiler, system2); } @@ -406,6 +405,21 @@ class PurgeCommands { profiler.stop(); } + void deleteApplicationProjects(String applicationUuid) { + profiler.start("deleteApplicationProjects (app_projects)"); + purgeMapper.deleteApplicationBranchProjectBranchesByApplicationUuid(applicationUuid); + purgeMapper.deleteApplicationProjectsByApplicationUuid(applicationUuid); + session.commit(); + profiler.stop(); + } + + void deleteApplicationBranchProjects(String applicationBranchUuid) { + profiler.start("deleteApplicationBranchProjects (app_branch_project_branch)"); + purgeMapper.deleteApplicationBranchProjects(applicationBranchUuid); + session.commit(); + profiler.stop(); + } + public void deleteProjectAlmSettings(String rootUuid) { profiler.start("deleteProjectAlmSettings (project_alm_settings)"); purgeMapper.deleteProjectAlmSettingsByProjectUuid(rootUuid); @@ -415,6 +429,7 @@ class PurgeCommands { void deleteBranch(String rootUuid) { profiler.start("deleteBranch (project_branches)"); + purgeMapper.deleteApplicationBranchProjectBranchesByProjectBranchUuid(rootUuid); purgeMapper.deleteBranchByUuid(rootUuid); session.commit(); profiler.stop(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java index bb39d91a389..3498ad1f49d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java @@ -221,6 +221,8 @@ public class PurgeDao implements Dao { commands.deletePermissions(rootUuid); commands.deleteNewCodePeriods(rootUuid); commands.deleteBranch(rootUuid); + commands.deleteApplicationBranchProjects(rootUuid); + commands.deleteApplicationProjects(rootUuid); commands.deleteComponents(rootUuid); commands.deleteComponentsByMainBranchProjectUuid(rootUuid); commands.deleteProject(rootUuid); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java index 840533516f9..57ca6f26a3e 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java @@ -141,6 +141,14 @@ public interface PurgeMapper { void deleteProjectMappingsByProjectUuid(@Param("projectUuid") String projectUuid); + void deleteApplicationProjectsByApplicationUuid(@Param("applicationUuid") String applicationUuid); + + void deleteApplicationBranchProjectBranchesByApplicationUuid(@Param("applicationUuid") String applicationUuid); + + void deleteApplicationBranchProjects(@Param("branchUuid") String applicationBranchUuid); + + void deleteApplicationBranchProjectBranchesByProjectBranchUuid(@Param("projectBranchUuid") String projectBranchUuid); + void deleteBranchByUuid(@Param("uuid") String uuid); void deleteLiveMeasuresByProjectUuid(@Param("projectUuid") String projectUuid); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ApplicationProjectsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ApplicationProjectsMapper.xml new file mode 100644 index 00000000000..9b7533340f0 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ApplicationProjectsMapper.xml @@ -0,0 +1,196 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd"> +<mapper namespace="org.sonar.db.component.ApplicationProjectsMapper"> + + <sql id="appColumns"> + app.uuid as uuid + </sql> + + <sql id="branchColumns"> + pb.uuid as uuid, + pb.project_uuid as projectUuid, + pb.kee as kee, + pb.branch_type as branchType, + pb.merge_branch_uuid as mergeBranchUuid, + pb.pull_request_binary as pullRequestBinary, + pb.exclude_from_purge as excludeFromPurge, + pb.need_issue_sync as needIssueSync + </sql> + + <sql id="projectColumns"> + p.uuid as uuid, + p.organization_uuid as organizationUuid, + p.kee as kee, + p.qualifier as qualifier, + p.name as name, + p.description as description, + p.tags as tagsString, + p.private as isPrivate, + p.created_at as createdAt, + p.updated_at as updatedAt + </sql> + + <select id="selectProjects" parameterType="String" resultType="Project"> + SELECT + <include refid="projectColumns"/> + FROM + app_projects appProj + INNER JOIN + projects p + on + p.uuid = appProj.project_uuid + WHERE + appProj.application_uuid=#{applicationUuid,jdbcType=VARCHAR} + </select> + + <update id="addProject" parameterType="map"> + INSERT INTO app_projects ( + uuid, + application_uuid, + project_uuid, + created_at + ) + VALUES ( + #{uuid,jdbcType=VARCHAR}, + #{applicationUuid,jdbcType=VARCHAR}, + #{projectUuid,jdbcType=VARCHAR}, + #{now,jdbcType=BIGINT} + ) + </update> + + <delete id="removeApplicationBranchProjectBranchesByApplicationAndProject" parameterType="map"> + DELETE FROM + app_branch_project_branch + WHERE + app_branch_project_branch.application_uuid = #{applicationUuid,jdbcType=VARCHAR} + AND + app_branch_project_branch.project_uuid=#{projectUuid,jdbcType=VARCHAR} + </delete> + + <delete id="removeApplicationProjectsByApplicationAndProject" parameterType="map"> + DELETE FROM + app_projects + WHERE + application_uuid=#{applicationUuid,jdbcType=VARCHAR} + AND + project_uuid=#{projectUuid,jdbcType=VARCHAR} + </delete> + + <delete id="removeApplicationBranchProjectBranchesByApplication" parameterType="String"> + DELETE FROM + app_branch_project_branch + where + application_uuid=#{applicationUuid,jdbcType=VARCHAR} + </delete> + + <delete id="removeApplicationProjectsByApplication" parameterType="String"> + DELETE FROM + app_projects + WHERE + application_uuid=#{applicationUuid,jdbcType=VARCHAR} + </delete> + + <insert id="addProjectBranchToAppBranch" parameterType="map"> + INSERT INTO app_branch_project_branch ( + uuid, + application_uuid, + application_branch_uuid, + project_uuid, + project_branch_uuid, + created_at + ) + VALUES ( + #{uuid,jdbcType=VARCHAR}, + #{applicationUuid,jdbcType=VARCHAR}, + #{applicationBranchUuid,jdbcType=VARCHAR}, + #{projectUuid,jdbcType=VARCHAR}, + #{projectBranchUuid,jdbcType=VARCHAR}, + #{now,jdbcType=BIGINT} + ) + </insert> + + <delete id="removeProjectBranchFromAppBranch" parameterType="map"> + DELETE FROM + app_branch_project_branch + WHERE + app_branch_project_branch.application_branch_uuid = #{applicationBranchUuid,jdbcType=VARCHAR} + AND + app_branch_project_branch.project_branch_uuid = #{projectBranchUuid,jdbcType=VARCHAR} + </delete> + + <select id="selectProjectBranchesFromAppBranch" parameterType="String" resultType="org.sonar.db.component.BranchDto"> + SELECT + <include refid="branchColumns"/> + FROM + app_branch_project_branch + INNER JOIN + project_branches pb + ON + app_branch_project_branch.project_branch_uuid = pb.uuid + WHERE + app_branch_project_branch.application_branch_uuid = #{applicationBranchUuid,jdbcType=VARCHAR} + </select> + + <select id="countApplicationProjects" parameterType="String" resultType="int"> + select + count(1) + FROM + app_projects ap + WHERE + ap.application_uuid = #{applicationUuid,jdbcType=VARCHAR} + </select> + + <update id="updateApplicationBranchName" parameterType="String"> + UPDATE + project_branches + SET + kee = #{newName,jdbcType=VARCHAR} + WHERE + uuid = #{uuid,jdbcType=VARCHAR} + </update> + + <select id="selectApplicationsFromProjectBranch" parameterType="String" resultType="Project"> + SELECT + <include refid="projectColumns"/> + FROM + projects p + WHERE p.uuid IN ( + SELECT + abp.application_uuid + FROM + app_branch_project_branch abp + INNER JOIN + project_branches pb + ON + abp.project_branch_uuid = pb.uuid + WHERE + pb.kee = #{branchKey,jdbcType=VARCHAR} + AND + abp.project_uuid = #{projectUuid,jdbcType=VARCHAR} + ) + </select> + + <select id="selectApplicationsFromProjects" resultType="Project"> + SELECT + <include refid="projectColumns"/> + FROM + app_projects ap + INNER JOIN + projects p + ON + ap.application_uuid = p.uuid + WHERE + ap.project_uuid in + <foreach collection="projectUuids" open="(" close=")" item="uuid" separator=","> + #{uuid,jdbcType=VARCHAR} + </foreach> + </select> + + <delete id="removeAllProjectBranchesOfAppBranch" parameterType="String"> + DELETE FROM + app_branch_project_branch + WHERE + application_branch_uuid=#{applicationBranchUuid,jdbcType=VARCHAR} + </delete> + +</mapper> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml index dccb832be36..8cdc4b70ddc 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml @@ -48,6 +48,13 @@ uuid = #{projectUuid, jdbcType=VARCHAR} </update> + <delete id="deleteBranch" parameterType="string"> + delete from project_branches + where + project_uuid = #{projectUuid, jdbcType=VARCHAR}, + kee = #{branchKey, jdbcType=VARCHAR} + </delete> + <update id="updateExcludeFromPurge"> update project_branches set diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectMapper.xml index d3903f6567a..dfc9c9d51fc 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectMapper.xml @@ -46,6 +46,18 @@ </foreach> </select> + <select id="selectApplicationsByKeys" resultType="Project"> + select + <include refid="projectColumns"/> + from projects p + where + p.qualifier='APP' and + p.kee in + <foreach collection="kees" open="(" close=")" item="k" separator=","> + #{k,jdbcType=VARCHAR} + </foreach> + </select> + <select id="selectProjects" resultType="Project"> select <include refid="projectColumns"/> @@ -62,7 +74,7 @@ p.organization_uuid=#{organizationUuid,jdbcType=VARCHAR} </select> - <select id="selectProjectsByOrganizationUuid" parameterType="String" resultType="Project"> + <select id="selectProjectsByOrganizationUuid" parameterType="String" resultType="Project"> select <include refid="projectColumns"/> from projects p @@ -71,6 +83,14 @@ p.organization_uuid=#{organizationUuid,jdbcType=VARCHAR} </select> + <select id="selectAllApplications" resultType="Project"> + select + <include refid="projectColumns"/> + from projects p + where + p.qualifier='APP' + </select> + <select id="selectProjectByKey" parameterType="String" resultType="Project"> SELECT <include refid="projectColumns"/> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml index e4b71806473..828a957dae3 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml @@ -329,6 +329,33 @@ </foreach> </delete> + <delete id="deleteApplicationProjectsByApplicationUuid" parameterType="map"> + DELETE + FROM app_projects + where + application_uuid=#{applicationUuid,jdbcType=VARCHAR} + </delete> + + <delete id="deleteApplicationBranchProjectBranchesByApplicationUuid" parameterType="map"> + DELETE + FROM app_branch_project_branch + where + application_uuid=#{applicationUuid,jdbcType=VARCHAR} + </delete> + + <delete id="deleteApplicationBranchProjects" parameterType="String"> + DELETE + FROM app_branch_project_branch + where + application_branch_uuid=#{branchUuid,jdbcType=VARCHAR} + </delete> + + <delete id="deleteApplicationBranchProjectBranchesByProjectBranchUuid" parameterType="String"> + DELETE + FROM app_branch_project_branch + where project_branch_uuid=#{projectBranchUuid,jdbcType=VARCHAR} + </delete> + <delete id="deleteIssueChangesFromIssueKeys" parameterType="map"> DELETE FROM issue_changes WHERE issue_key IN diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl index eba030939d8..e4effd33c0b 100644 --- a/server/sonar-db-dao/src/schema/schema-sq.ddl +++ b/server/sonar-db-dao/src/schema/schema-sq.ddl @@ -89,6 +89,32 @@ CREATE TABLE "ANALYSIS_PROPERTIES"( ALTER TABLE "ANALYSIS_PROPERTIES" ADD CONSTRAINT "PK_ANALYSIS_PROPERTIES" PRIMARY KEY("UUID"); CREATE INDEX "ANALYSIS_PROPERTIES_ANALYSIS" ON "ANALYSIS_PROPERTIES"("ANALYSIS_UUID"); +CREATE TABLE "APP_BRANCH_PROJECT_BRANCH"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "APPLICATION_BRANCH_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "PROJECT_BRANCH_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "APP_BRANCH_PROJECT_BRANCH" ADD CONSTRAINT "PK_APP_BRANCH_PROJECT_BRANCH" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_APP_BRANCH_PROJ" ON "APP_BRANCH_PROJECT_BRANCH"("APPLICATION_BRANCH_UUID", "PROJECT_BRANCH_UUID"); +CREATE INDEX "IDX_ABPB_APP_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("APPLICATION_UUID"); +CREATE INDEX "IDX_ABPB_APP_BRANCH_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("APPLICATION_BRANCH_UUID"); +CREATE INDEX "IDX_ABPB_PROJ_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("PROJECT_UUID"); +CREATE INDEX "IDX_ABPB_PROJ_BRANCH_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("PROJECT_BRANCH_UUID"); + +CREATE TABLE "APP_PROJECTS"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "APP_PROJECTS" ADD CONSTRAINT "PK_APP_PROJECTS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_APP_PROJECTS" ON "APP_PROJECTS"("APPLICATION_UUID", "PROJECT_UUID"); +CREATE INDEX "IDX_APP_PROJ_APPLICATION_UUID" ON "APP_PROJECTS"("APPLICATION_UUID"); +CREATE INDEX "IDX_APP_PROJ_PROJECT_UUID" ON "APP_PROJECTS"("PROJECT_UUID"); + CREATE TABLE "CE_ACTIVITY"( "UUID" VARCHAR(40) NOT NULL, "TASK_TYPE" VARCHAR(15) NOT NULL, diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ApplicationProjectsDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ApplicationProjectsDaoTest.java new file mode 100644 index 00000000000..8ba95953066 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ApplicationProjectsDaoTest.java @@ -0,0 +1,157 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.component; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.project.ProjectDto; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ApplicationProjectsDaoTest { + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private UuidFactoryFast uuids = UuidFactoryFast.getInstance(); + private TestSystem2 system2 = new TestSystem2(); + private DbSession dbSession = db.getSession(); + private ApplicationProjectsDao underTest = new ApplicationProjectsDao(system2, uuids); + + @Before + public void before() { + system2.setNow(1000L); + } + + @Test + public void select_projects() { + insertApplicationProject("uuid2", "p1"); + insertApplicationProject("uuid2", "p2"); + + assertThat(underTest.selectProjects(dbSession, "uuid")).isEmpty(); + assertThat(underTest.selectProjects(dbSession, "uuid2")).extracting(ProjectDto::getUuid).containsOnly("p1", "p2"); + } + + @Test + public void select_projects_from_non_existing_app_is_empty() { + insertApplicationProject("uuid", "p1"); + assertThat(underTest.selectProjects(dbSession, "does_not_exist")).isEmpty(); + } + + @Test + public void add_project() { + insertProject("p1"); + underTest.addProject(dbSession, "uuid", "p1"); + assertThat(underTest.selectProjects(dbSession, "uuid")).extracting(ProjectDto::getUuid).containsOnly("p1"); + } + + @Test + public void add_project_branch_to_application_branch() { + insertProject("p1"); + insertBranch("p1", "b1"); + insertApplication("app1"); + insertBranch("app1", "app-b1"); + underTest.addProjectBranchToAppBranch(dbSession, "app1", "app-b1", "p1", "b1"); + assertThat(underTest.selectProjectBranchesFromAppBranch(dbSession, "app-b1")).extracting(BranchDto::getUuid).containsOnly("b1"); + } + + @Test + public void remove_project() { + insertApplicationProject("uuid", "p1"); + insertApplicationProject("uuid", "p2"); + assertThat(underTest.selectProjects(dbSession, "uuid")).extracting(ProjectDto::getUuid).contains("p1"); + underTest.removeApplicationProjectsByApplicationAndProject(dbSession, "uuid", "p1"); + assertThat(underTest.selectProjects(dbSession, "uuid")).extracting(ProjectDto::getUuid).containsOnly("p2"); + } + + @Test + public void remove_project_from_non_existing_app_is_no_op() { + insertApplicationProject("uuid", "p1"); + underTest.removeApplicationProjectsByApplicationAndProject(dbSession, "non_existing", "p1"); + assertThat(underTest.selectProjects(dbSession, "uuid")).extracting(ProjectDto::getUuid).containsOnly("p1"); + } + + @Test + public void remove_non_existing_project_from_app_is_no_op() { + insertApplicationProject("uuid", "p1"); + underTest.removeApplicationProjectsByApplicationAndProject(dbSession, "uuid", "non_existing"); + assertThat(underTest.selectProjects(dbSession, "uuid")).extracting(ProjectDto::getUuid).containsOnly("p1"); + } + + @Test + public void remove() { + insertApplicationProject("uuid", "p1"); + insertApplicationProject("uuid", "p2"); + + underTest.remove(dbSession, "uuid"); + assertThat(underTest.selectProjects(dbSession, "uuid")).isEmpty(); + } + + private String insertApplicationProject(String applicationUuid, String projectUuid) { + String uuid = uuids.create(); + db.executeInsert( + "app_projects", + "uuid", uuid, + "application_uuid", applicationUuid, + "project_uuid", projectUuid, + "created_at", 1000L); + insertProject(projectUuid); + return uuid; + } + + private void insertProject(String projectUuid) { + db.executeInsert("projects", + "uuid", projectUuid, + "kee", projectUuid, + "qualifier", "TRK", + "ORGANIZATION_UUID", "ORGANIZATION_UUID", + "private", true, + "updated_at", 1000L, + "created_at", 1000L); + } + + private void insertApplication(String appUuid) { + db.executeInsert("projects", + "uuid", appUuid, + "kee", appUuid, + "qualifier", "APP", + "ORGANIZATION_UUID", "ORGANIZATION_UUID", + "private", true, + "updated_at", 1000L, + "created_at", 1000L); + } + + private void insertBranch(String projectUuid, String branchKey) { + db.executeInsert("project_branches", + "uuid", branchKey, + "branch_type", "BRANCH", + "project_uuid", projectUuid, + "kee", branchKey, + "NEED_ISSUE_SYNC", true, + "updated_at", 1000L, + "created_at", 1000L); + } + +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java index 1dbdd3aa301..28117efbf7d 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java @@ -86,9 +86,7 @@ public class ProjectMeasuresIndexerIteratorTest { @Test public void return_application_measure() { - OrganizationDto organization = dbTester.organizations().insert(); - ComponentDto project = dbTester.components().insertPrivateApplication(organization, - c -> c.setDbKey("App-Key").setName("App Name")); + ComponentDto project = dbTester.components().insertPrivateApplication(c -> c.setDbKey("App-Key").setName("App Name")); SnapshotDto analysis = dbTester.components().insertSnapshot(project); MetricDto metric1 = dbTester.measures().insertMetric(m -> m.setValueType(INT.name()).setKey("ncloc")); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java index cf3dcdb411d..db459e897fd 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java @@ -536,6 +536,84 @@ public class PurgeDaoTest { } @Test + public void delete_application() { + MetricDto metric = db.measures().insertMetric(); + ComponentDto project = db.components().insertPrivateProject(); + BranchDto projectBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.uuid()).get(); + RuleDefinitionDto rule = db.rules().insert(); + + ComponentDto app = db.components().insertPrivateApplication(); + ComponentDto appBranch = db.components().insertProjectBranch(app); + ComponentDto otherApp = db.components().insertPrivateApplication(); + ComponentDto otherAppBranch = db.components().insertProjectBranch(otherApp); + + SnapshotDto appAnalysis = db.components().insertSnapshot(app); + SnapshotDto appBranchAnalysis = db.components().insertSnapshot(appBranch); + SnapshotDto otherAppAnalysis = db.components().insertSnapshot(otherApp); + SnapshotDto otherAppBranchAnalysis = db.components().insertSnapshot(otherAppBranch); + + MeasureDto appMeasure = db.measures().insertMeasure(app, appAnalysis, metric); + MeasureDto appBranchMeasure = db.measures().insertMeasure(appBranch, appBranchAnalysis, metric); + MeasureDto otherAppMeasure = db.measures().insertMeasure(otherApp, otherAppAnalysis, metric); + MeasureDto otherAppBranchMeasure = db.measures().insertMeasure(otherAppBranch, otherAppBranchAnalysis, metric); + + db.components().addApplicationProject(app, project); + db.components().addApplicationProject(otherApp, project); + db.components().addProjectBranchToApplicationBranch(dbClient.branchDao().selectByUuid(dbSession, appBranch.uuid()).get(), projectBranch); + db.components().addProjectBranchToApplicationBranch(dbClient.branchDao().selectByUuid(dbSession, otherAppBranch.uuid()).get(), projectBranch); + + underTest.deleteProject(dbSession, app.uuid()); + dbSession.commit(); + + assertThat(uuidsIn("components")).containsOnly(project.uuid(), otherApp.uuid(), otherAppBranch.uuid()); + assertThat(uuidsIn("projects")).containsOnly(project.uuid(), otherApp.uuid()); + assertThat(uuidsIn("snapshots")).containsOnly(otherAppAnalysis.getUuid(), otherAppBranchAnalysis.getUuid()); + assertThat(uuidsIn("project_branches")).containsOnly(project.uuid(), otherApp.uuid(), otherAppBranch.uuid()); + assertThat(uuidsIn("project_measures")).containsOnly(otherAppMeasure.getUuid(), otherAppBranchMeasure.getUuid()); + assertThat(uuidsIn("app_projects", "application_uuid")).containsOnly(otherApp.uuid()); + assertThat(uuidsIn("app_branch_project_branch", "application_branch_uuid")).containsOnly(otherAppBranch.uuid()); + } + + @Test + public void delete_application_branch() { + MetricDto metric = db.measures().insertMetric(); + ComponentDto project = db.components().insertPrivateProject(); + BranchDto projectBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.uuid()).get(); + RuleDefinitionDto rule = db.rules().insert(); + + ComponentDto app = db.components().insertPrivateApplication(); + ComponentDto appBranch = db.components().insertProjectBranch(app); + ComponentDto otherApp = db.components().insertPrivateApplication(); + ComponentDto otherAppBranch = db.components().insertProjectBranch(otherApp); + + SnapshotDto appAnalysis = db.components().insertSnapshot(app); + SnapshotDto appBranchAnalysis = db.components().insertSnapshot(appBranch); + SnapshotDto otherAppAnalysis = db.components().insertSnapshot(otherApp); + SnapshotDto otherAppBranchAnalysis = db.components().insertSnapshot(otherAppBranch); + + MeasureDto appMeasure = db.measures().insertMeasure(app, appAnalysis, metric); + MeasureDto appBranchMeasure = db.measures().insertMeasure(appBranch, appBranchAnalysis, metric); + MeasureDto otherAppMeasure = db.measures().insertMeasure(otherApp, otherAppAnalysis, metric); + MeasureDto otherAppBranchMeasure = db.measures().insertMeasure(otherAppBranch, otherAppBranchAnalysis, metric); + + db.components().addApplicationProject(app, project); + db.components().addApplicationProject(otherApp, project); + db.components().addProjectBranchToApplicationBranch(dbClient.branchDao().selectByUuid(dbSession, appBranch.uuid()).get(), projectBranch); + db.components().addProjectBranchToApplicationBranch(dbClient.branchDao().selectByUuid(dbSession, otherAppBranch.uuid()).get(), projectBranch); + + underTest.deleteBranch(dbSession, appBranch.uuid()); + dbSession.commit(); + + assertThat(uuidsIn("components")).containsOnly(project.uuid(), app.uuid(), otherApp.uuid(), otherAppBranch.uuid()); + assertThat(uuidsIn("projects")).containsOnly(project.uuid(), app.uuid(), otherApp.uuid()); + assertThat(uuidsIn("snapshots")).containsOnly(otherAppAnalysis.getUuid(), appAnalysis.getUuid(), otherAppBranchAnalysis.getUuid()); + assertThat(uuidsIn("project_branches")).containsOnly(project.uuid(), app.uuid(), otherApp.uuid(), otherAppBranch.uuid()); + assertThat(uuidsIn("project_measures")).containsOnly(appMeasure.getUuid(), otherAppMeasure.getUuid(), otherAppBranchMeasure.getUuid()); + assertThat(uuidsIn("app_projects", "application_uuid")).containsOnly(app.uuid(), otherApp.uuid()); + assertThat(uuidsIn("app_branch_project_branch", "application_branch_uuid")).containsOnly(otherAppBranch.uuid()); + } + + @Test public void delete_webhooks_from_project() { OrganizationDto organization = db.organizations().insert(); ProjectDto project1 = db.components().insertPrivateProjectDto(organization); diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentDbTester.java index b46a7eb5e3a..2ee5805eb4c 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentDbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentDbTester.java @@ -149,7 +149,6 @@ public class ComponentDbTester { return getProjectDto(componentDto); } - public final ProjectDto insertPublicProjectDto(OrganizationDto organization, Consumer<ComponentDto> dtoPopulator) { ComponentDto componentDto = insertPublicProject(organization, dtoPopulator); return getProjectDto(componentDto); @@ -186,6 +185,11 @@ public class ComponentDbTester { return insertComponentAndBranchAndProject(ComponentTesting.newPrivateProjectDto(organizationDto, uuid), true, defaults(), dtoPopulator); } + + public final ComponentDto insertPrivateProjectWithCustomBranch(String branchKey) { + return insertPrivateProjectWithCustomBranch(db.getDefaultOrganization(), b -> b.setBranchType(BRANCH).setKey(branchKey), defaults()); + } + public final ComponentDto insertPrivateProjectWithCustomBranch(OrganizationDto organizationDto, Consumer<BranchDto> branchPopulator) { return insertPrivateProjectWithCustomBranch(organizationDto, branchPopulator, defaults()); } @@ -279,13 +283,37 @@ public class ComponentDbTester { public final ComponentDto insertPrivateApplication() { return insertPrivateApplication(db.getDefaultOrganization()); } - - public final ComponentDto insertPrivateApplication(OrganizationDto organization) { - return insertPrivateApplication(organization, defaults()); + + public final ProjectDto insertPrivateApplicationDto() { + return getProjectDto(insertPrivateApplication(db.getDefaultOrganization())); + } + + public final ProjectDto insertPublicApplicationDto() { + return getProjectDto(insertPublicApplication(db.getDefaultOrganization())); + } + + public final ProjectDto insertPrivateApplicationDto(Consumer<ComponentDto> dtoPopulator) { + return getProjectDto(insertPrivateApplication(db.getDefaultOrganization(), dtoPopulator, defaults())); + } + + public final ProjectDto insertPrivateApplicationDto(Consumer<ComponentDto> dtoPopulator, Consumer<ProjectDto> appPopulator) { + return getProjectDto(insertPrivateApplication(db.getDefaultOrganization(), dtoPopulator, appPopulator)); + } + + public final ComponentDto insertPrivateApplication(Consumer<ComponentDto> dtoPopulator) { + return insertPrivateApplication(db.getDefaultOrganization(), dtoPopulator, defaults()); } public final ComponentDto insertPrivateApplication(OrganizationDto organization, Consumer<ComponentDto> dtoPopulator) { - return insertComponentAndBranchAndProject(ComponentTesting.newApplication(organization).setPrivate(true), true, defaults(), dtoPopulator); + return insertPrivateApplication(db.getDefaultOrganization(), dtoPopulator, defaults()); + } + + public final ComponentDto insertPrivateApplication(OrganizationDto organization) { + return insertPrivateApplication(organization, defaults(), defaults()); + } + + public final ComponentDto insertPrivateApplication(OrganizationDto organization, Consumer<ComponentDto> dtoPopulator, Consumer<ProjectDto> projectPopulator) { + return insertComponentAndBranchAndProject(ComponentTesting.newApplication(organization).setPrivate(true), true, defaults(), dtoPopulator, projectPopulator); } public final ComponentDto insertSubView(ComponentDto view) { @@ -313,6 +341,27 @@ public class ComponentDbTester { return component; } + public void addApplicationProject(ComponentDto application, ComponentDto... projects) { + for(ComponentDto project : projects){ + dbClient.applicationProjectsDao().addProject(dbSession, application.uuid(), project.uuid()); + } + db.commit(); + } + + public void addApplicationProject(ProjectDto application, ProjectDto... projects) { + for(ProjectDto project : projects){ + dbClient.applicationProjectsDao().addProject(dbSession, application.getUuid(), project.getUuid()); + } + db.commit(); + } + + public void addProjectBranchToApplicationBranch(BranchDto applicationBranch, BranchDto... projectBranches) { + for(BranchDto projectBranch : projectBranches){ + dbClient.applicationProjectsDao().addProjectBranchToAppBranch(dbSession, applicationBranch, projectBranch); + } + db.commit(); + } + private ComponentDto insertComponentAndBranchAndProject(ComponentDto component, @Nullable Boolean isPrivate, Consumer<BranchDto> branchPopulator, Consumer<ComponentDto> componentDtoPopulator) { return insertComponentAndBranchAndProject(component, isPrivate, branchPopulator, componentDtoPopulator, defaults()); @@ -421,7 +470,7 @@ public class ComponentDbTester { } // TODO temporary constructor to quickly create project from previous project component. - private ProjectDto toProjectDto(ComponentDto componentDto, long createTime) { + public static ProjectDto toProjectDto(ComponentDto componentDto, long createTime) { return new ProjectDto() .setUuid(componentDto.uuid()) .setKey(componentDto.getDbKey()) diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentTesting.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentTesting.java index a5e4a1cc1d5..eaf1028f7f1 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentTesting.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentTesting.java @@ -286,7 +286,7 @@ public class ComponentTesting { .setLongName(project.getName()) .setDescription(project.getDescription()) .setScope(Scopes.PROJECT) - .setQualifier(Qualifiers.PROJECT) + .setQualifier(project.getQualifier()) .setPath(null) .setLanguage(null) .setEnabled(true) diff --git a/server/sonar-db-migration/build.gradle b/server/sonar-db-migration/build.gradle index de715638b1e..ebdb837087e 100644 --- a/server/sonar-db-migration/build.gradle +++ b/server/sonar-db-migration/build.gradle @@ -25,6 +25,8 @@ dependencies { testCompile 'org.assertj:assertj-core' testCompile 'org.mindrot:jbcrypt' testCompile 'org.mockito:mockito-core' + testCompile 'org.xmlunit:xmlunit-core' + testCompile 'org.xmlunit:xmlunit-matchers' testCompile project(':sonar-scanner-protocol') testCompile project(':sonar-testing-harness') testCompile testFixtures(project(':server:sonar-db-core')) diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjs.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjs.java new file mode 100644 index 00000000000..621ef6bf9e5 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjs.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +public class AddIndexToApplicationBranchProjs extends DdlChange { + private static final String TABLE = "app_branch_project_branch"; + + public AddIndexToApplicationBranchProjs(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("uniq_app_branch_proj") + .addColumn("application_branch_uuid") + .addColumn("project_branch_uuid") + .setUnique(true) + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("idx_abpb_app_uuid") + .addColumn("application_uuid") + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("idx_abpb_app_branch_uuid") + .addColumn("application_branch_uuid") + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("idx_abpb_proj_uuid") + .addColumn("project_uuid") + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("idx_abpb_proj_branch_uuid") + .addColumn("project_branch_uuid") + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjects.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjects.java new file mode 100644 index 00000000000..d047c67544e --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjects.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +public class AddIndexToApplicationProjects extends DdlChange { + private static final String TABLE = "app_projects"; + + public AddIndexToApplicationProjects(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("uniq_app_projects") + .addColumn("application_uuid") + .addColumn("project_uuid") + .setUnique(true) + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("idx_app_proj_application_uuid") + .addColumn("application_uuid") + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("idx_app_proj_project_uuid") + .addColumn("project_uuid") + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjs.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjs.java new file mode 100644 index 00000000000..615b03f4144 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjs.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DdlChange; +import org.sonar.server.platform.db.migration.version.v84.util.AddPrimaryKeyBuilder; + +public class AddPkToApplicationBranchProjs extends DdlChange { + private static final String TABLE = "app_branch_project_branch"; + + public AddPkToApplicationBranchProjs(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AddPrimaryKeyBuilder(TABLE, "uuid").build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjects.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjects.java new file mode 100644 index 00000000000..0aa5bb6bf23 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjects.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DdlChange; +import org.sonar.server.platform.db.migration.version.v84.util.AddPrimaryKeyBuilder; + +public class AddPkToApplicationProjects extends DdlChange { + private static final String TABLE = "app_projects"; + + public AddPkToApplicationProjects(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AddPrimaryKeyBuilder(TABLE, "uuid").build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationBranchProjs.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationBranchProjs.java new file mode 100644 index 00000000000..5711c4384e3 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationBranchProjs.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.sonar.core.util.Uuids; +import org.sonar.db.Database; +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.newVarcharColumnDefBuilder; + +public class CreateApplicationBranchProjs extends DdlChange { + public CreateApplicationBranchProjs(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new CreateTableBuilder(getDialect(), "app_branch_project_branch") + .addColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("application_uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("application_branch_uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("project_uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("project_branch_uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newBigIntegerColumnDefBuilder().setColumnName("created_at").setIsNullable(false).build()) + .build()); + } + +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationProjectsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationProjectsTable.java new file mode 100644 index 00000000000..7ef8f8eee22 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationProjectsTable.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.sonar.core.util.Uuids; +import org.sonar.db.Database; +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.newVarcharColumnDefBuilder; + +public class CreateApplicationProjectsTable extends DdlChange { + public CreateApplicationProjectsTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new CreateTableBuilder(getDialect(), "app_projects") + .addColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("application_uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("project_uuid").setIsNullable(false).setLimit(Uuids.MAX_LENGTH).build()) + .addColumn(newBigIntegerColumnDefBuilder().setColumnName("created_at").setIsNullable(false).build()) + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/DbVersion86.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/DbVersion86.java index 9429fc93a39..f826e79eb6e 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/DbVersion86.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/DbVersion86.java @@ -43,6 +43,16 @@ public class DbVersion86 implements DbVersion { .add(4113, "Make 'name' column in 'groups' table unique", AddUniqueIndexOnNameColumnOfGroupsTable.class) .add(4114, "Move default permission templates to internal properties", MoveDefaultTemplatesToInternalProperties.class) .add(4115, "Set 'sonar.forceAuthentication' to false for upgraded instances", SetForceAuthenticationSettings.class) + + .add(4116, "Create table 'app_projects'", CreateApplicationProjectsTable.class) + .add(4117, "Create primary key for 'app_projects'", AddPkToApplicationProjects.class) + .add(4118, "Create index for 'app_projects'", AddIndexToApplicationProjects.class) + + .add(4119, "Create table 'app_branch_project_branch'", CreateApplicationBranchProjs.class) + .add(4120, "Create primary key for 'app_branch_project_branch'", AddPkToApplicationBranchProjs.class) + .add(4121, "Create index for 'app_branch_project_branch'", AddIndexToApplicationBranchProjs.class) + + .add(4122, "Migrate view definitions from xml to db", MigrateApplicationDefinitionsFromXmlToDb.class) ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDb.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDb.java new file mode 100644 index 00000000000..72675effcc2 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDb.java @@ -0,0 +1,732 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.sql.SQLException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.sax.SAXSource; +import javax.xml.validation.SchemaFactory; +import org.apache.commons.lang.StringUtils; +import org.codehaus.staxmate.SMInputFactory; +import org.codehaus.staxmate.in.SMHierarchicCursor; +import org.codehaus.staxmate.in.SMInputCursor; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.Select; +import org.sonar.server.platform.db.migration.step.Upsert; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING; +import static org.apache.commons.io.IOUtils.toInputStream; +import static org.apache.commons.lang.StringEscapeUtils.escapeXml; +import static org.apache.commons.lang.StringUtils.trim; + +public class MigrateApplicationDefinitionsFromXmlToDb extends DataChange { + static final int TEXT_VALUE_MAX_LENGTH = 4000; + private static final String SELECT_APPLICATION_UUID_BY_KEY = "select uuid from projects where kee = ? and qualifier = 'APP'"; + private static final String SELECT_PROJECTS_BY_KEYS = "select kee,uuid from projects where kee in (%s) and qualifier = 'TRK'"; + private static final String UPDATE_INTERNAL_PROP_TEXT_VALUE = "update internal_properties set text_value = ?, clob_value = NULL where kee = ?"; + private static final String UPDATE_INTERNAL_PROP_CLOB_VALUE = "update internal_properties set clob_value = ?, text_value = NULL where kee = ?"; + private static final String VIEWS_DEF_KEY = "views.def"; + + private final UuidFactory uuidFactory; + private final System2 system; + + public MigrateApplicationDefinitionsFromXmlToDb(Database db, UuidFactory uuidFactory, System2 system) { + super(db); + + this.uuidFactory = uuidFactory; + this.system = system; + } + + @Override + protected void execute(Context context) throws SQLException { + String xml = getViewsDefinition(context); + // skip migration if `views.def` does not exist in the db + if (xml == null) { + return; + } + + try { + Map<String, ViewXml.ViewDef> defs = ViewXml.parse(xml); + List<ViewXml.ViewDef> applications = defs.values() + .stream() + .filter(v -> Qualifiers.APP.equals(v.getQualifier())) + .collect(Collectors.toList()); + for (ViewXml.ViewDef app : applications) { + insertApplication(context, app); + } + cleanUpViewsDefinitionsXml(context, defs.values()); + } catch (Exception e) { + throw new IllegalStateException("Failed to migrate views definitions property.", e); + } + + } + + private void insertApplication(Context context, ViewXml.ViewDef app) throws SQLException { + long now = system.now(); + String applicationUuid = context.prepareSelect(SELECT_APPLICATION_UUID_BY_KEY) + .setString(1, app.getKey()) + .get(r -> r.getString(1)); + + // ignore if application only exists in xml and not in the db. It will be removed from the xml at later stage of the migration. + if (applicationUuid == null) { + return; + } + + String queryParam = app.getProjects().stream().map(uuid -> "'" + uuid + "'").collect(Collectors.joining(",")); + Map<String, String> projectUuidsByKeys = context.prepareSelect(format(SELECT_PROJECTS_BY_KEYS, queryParam)) + .list(r -> new AbstractMap.SimpleEntry<>(r.getString(1), r.getString(2))) + .stream() + .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); + + insertApplicationProjects(context, app, applicationUuid, projectUuidsByKeys, now); + if (!app.getApplicationBranches().isEmpty()) { + insertApplicationBranchesProjects(context, app, applicationUuid, projectUuidsByKeys, now); + } + } + + private void insertApplicationProjects(Context context, ViewXml.ViewDef app, String applicationUuid, + Map<String, String> projectUuidsByKeys, long createdTime) throws SQLException { + Upsert insertApplicationProjectsQuery = context.prepareUpsert("insert into " + + "app_projects(uuid, application_uuid, project_uuid, created_at) " + + "values (?, ?, ?, ?)"); + for (String projectKey : app.getProjects()) { + String applicationProjectUuid = uuidFactory.create(); + String projectUuid = projectUuidsByKeys.get(projectKey); + + // ignore project if it does not exist anymore + if (projectUuid == null) { + continue; + } + + insertApplicationProjectsQuery + .setString(1, applicationProjectUuid) + .setString(2, applicationUuid) + .setString(3, projectUuid) + .setLong(4, createdTime) + .addBatch(); + } + + insertApplicationProjectsQuery.execute().commit(); + } + + private void insertApplicationBranchesProjects(Context context, ViewXml.ViewDef app, String applicationUuid, + Map<String, String> projectUuidsByKeys, long createdTime) throws SQLException { + Map<String, String> appBranchUuidByKey = context.prepareSelect("select kee,uuid from project_branches where project_uuid = ?") + .setString(1, applicationUuid) + .list(r -> new AbstractMap.SimpleEntry<>(r.getString(1), r.getString(2))) + .stream() + .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); + + Upsert insertApplicationsBranchProjectsQuery = context.prepareUpsert("insert into " + + "app_branch_project_branch(uuid, application_uuid, application_branch_uuid, project_uuid, project_branch_uuid, created_at) " + + "values (?, ?, ?, ?, ?, ?)"); + boolean insert = false; + for (ViewXml.ApplicationBranchDef branch : app.getApplicationBranches()) { + String applicationBranchUuid = appBranchUuidByKey.get(branch.getKey()); + // ignore application branch if it does not exist in the DB anymore + if (applicationBranchUuid == null) { + continue; + } + + if (insertApplicationBranchProjects(context, branch, applicationUuid, applicationBranchUuid, projectUuidsByKeys, createdTime, + insertApplicationsBranchProjectsQuery)) { + insert = true; + } + } + + if (insert) { + insertApplicationsBranchProjectsQuery.execute().commit(); + } + } + + private boolean insertApplicationBranchProjects(Context context, ViewXml.ApplicationBranchDef branch, String applicationUuid, + String applicationBranchUuid, Map<String, String> projectUuidsByKeys, long createdTime, + Upsert insertApplicationsBranchProjectsQuery) throws SQLException { + + boolean insert = false; + for (ViewXml.ApplicationProjectDef appProjDef : branch.getProjects()) { + String projectUuid = projectUuidsByKeys.get(appProjDef.getKey()); + + // ignore projects that do not exist in the DB anymore + if (projectUuid != null) { + String projectBranchUuid = context.prepareSelect("select uuid from project_branches where project_uuid = ? and kee = ?") + .setString(1, projectUuid) + .setString(2, appProjDef.getBranch()) + .get(r -> r.getString(1)); + + // ignore project branches that do not exist in the DB anymore + if (projectBranchUuid != null) { + String applicationBranchProjectUuid = uuidFactory.create(); + insertApplicationsBranchProjectsQuery + .setString(1, applicationBranchProjectUuid) + .setString(2, applicationUuid) + .setString(3, applicationBranchUuid) + .setString(4, projectUuid) + .setString(5, projectBranchUuid) + .setLong(6, createdTime) + .addBatch(); + insert = true; + } + } + } + + return insert; + } + + @CheckForNull + private static String getViewsDefinition(DataChange.Context context) throws SQLException { + Select select = context.prepareSelect("select text_value,clob_value from internal_properties where kee=?"); + select.setString(1, VIEWS_DEF_KEY); + return select.get(row -> { + String v = row.getString(1); + if (v != null) { + return v; + } else { + return row.getString(2); + } + }); + } + + private static void cleanUpViewsDefinitionsXml(Context context, Collection<ViewXml.ViewDef> definitions) throws SQLException, IOException { + definitions = definitions.stream() + .filter(d -> !"APP".equals(d.getQualifier())) + .collect(Collectors.toList()); + + StringWriter output = new StringWriter(); + new ViewXml.ViewDefinitionsSerializer().write(definitions, output); + String value = output.toString(); + String statement = UPDATE_INTERNAL_PROP_TEXT_VALUE; + if (mustBeStoredInClob(value)) { + statement = UPDATE_INTERNAL_PROP_CLOB_VALUE; + } + + context.prepareUpsert(statement) + .setString(1, output.toString()) + .setString(2, VIEWS_DEF_KEY) + .execute() + .commit(); + } + + private static boolean mustBeStoredInClob(String value) { + return value.length() > TEXT_VALUE_MAX_LENGTH; + } + + private static class ViewXml { + static final String SCHEMA_VIEWS = "/static/views.xsd"; + static final String VIEWS_HEADER_BARE = "<views>"; + static final Pattern VIEWS_HEADER_BARE_PATTERN = Pattern.compile(VIEWS_HEADER_BARE); + static final String VIEWS_HEADER_FQ = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + + "<views xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://sonarsource.com/schema/views\">"; + + private ViewXml() { + // nothing to do here + } + + private static Map<String, ViewDef> parse(String xml) throws ParserConfigurationException, SAXException, IOException, XMLStreamException { + if (StringUtils.isEmpty(xml)) { + return new LinkedHashMap<>(0); + } + + List<ViewDef> views; + validate(xml); + SMInputFactory inputFactory = initStax(); + SMHierarchicCursor rootC = inputFactory.rootElementCursor(new StringReader(xml)); + rootC.advance(); // <views> + SMInputCursor cursor = rootC.childElementCursor(); + views = parseViewDefinitions(cursor); + + Map<String, ViewDef> result = new LinkedHashMap<>(views.size()); + for (ViewDef def : views) { + result.put(def.getKey(), def); + } + + return result; + } + + private static void validate(String xml) throws IOException, SAXException, ParserConfigurationException { + // Replace bare, namespace unaware header with fully qualified header (with schema declaration) + String fullyQualifiedXml = VIEWS_HEADER_BARE_PATTERN.matcher(xml).replaceFirst(VIEWS_HEADER_FQ); + try (InputStream xsd = MigrateApplicationDefinitionsFromXmlToDb.class.getResourceAsStream(SCHEMA_VIEWS)) { + InputSource viewsDefinition = new InputSource(new InputStreamReader(toInputStream(fullyQualifiedXml, UTF_8), UTF_8)); + + SchemaFactory saxSchemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + saxSchemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + saxSchemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setFeature(FEATURE_SECURE_PROCESSING, true); + parserFactory.setNamespaceAware(true); + parserFactory.setSchema(saxSchemaFactory.newSchema(new SAXSource(new InputSource(xsd)))); + + SAXParser saxParser = parserFactory.newSAXParser(); + saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + saxParser.parse(viewsDefinition, new ViewsValidator()); + } + } + + private static List<ViewDef> parseViewDefinitions(SMInputCursor viewsCursor) throws XMLStreamException { + List<ViewDef> views = new ArrayList<>(); + while (viewsCursor.getNext() != null) { + ViewDef viewDef = new ViewDef(); + viewDef.setKey(viewsCursor.getAttrValue("key")); + viewDef.setDef(Boolean.parseBoolean(viewsCursor.getAttrValue("def"))); + viewDef.setParent(viewsCursor.getAttrValue("parent")); + viewDef.setRoot(viewsCursor.getAttrValue("root")); + SMInputCursor viewCursor = viewsCursor.childElementCursor(); + while (viewCursor.getNext() != null) { + String nodeName = viewCursor.getLocalName(); + parseChildElement(viewDef, viewCursor, nodeName); + } + views.add(viewDef); + } + return views; + } + + private static void parseChildElement(ViewDef viewDef, SMInputCursor viewCursor, String nodeName) throws XMLStreamException { + if (StringUtils.equals(nodeName, "name")) { + viewDef.setName(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "desc")) { + viewDef.setDesc(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "regexp")) { + viewDef.setRegexp(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "language")) { + viewDef.setLanguage(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "tag_key")) { + viewDef.setTagKey(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "tag_value")) { + viewDef.setTagValue(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "p")) { + viewDef.addProject(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "vw-ref")) { + viewDef.addReference(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "qualifier")) { + viewDef.setQualifier(trim(viewCursor.collectDescendantText())); + } else if (StringUtils.equals(nodeName, "branch")) { + parseBranch(viewDef, viewCursor); + } else if (StringUtils.equals(nodeName, "tagsAssociation")) { + parseTagsAssociation(viewDef, viewCursor); + } + } + + private static void parseBranch(ViewDef def, SMInputCursor viewCursor) throws XMLStreamException { + List<ApplicationProjectDef> projects = new ArrayList<>(); + String key = viewCursor.getAttrValue("key"); + SMInputCursor projectCursor = viewCursor.childElementCursor(); + while (projectCursor.getNext() != null) { + if (Objects.equals(projectCursor.getLocalName(), "p")) { + String branch = projectCursor.getAttrValue("branch"); + String projectKey = trim(projectCursor.collectDescendantText()); + projects.add(new ApplicationProjectDef().setKey(projectKey).setBranch(branch)); + } + } + def.getApplicationBranches().add(new ApplicationBranchDef() + .setKey(key) + .setProjects(projects)); + } + + private static void parseTagsAssociation(ViewDef def, SMInputCursor viewCursor) throws XMLStreamException { + SMInputCursor projectCursor = viewCursor.childElementCursor(); + while (projectCursor.getNext() != null) { + def.addTagAssociation(trim(projectCursor.collectDescendantText())); + } + } + + private static SMInputFactory initStax() { + XMLInputFactory xmlFactory = XMLInputFactory.newInstance(); + xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); + xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); + // just so it won't try to load DTD in if there's DOCTYPE + xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); + xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); + return new SMInputFactory(xmlFactory); + } + + private static class ViewDef { + String key = null; + + String parent = null; + + String root = null; + + boolean def = false; + + List<String> p = new ArrayList<>(); + + List<String> vwRef = new ArrayList<>(); + + String name = null; + + String desc = null; + + String regexp = null; + + String language = null; + + String tagKey = null; + + String tagValue = null; + + String qualifier = null; + + List<ApplicationBranchDef> applicationBranches = new ArrayList<>(); + + Set<String> tagsAssociation = new TreeSet<>(); + + public String getKey() { + return key; + } + + public String getParent() { + return parent; + } + + @CheckForNull + public String getRoot() { + return root; + } + + public boolean isDef() { + return def; + } + + public List<String> getProjects() { + return p; + } + + public List<String> getReferences() { + return vwRef; + } + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } + + @CheckForNull + public String getRegexp() { + return regexp; + } + + @CheckForNull + public String getLanguage() { + return language; + } + + @CheckForNull + public String getTagKey() { + return tagKey; + } + + @CheckForNull + public String getTagValue() { + return tagValue; + } + + @CheckForNull + public String getQualifier() { + return qualifier; + } + + public List<ApplicationBranchDef> getApplicationBranches() { + return applicationBranches; + } + + public Set<String> getTagsAssociation() { + return tagsAssociation; + } + + public ViewDef setKey(String key) { + this.key = key; + return this; + } + + public ViewDef setParent(String parent) { + this.parent = parent; + return this; + } + + public ViewDef setRoot(@Nullable String root) { + this.root = root; + return this; + } + + public ViewDef setDef(boolean def) { + this.def = def; + return this; + } + + public ViewDef setProjects(List<String> projects) { + this.p = projects; + return this; + } + + public ViewDef addProject(String project) { + this.p.add(project); + return this; + } + + public ViewDef removeProject(String project) { + this.p.remove(project); + return this; + } + + public ViewDef setName(String name) { + this.name = name; + return this; + } + + public ViewDef setDesc(@Nullable String desc) { + this.desc = desc; + return this; + } + + public ViewDef setRegexp(@Nullable String regexp) { + this.regexp = regexp; + return this; + } + + public ViewDef setLanguage(@Nullable String language) { + this.language = language; + return this; + } + + public ViewDef setTagKey(@Nullable String tagKey) { + this.tagKey = tagKey; + return this; + } + + public ViewDef setTagValue(@Nullable String tagValue) { + this.tagValue = tagValue; + return this; + } + + public ViewDef addReference(String reference) { + this.vwRef.add(reference); + return this; + } + + public ViewDef removeReference(String reference) { + this.vwRef.remove(reference); + return this; + } + + public ViewDef setReferences(List<String> vwRef) { + this.vwRef = vwRef; + return this; + } + + public ViewDef setQualifier(@Nullable String qualifier) { + this.qualifier = qualifier; + return this; + } + + public ViewDef setApplicationBranches(List<ApplicationBranchDef> branches) { + this.applicationBranches = branches; + return this; + } + + public ViewDef addTagAssociation(String tag) { + this.tagsAssociation.add(tag); + return this; + } + + public ViewDef setTagsAssociation(Set<String> tagsAssociation) { + this.tagsAssociation = tagsAssociation; + return this; + } + } + + private static class ApplicationProjectDef { + private String key = null; + private String branch = null; + + public String getKey() { + return key; + } + + public ApplicationProjectDef setKey(String key) { + this.key = key; + return this; + } + + @CheckForNull + public String getBranch() { + return branch; + } + + public ApplicationProjectDef setBranch(@Nullable String branch) { + this.branch = branch; + return this; + } + } + + private static class ApplicationBranchDef { + + private String key = null; + private List<ApplicationProjectDef> p = new ArrayList<>(); + + public String getKey() { + return key; + } + + public ApplicationBranchDef setKey(String key) { + this.key = key; + return this; + } + + public List<ApplicationProjectDef> getProjects() { + return p; + } + + public ApplicationBranchDef setProjects(List<ApplicationProjectDef> p) { + this.p = p; + return this; + } + } + + private static final class ViewsValidator extends DefaultHandler { + @Override + public void error(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void warning(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + throw exception; + } + } + + static class ViewDefinitionsSerializer { + + public void write(Collection<ViewDef> definitions, Writer writer) throws IOException { + writer.append(VIEWS_HEADER_BARE); + + for (ViewDef def : definitions) { + writer.append("<vw"); + writer.append(" key=\"").append(escapeXml(def.getKey())).append("\""); + writer.append(" def=\"").append(Boolean.toString(def.isDef())).append("\""); + String parent = def.getParent(); + if (parent != null) { + writer.append(" root=\"").append(escapeXml(def.getRoot())).append("\""); + writer.append(" parent=\"").append(escapeXml(parent)).append("\""); + } + writer.append(">"); + + writer.append("<name><![CDATA[").append(def.getName()).append("]]></name>"); + writeOptionalElements(writer, def); + + for (String project : def.getProjects()) { + writer.append("<p>").append(project).append("</p>"); + } + for (String ref : def.getReferences()) { + writer.append("<vw-ref><![CDATA[").append(ref).append("]]></vw-ref>"); + } + writeTagsAssociation(writer, def); + writer.append("</vw>"); + } + + writer.append("</views>"); + } + + private static void writeOptionalElements(Writer writer, ViewDef def) throws IOException { + String description = def.getDesc(); + if (description != null) { + writer.append("<desc><![CDATA[").append(description).append("]]></desc>"); + } + String regexp = def.getRegexp(); + if (regexp != null) { + writer.append("<regexp><![CDATA[").append(regexp).append("]]></regexp>"); + } + String language = def.getLanguage(); + if (language != null) { + writer.append("<language><![CDATA[").append(language).append("]]></language>"); + } + String customMeasureKey = def.getTagKey(); + if (customMeasureKey != null) { + writer.append("<tag_key><![CDATA[").append(customMeasureKey).append("]]></tag_key>"); + } + String customMeasureValue = def.getTagValue(); + if (customMeasureValue != null) { + writer.append("<tag_value><![CDATA[").append(customMeasureValue).append("]]></tag_value>"); + } + String qualifier = def.getQualifier(); + if (qualifier != null) { + writer.append("<qualifier><![CDATA[").append(qualifier).append("]]></qualifier>"); + } + } + + private static void writeTagsAssociation(Writer writer, ViewDef definition) throws IOException { + Set<String> tagsAssociation = definition.getTagsAssociation(); + if (tagsAssociation.isEmpty()) { + return; + } + writer.append("<tagsAssociation>"); + for (String tag : tagsAssociation) { + writer.append("<tag>").append(tag).append("</tag>"); + } + writer.append("</tagsAssociation>"); + } + + } + } +} diff --git a/server/sonar-db-migration/src/main/resources/static/views.xsd b/server/sonar-db-migration/src/main/resources/static/views.xsd new file mode 100644 index 00000000000..952ae224cb3 --- /dev/null +++ b/server/sonar-db-migration/src/main/resources/static/views.xsd @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<schema targetNamespace="http://sonarsource.com/schema/views" + elementFormDefault="qualified" xmlns="http://www.w3.org/2001/XMLSchema" + xmlns:tns="http://sonarsource.com/schema/views"> + <element name="views"> + <complexType> + <sequence> + <element name="vw" type="tns:vwType" maxOccurs="unbounded" minOccurs="0"/> + </sequence> + </complexType> + </element> + <complexType name="vwType"> + <sequence> + <choice minOccurs="1" maxOccurs="unbounded"> + <element name="name" maxOccurs="1" minOccurs="1" type="tns:nameType"/> + <element name="desc" maxOccurs="1" minOccurs="0" type="tns:descType"/> + <element name="regexp" maxOccurs="1" minOccurs="0" type="string"/> + <element name="language" maxOccurs="1" minOccurs="0" type="string"/> + <element name="tag_key" maxOccurs="1" minOccurs="0" type="string"/> + <element name="tag_value" maxOccurs="1" minOccurs="0" type="string"/> + <element name="p" maxOccurs="unbounded" minOccurs="0" type="tns:keyType"/> + <element name="remoteView" maxOccurs="unbounded" minOccurs="0" type="tns:remoteViewType"/> + <element name="vw-ref" maxOccurs="unbounded" minOccurs="0" type="tns:keyType"/> + <element name="qualifier" maxOccurs="1" minOccurs="0" type="string"/> + <element name="branch" maxOccurs="unbounded" minOccurs="0" type="tns:branchType"/> + <element name="tagsAssociation" maxOccurs="unbounded" minOccurs="0" type="tns:tagsAssociationType"/> + </choice> + </sequence> + <attribute name="key" use="required" type="tns:keyType"/> + <attribute name="def" type="boolean"/> + <attribute name="root" type="tns:keyType"/> + <attribute name="parent" type="tns:keyType"/> + </complexType> + <complexType name="tagsAssociationType"> + <sequence> + <element name="tag" maxOccurs="unbounded" minOccurs="1" type="string"/> + </sequence> + </complexType> + <complexType name="branchType"> + <sequence> + <choice minOccurs="1" maxOccurs="unbounded"> + <element name="p" maxOccurs="unbounded" minOccurs="1" type="tns:branchProject"/> + </choice> + </sequence> + <attribute name="key" use="required" type="tns:branchKeyType"/> + </complexType> + <complexType name="branchProject"> + <simpleContent> + <extension base="tns:keyType"> + <attribute name="branch" type="tns:branchKeyType" /> + </extension> + </simpleContent> + </complexType> + <complexType name="remoteViewType"> + <sequence> + <choice minOccurs="1" maxOccurs="unbounded"> + <element name="key" minOccurs="1" maxOccurs="1" type="tns:keyType"/> + <element name="remoteKey" minOccurs="1" maxOccurs="1" type="tns:keyType"/> + <element name="name" minOccurs="1" maxOccurs="1" type="tns:nameType"/> + <element name="desc" minOccurs="0" maxOccurs="1" type="tns:descType"/> + <element name="server" minOccurs="1" maxOccurs="1" type="string"/> + </choice> + </sequence> + </complexType> + <simpleType name="keyType"> + <restriction base="string"> + <maxLength value="400"/> + </restriction> + </simpleType> + <simpleType name="nameType"> + <restriction base="string"> + <maxLength value="256"/> + </restriction> + </simpleType> + <simpleType name="descType"> + <restriction base="string"> + <maxLength value="256"/> + </restriction> + </simpleType> + <simpleType name="branchKeyType"> + <restriction base="string"> + <maxLength value="255"/> + </restriction> + </simpleType> +</schema> diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjsTest.java new file mode 100644 index 00000000000..b9e522a0911 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjsTest.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.MigrationStep; + +public class AddIndexToApplicationBranchProjsTest { + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(AddIndexToApplicationBranchProjsTest.class, "schema.sql"); + + private final MigrationStep underTest = new AddIndexToApplicationBranchProjs(db.database()); + + @Test + public void execute() throws SQLException { + underTest.execute(); + + db.assertUniqueIndex("app_branch_project_branch", "uniq_app_branch_proj", "application_branch_uuid", "project_branch_uuid"); + db.assertIndex("app_branch_project_branch", "idx_abpb_app_uuid", "application_uuid"); + db.assertIndex("app_branch_project_branch", "idx_abpb_app_branch_uuid", "application_branch_uuid"); + db.assertIndex("app_branch_project_branch", "idx_abpb_proj_uuid", "project_uuid"); + db.assertIndex("app_branch_project_branch", "idx_abpb_proj_branch_uuid", "project_branch_uuid"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjectsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjectsTest.java new file mode 100644 index 00000000000..3b876b2421c --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjectsTest.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.MigrationStep; + +public class AddIndexToApplicationProjectsTest { + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(AddIndexToApplicationProjectsTest.class, "schema.sql"); + + private final MigrationStep underTest = new AddIndexToApplicationProjects(db.database()); + + @Test + public void execute() throws SQLException { + underTest.execute(); + + db.assertUniqueIndex("app_projects", "uniq_app_projects", "application_uuid", "project_uuid"); + db.assertIndex("app_projects", "idx_app_proj_application_uuid", "application_uuid"); + db.assertIndex("app_projects", "idx_app_proj_project_uuid", "project_uuid"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjsTest.java new file mode 100644 index 00000000000..a459e988743 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjsTest.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.MigrationStep; + +public class AddPkToApplicationBranchProjsTest { + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(AddPkToApplicationBranchProjsTest.class, "schema.sql"); + + private MigrationStep underTest = new AddPkToApplicationBranchProjs(db.database()); + + @Test + public void execute() throws SQLException { + db.assertNoPrimaryKey("app_branch_project_branch"); + underTest.execute(); + db.assertPrimaryKey("app_branch_project_branch", "pk_app_branch_project_branch", "uuid"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjectsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjectsTest.java new file mode 100644 index 00000000000..f5621117ea7 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjectsTest.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.MigrationStep; + +public class AddPkToApplicationProjectsTest { + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(AddPkToApplicationProjectsTest.class, "schema.sql"); + + private MigrationStep underTest = new AddPkToApplicationProjects(db.database()); + + @Test + public void execute() throws SQLException { + db.assertNoPrimaryKey("app_projects"); + underTest.execute(); + db.assertPrimaryKey("app_projects", "pk_app_projects", "uuid"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationProjectsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationProjectsTableTest.java new file mode 100644 index 00000000000..e21cae3874c --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/CreateApplicationProjectsTableTest.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.MigrationStep; +import org.sonar.server.platform.db.migration.version.v86.CreateApplicationProjectsTable; + +public class CreateApplicationProjectsTableTest { + private final static String TABLE_NAME = "app_projects"; + + @Rule + public CoreDbTester db = CoreDbTester.createEmpty(); + + private MigrationStep underTest = new CreateApplicationProjectsTable(db.database()); + + @Test + public void execute() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + underTest.execute(); + db.assertTableExists(TABLE_NAME); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDbTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDbTest.java new file mode 100644 index 00000000000..ad79409fd34 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDbTest.java @@ -0,0 +1,717 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v86; + +import java.sql.SQLException; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.MigrationStep; +import org.xmlunit.builder.DiffBuilder; +import org.xmlunit.builder.Input; +import org.xmlunit.diff.Diff; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.server.platform.db.migration.version.v86.MigrateApplicationDefinitionsFromXmlToDb.TEXT_VALUE_MAX_LENGTH; + +public class MigrateApplicationDefinitionsFromXmlToDbTest { + private static final String QUALIFIER_PROJECT = "TRK"; + private static final String QUALIFIER_APP = "APP"; + private static final long NOW = 100_000_000_000L; + + private static final String PROJECT_1_UUID = "proj1-uuid"; + private static final String PROJECT_1_MASTER_BRANCH_UUID = "proj1-master-uuid"; + private static final String PROJECT_1_BRANCH_1_UUID = "proj1-branch1-uuid"; + private static final String PROJECT_1_BRANCH_2_UUID = "proj1-branch2-uuid"; + private static final String PROJECT_2_UUID = "proj2-uuid"; + private static final String PROJECT_2_MASTER_BRANCH_UUID = "proj2-master-uuid"; + private static final String PROJECT_2_BRANCH_1_UUID = "proj2-branch1-uuid"; + private static final String APP_1_UUID = "app1-uuid"; + private static final String APP_1_MASTER_BRANCH_UUID = "app1-master-uuid"; + private static final String APP_1_BRANCH_1_UUID = "app1-branch1-uuid"; + private static final String APP_1_BRANCH_2_UUID = "app1-branch2-uuid"; + private static final String APP_2_UUID = "app2-uuid"; + private static final String APP_2_MASTER_BRANCH_UUID = "app2-master-uuid"; + private static final String APP_2_BRANCH_1_UUID = "app2-branch1-uuid"; + private static final String EMPTY_XML = "<views></views>"; + + private static final String EMPTY_APP_XML = "<views>\n" + + " <vw key=\"app1\" def=\"false\">\n" + + " <name><![CDATA[app1]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[APP]]></qualifier>\n" + + " </vw>\n" + + "</views>"; + + private static final String APP_WITH_NO_BRANCHES_XML = "<views>\n" + + " <vw key=\"app1-key\" def=\"false\">\n" + + " <name><![CDATA[app1-key]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[APP]]></qualifier>\n" + + " <p>proj1-key</p>\n" + + " <p>proj2-key</p>\n" + + " </vw>\n" + + "</views>"; + + private static final String COMPLEX_XML_BEFORE = "<views>\n" + + " <vw key=\"app1-key\" def=\"false\">\n" + + " <name><![CDATA[app1-key]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[APP]]></qualifier>\n" + + " <p>proj1-key</p>\n" + + " <p>proj2-key</p>\n" + + " <branch key=\"app1-branch1\">\n" + + " <p branch=\"proj1-branch-1\">proj1-key</p>\n" + + " <p branch=\"m1\">proj2-key</p>\n" + + " </branch>\n" + + " <branch key=\"app1-branch2\">\n" + + " <p branch=\"proj1-branch-2\">proj1-key</p>\n" + + " <p branch=\"m1\">proj2-key</p>\n" + + " </branch>\n" + + " </vw>\n" + + " <vw key=\"app2-key\" def=\"false\">\n" + + " <name><![CDATA[app2-key]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[APP]]></qualifier>\n" + + " <p>proj1-key</p>\n" + + " <p>proj2-key</p>\n" + + " <branch key=\"m1\">\n" + + " <p branch=\"proj1-branch-1\">proj1-key</p>\n" + + " <p branch=\"m1\">proj2-key</p>\n" + + " </branch>\n" + + " </vw>\n" + + " <vw key=\"port1\" def=\"true\">\n" + + " <name><![CDATA[port1]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port2\" def=\"false\">\n" + + " <name><![CDATA[port2]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <vw-ref><![CDATA[app1-key]]></vw-ref>\n" + + " <vw-ref><![CDATA[port1]]></vw-ref>\n" + + " </vw>\n" + + " <vw key=\"port3\" def=\"false\">\n" + + " <name><![CDATA[port3]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <p>proj1-key</p>\n" + + " </vw>\n" + + " <vw key=\"port4\" def=\"false\">\n" + + " <name><![CDATA[port4]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"portx\" def=\"false\" root=\"port2\" parent=\"port2\">\n" + + " <name><![CDATA[portx]]></name>\n" + + " </vw>\n" + + " <vw key=\"port5\" def=\"false\">\n" + + " <name><![CDATA[port5]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <tagsAssociation>\n" + + " <tag>tag1</tag>\n" + + " </tagsAssociation>\n" + + " </vw>\n" + + " <vw key=\"port6\" def=\"false\">\n" + + " <name><![CDATA[port6]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <regexp><![CDATA[.*oj.*]]></regexp>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port7\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + "</views>"; + + private static final String COMPLEX_XML_AFTER = "<views>\n" + + " <vw key=\"port1\" def=\"true\">\n" + + " <name><![CDATA[port1]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port2\" def=\"false\">\n" + + " <name><![CDATA[port2]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <vw-ref><![CDATA[app1-key]]></vw-ref>\n" + + " <vw-ref><![CDATA[port1]]></vw-ref>\n" + + " </vw>\n" + + " <vw key=\"port3\" def=\"false\">\n" + + " <name><![CDATA[port3]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <p>proj1-key</p>\n" + + " </vw>\n" + + " <vw key=\"port4\" def=\"false\">\n" + + " <name><![CDATA[port4]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"portx\" def=\"false\" root=\"port2\" parent=\"port2\">\n" + + " <name><![CDATA[portx]]></name>\n" + + " </vw>\n" + + " <vw key=\"port5\" def=\"false\">\n" + + " <name><![CDATA[port5]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <tagsAssociation>\n" + + " <tag>tag1</tag>\n" + + " </tagsAssociation>\n" + + " </vw>\n" + + " <vw key=\"port6\" def=\"false\">\n" + + " <name><![CDATA[port6]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <regexp><![CDATA[.*oj.*]]></regexp>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port7\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + "</views>"; + + private static final String LARGE_XML_BEFORE_AND_AFTER = "<views>\n" + + " <vw key=\"port1\" def=\"true\">\n" + + " <name><![CDATA[port1]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port2\" def=\"false\">\n" + + " <name><![CDATA[port2]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <vw-ref><![CDATA[app1-key]]></vw-ref>\n" + + " <vw-ref><![CDATA[port1]]></vw-ref>\n" + + " </vw>\n" + + " <vw key=\"port3\" def=\"false\">\n" + + " <name><![CDATA[port3]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <p>proj1-key</p>\n" + + " </vw>\n" + + " <vw key=\"port4\" def=\"false\">\n" + + " <name><![CDATA[port4]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"portx\" def=\"false\" root=\"port2\" parent=\"port2\">\n" + + " <name><![CDATA[portx]]></name>\n" + + " </vw>\n" + + " <vw key=\"port5\" def=\"false\">\n" + + " <name><![CDATA[port5]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " <tagsAssociation>\n" + + " <tag>tag1</tag>\n" + + " </tagsAssociation>\n" + + " </vw>\n" + + " <vw key=\"port6\" def=\"false\">\n" + + " <name><![CDATA[port6]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <regexp><![CDATA[.*oj.*]]></regexp>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port7\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port8\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port9\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port10\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port11\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port12\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port13\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port14\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port15\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port16\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port17\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port18\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port19\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port20\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + " <vw key=\"port21\" def=\"false\">\n" + + " <name><![CDATA[port7]]></name>\n" + + " <desc><![CDATA[]]></desc>\n" + + " <tag_key><![CDATA[business_value]]></tag_key>\n" + + " <tag_value><![CDATA[12]]></tag_value>\n" + + " <qualifier><![CDATA[VW]]></qualifier>\n" + + " </vw>\n" + + "</views>"; + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(MigrateApplicationDefinitionsFromXmlToDbTest.class, "schema.sql"); + + private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + private final System2 system2 = new TestSystem2().setNow(NOW); + private final MigrationStep underTest = new MigrateApplicationDefinitionsFromXmlToDb(db.database(), uuidFactory, system2); + + @Test + public void does_nothing_when_no_views_def_property() throws SQLException { + setupProjectsAndApps(); + + underTest.execute(); + + assertThat(db.countSql("select count(*) from app_projects")).isZero(); + assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero(); + assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isZero(); + } + + @Test + public void does_nothing_when_views_def_property_empty_string() throws SQLException { + setupProjectsAndApps(); + insertViewsDefInternalProperty(""); + + underTest.execute(); + + assertThat(db.countSql("select count(*) from app_projects")).isZero(); + assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero(); + } + + @Test + public void does_nothing_when_views_def_property_empty_views_content() throws SQLException { + setupProjectsAndApps(); + insertViewsDefInternalProperty(EMPTY_XML); + + underTest.execute(); + + assertThat(db.countSql("select count(*) from app_projects")).isZero(); + assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero(); + } + + @Test + public void throws_ISE_when_views_def_property_does_not_pass_validation() { + setupProjectsAndApps(); + insertViewsDefInternalProperty("abcdefghi"); + + assertThatThrownBy(underTest::execute) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Failed to migrate views definitions property."); + + assertThat(db.countSql("select count(*) from app_projects")).isZero(); + assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero(); + } + + @Test + public void migrates_applications_to_new_tables() throws SQLException { + setupProjectsAndApps(); + insertViewsDefInternalProperty(COMPLEX_XML_BEFORE); + + underTest.execute(); + + assertThat(db.select("select uuid from projects")) + .extracting(r -> r.get("UUID")) + .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID); + assertThat(db.select("select application_uuid, project_uuid from app_projects")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID), + tuple(APP_2_UUID, PROJECT_1_UUID), + tuple(APP_2_UUID, PROJECT_2_UUID)); + assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID), + tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_2_UUID, PROJECT_1_BRANCH_2_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_2_UUID, PROJECT_2_BRANCH_1_UUID), + tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID), + tuple(APP_2_UUID, PROJECT_2_UUID, APP_2_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID)); + } + + @Test + public void migrates_applications_without_application_branches_to_new_tables() throws SQLException { + setupFullProject1(); + setupProject2(); + setupApp1WithNoBranches(); + insertViewsDefInternalProperty(APP_WITH_NO_BRANCHES_XML); + + underTest.execute(); + + assertThat(db.select("select uuid from projects")) + .extracting(r -> r.get("UUID")) + .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID); + assertThat(db.select("select application_uuid, project_uuid from app_projects")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID)); + assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero(); + } + + @Test + public void migrates_app_with_0_projects_in_views_definition() throws SQLException { + setupProjectsAndApps(); + insertViewsDefInternalProperty(EMPTY_APP_XML); + + underTest.execute(); + + assertThat(db.select("select uuid from projects")) + .extracting(r -> r.get("UUID")) + .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID); + assertThat(db.countSql("select count(*) from app_projects")).isZero(); + assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero(); + } + + @Test + public void skips_apps_that_are_present_in_views_definition_but_not_in_db() throws SQLException { + setupFullProject1(); + setupProject2(); + setupApp1WithTwoBranches(); + insertViewsDefInternalProperty(COMPLEX_XML_BEFORE); + + underTest.execute(); + + assertThat(db.select("select uuid from projects")) + .extracting(r -> r.get("UUID")) + .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID); + assertThat(db.select("select application_uuid, project_uuid from app_projects")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID)); + assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID), + tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_2_UUID, PROJECT_1_BRANCH_2_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_2_UUID, PROJECT_2_BRANCH_1_UUID)); + } + + @Test + public void skips_app_branches_that_are_present_in_views_definition_but_not_in_db() throws SQLException { + setupFullProject1(); + setupProject2(); + setupApp1WithNoBranches(); + setupApp2(); + insertViewsDefInternalProperty(COMPLEX_XML_BEFORE); + + underTest.execute(); + + assertThat(db.select("select uuid from projects")) + .extracting(r -> r.get("UUID")) + .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID); + assertThat(db.select("select application_uuid, project_uuid from app_projects")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID), + tuple(APP_2_UUID, PROJECT_1_UUID), + tuple(APP_2_UUID, PROJECT_2_UUID)); + assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID), + tuple(APP_2_UUID, PROJECT_2_UUID, APP_2_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID)); + } + + @Test + public void skips_projects_that_are_present_in_apps_views_definitions_but_not_in_db() throws SQLException { + setupPartialProject1(); + setupApp1WithTwoBranches(); + setupApp2(); + insertViewsDefInternalProperty(COMPLEX_XML_BEFORE); + + underTest.execute(); + + assertThat(db.select("select uuid from projects")) + .extracting(r -> r.get("UUID")) + .containsExactlyInAnyOrder(PROJECT_1_UUID, APP_1_UUID, APP_2_UUID); + assertThat(db.select("select application_uuid, project_uuid from app_projects")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID), + tuple(APP_2_UUID, PROJECT_1_UUID)); + assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID), + tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID)); + } + + @Test + public void skips_projects_branches_that_are_present_in_apps_views_definitions_but_not_in_db() throws SQLException { + setupPartialProject1(); + setupProject2(); + setupApp1WithTwoBranches(); + setupApp2(); + insertViewsDefInternalProperty(COMPLEX_XML_BEFORE); + + underTest.execute(); + + assertThat(db.select("select uuid from projects")) + .extracting(r -> r.get("UUID")) + .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID); + assertThat(db.select("select application_uuid, project_uuid from app_projects")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID), + tuple(APP_2_UUID, PROJECT_1_UUID), + tuple(APP_2_UUID, PROJECT_2_UUID)); + assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch")) + .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID")) + .containsExactlyInAnyOrder( + tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID), + tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_2_UUID, PROJECT_2_BRANCH_1_UUID), + tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID), + tuple(APP_2_UUID, PROJECT_2_UUID, APP_2_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID)); + } + + @Test + public void removes_application_definitions_from_xml() throws SQLException { + setupProjectsAndApps(); + insertViewsDefInternalProperty(COMPLEX_XML_BEFORE); + + underTest.execute(); + + assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isOne(); + assertViewsXmlDefinitionSimilar(COMPLEX_XML_AFTER, false); + } + + @Test + public void removes_application_definitions_from_large_xmls() throws SQLException { + setupProjectsAndApps(); + insertViewsDefInternalProperty(LARGE_XML_BEFORE_AND_AFTER); + + underTest.execute(); + + assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isOne(); + assertViewsXmlDefinitionSimilar(LARGE_XML_BEFORE_AND_AFTER, true); + } + + @Test + public void does_not_change_the_xml_if_there_are_no_application_definitions() throws SQLException { + setupProjectsAndApps(); + insertViewsDefInternalProperty(EMPTY_XML); + + underTest.execute(); + + assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isOne(); + assertViewsXmlDefinitionSimilar(EMPTY_XML, false); + } + + private void setupProjectsAndApps() { + setupFullProject1(); + setupProject2(); + setupApp1WithTwoBranches(); + setupApp2(); + } + + private void setupFullProject1() { + setupPartialProject1(); + insertBranch(PROJECT_1_BRANCH_2_UUID, PROJECT_1_UUID, "proj1-branch-2"); + } + + private void setupPartialProject1() { + insertProject(PROJECT_1_UUID, "proj1-key", QUALIFIER_PROJECT); + insertBranch(PROJECT_1_MASTER_BRANCH_UUID, PROJECT_1_UUID, "master"); + insertBranch(PROJECT_1_BRANCH_1_UUID, PROJECT_1_UUID, "proj1-branch-1"); + } + + private void setupProject2() { + insertProject(PROJECT_2_UUID, "proj2-key", QUALIFIER_PROJECT); + insertBranch(PROJECT_2_MASTER_BRANCH_UUID, PROJECT_2_UUID, "master"); + insertBranch(PROJECT_2_BRANCH_1_UUID, PROJECT_2_UUID, "m1"); + } + + private void setupApp1WithNoBranches() { + insertProject(APP_1_UUID, "app1-key", QUALIFIER_APP); + insertBranch(APP_1_MASTER_BRANCH_UUID, APP_1_UUID, "master"); + } + + private void setupApp1WithOneBranch() { + setupApp1WithNoBranches(); + insertBranch(APP_1_BRANCH_1_UUID, APP_1_UUID, "app1-branch1"); + } + + private void setupApp1WithTwoBranches() { + setupApp1WithOneBranch(); + insertBranch(APP_1_BRANCH_2_UUID, APP_1_UUID, "app1-branch2"); + } + + private void setupApp2() { + insertProject(APP_2_UUID, "app2-key", QUALIFIER_APP); + insertBranch(APP_2_MASTER_BRANCH_UUID, APP_2_UUID, "master"); + insertBranch(APP_2_BRANCH_1_UUID, APP_2_UUID, "m1"); + } + + private void insertViewsDefInternalProperty(@Nullable String xml) { + String valueColumn = "text_value"; + if (xml != null && xml.length() > TEXT_VALUE_MAX_LENGTH) { + valueColumn = "clob_value"; + } + + db.executeInsert("internal_properties", + "kee", "views.def", + "is_empty", "false", + valueColumn, xml, + "created_at", system2.now()); + } + + private void insertProject(String uuid, String key, String qualifier) { + db.executeInsert("PROJECTS", + "UUID", uuid, + "KEE", key, + "QUALIFIER", qualifier, + "ORGANIZATION_UUID", uuid + "-key", + "TAGS", "tag1", + "PRIVATE", Boolean.toString(false), + "UPDATED_AT", System2.INSTANCE.now()); + } + + private void insertBranch(String uuid, String projectUuid, String key) { + db.executeInsert( + "PROJECT_BRANCHES", + "UUID", uuid, + "PROJECT_UUID", projectUuid, + "KEE", key, + "BRANCH_TYPE", "BRANCH", + "MERGE_BRANCH_UUID", null, + "CREATED_AT", System2.INSTANCE.now(), + "UPDATED_AT", System2.INSTANCE.now(), + "NEED_ISSUE_SYNC", Boolean.toString(false)); + } + + private void assertViewsXmlDefinitionSimilar(final String expectedValue, final boolean expectClob) { + Map<String, Object> result = db.selectFirst("select text_value, clob_value from internal_properties where kee='views.def'"); + String textValue = (String) result.get("TEXT_VALUE"); + String clobValue = (String) result.get("CLOB_VALUE"); + + String existingValue; + if (expectClob) { + existingValue = clobValue; + assertThat(textValue).isNull(); + } else { + existingValue = textValue; + assertThat(clobValue).isNull(); + } + + Diff diff = DiffBuilder + .compare(Input.fromString(expectedValue)) + .withTest(Input.fromString(existingValue)) + .ignoreWhitespace() + .ignoreComments() + .checkForSimilar() + .build(); + assertThat(diff.getDifferences()) + .as(expectedValue) + .isEmpty(); + } +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjsTest/schema.sql new file mode 100644 index 00000000000..845d2fd0b18 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationBranchProjsTest/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE "APP_BRANCH_PROJECT_BRANCH"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "APPLICATION_BRANCH_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "PROJECT_BRANCH_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "APP_BRANCH_PROJECT_BRANCH" ADD CONSTRAINT "PK_APP_BRANCH_PROJECT_BRANCH" PRIMARY KEY("UUID"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjectsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjectsTest/schema.sql new file mode 100644 index 00000000000..7c2627968a6 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddIndexToApplicationProjectsTest/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE "APP_PROJECTS"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "APP_PROJECTS" ADD CONSTRAINT "PK_APP_PROJECTS" PRIMARY KEY("UUID"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjsTest/schema.sql new file mode 100644 index 00000000000..3270865158a --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationBranchProjsTest/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE "APP_BRANCH_PROJECT_BRANCH"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "APPLICATION_BRANCH_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "PROJECT_BRANCH_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjectsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjectsTest/schema.sql new file mode 100644 index 00000000000..3cb13c2066a --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/AddPkToApplicationProjectsTest/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE "APP_PROJECTS"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDbTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDbTest/schema.sql new file mode 100644 index 00000000000..dc3010a47b5 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v86/MigrateApplicationDefinitionsFromXmlToDbTest/schema.sql @@ -0,0 +1,66 @@ +CREATE TABLE "PROJECTS"( + "UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(400) NOT NULL, + "QUALIFIER" VARCHAR(10) NOT NULL, + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(2000), + "DESCRIPTION" VARCHAR(2000), + "PRIVATE" BOOLEAN NOT NULL, + "TAGS" VARCHAR(500), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT NOT NULL +); +ALTER TABLE "PROJECTS" ADD CONSTRAINT "PK_NEW_PROJECTS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_PROJECTS_KEE" ON "PROJECTS"("KEE"); +CREATE INDEX "IDX_QUALIFIER" ON "PROJECTS"("QUALIFIER"); + +CREATE TABLE "PROJECT_BRANCHES"( + "UUID" VARCHAR(50) NOT NULL, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "BRANCH_TYPE" VARCHAR(12) NOT NULL, + "MERGE_BRANCH_UUID" VARCHAR(50), + "PULL_REQUEST_BINARY" BLOB, + "MANUAL_BASELINE_ANALYSIS_UUID" VARCHAR(40), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL, + "EXCLUDE_FROM_PURGE" BOOLEAN DEFAULT FALSE NOT NULL, + "NEED_ISSUE_SYNC" BOOLEAN NOT NULL +); +ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_PROJECT_BRANCHES" ON "PROJECT_BRANCHES"("BRANCH_TYPE", "PROJECT_UUID", "KEE"); + +CREATE TABLE "INTERNAL_PROPERTIES"( + "KEE" VARCHAR(20) NOT NULL, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "INTERNAL_PROPERTIES" ADD CONSTRAINT "PK_INTERNAL_PROPERTIES" PRIMARY KEY("KEE"); + +CREATE TABLE "APP_BRANCH_PROJECT_BRANCH"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "APPLICATION_BRANCH_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "PROJECT_BRANCH_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "APP_BRANCH_PROJECT_BRANCH" ADD CONSTRAINT "PK_APP_BRANCH_PROJECT_BRANCH" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_APP_BRANCH_PROJ" ON "APP_BRANCH_PROJECT_BRANCH"("APPLICATION_BRANCH_UUID", "PROJECT_BRANCH_UUID"); +CREATE INDEX "IDX_ABPB_APP_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("APPLICATION_UUID"); +CREATE INDEX "IDX_ABPB_APP_BRANCH_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("APPLICATION_BRANCH_UUID"); +CREATE INDEX "IDX_ABPB_PROJ_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("PROJECT_UUID"); +CREATE INDEX "IDX_ABPB_PROJ_BRANCH_UUID" ON "APP_BRANCH_PROJECT_BRANCH"("PROJECT_BRANCH_UUID"); + +CREATE TABLE "APP_PROJECTS"( + "UUID" VARCHAR(40) NOT NULL, + "APPLICATION_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "APP_PROJECTS" ADD CONSTRAINT "PK_APP_PROJECTS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_APP_PROJECTS" ON "APP_PROJECTS"("APPLICATION_UUID", "PROJECT_UUID"); +CREATE INDEX "IDX_APP_PROJ_APPLICATION_UUID" ON "APP_PROJECTS"("APPLICATION_UUID"); +CREATE INDEX "IDX_APP_PROJ_PROJECT_UUID" ON "APP_PROJECTS"("PROJECT_UUID"); diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/Visibility.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/Visibility.java index 837fed756cf..3c60ff4621d 100644 --- a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/Visibility.java +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/Visibility.java @@ -43,7 +43,7 @@ public enum Visibility { return label; } - boolean isPrivate() { + public boolean isPrivate() { return isPrivate; } diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java index 3fa2dc6b13f..05c58ae67e0 100644 --- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java +++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java @@ -187,12 +187,17 @@ public class UserSessionRule implements TestRule, UserSession { return this; } + public UserSessionRule registerProjects(ProjectDto... projectDtos) { + ensureAbstractMockUserSession().registerProjects(projectDtos); + return this; + } + public UserSessionRule addProjectPermission(String projectPermission, ComponentDto... components) { ensureAbstractMockUserSession().addProjectPermission(projectPermission, components); return this; } - public UserSessionRule addProjectPermission(String projectPermission, ProjectDto projectDto) { + public UserSessionRule addProjectPermission(String projectPermission, ProjectDto... projectDto) { ensureAbstractMockUserSession().addProjectPermission(projectPermission, projectDto); return this; } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java index acadb402952..ec1c64e5f04 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java @@ -61,7 +61,6 @@ public class ComponentCleanerService { } public void deleteBranch(DbSession dbSession, BranchDto branch) { - // TODO: detect if other branches depend on it? dbClient.purgeDao().deleteBranch(dbSession, branch.getUuid()); projectIndexers.commitAndIndexBranches(dbSession, singletonList(branch), PROJECT_DELETION); } @@ -72,6 +71,12 @@ public class ComponentCleanerService { projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), PROJECT_DELETION); } + public void deleteApplication(DbSession dbSession, ProjectDto application) { + dbClient.purgeDao().deleteProject(dbSession, application.getUuid()); + dbClient.userDao().cleanHomepage(dbSession, application); + projectIndexers.commitAndIndexProjects(dbSession, singletonList(application), PROJECT_DELETION); + } + public void delete(DbSession dbSession, ComponentDto project) { checkArgument(!hasNotProjectScope(project) && !isNotDeletable(project) && project.getMainBranchProjectUuid() == null, "Only projects can be deleted"); dbClient.purgeDao().deleteProject(dbSession, project.uuid()); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java index 3043090204d..dc0fce4b712 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java @@ -29,7 +29,7 @@ import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.System2; import org.sonar.core.i18n.I18n; -import org.sonar.core.util.Uuids; +import org.sonar.core.util.UuidFactory; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.BranchDto; @@ -57,16 +57,18 @@ public class ComponentUpdater { private final PermissionTemplateService permissionTemplateService; private final FavoriteUpdater favoriteUpdater; private final ProjectIndexers projectIndexers; + private final UuidFactory uuidFactory; public ComponentUpdater(DbClient dbClient, I18n i18n, System2 system2, PermissionTemplateService permissionTemplateService, FavoriteUpdater favoriteUpdater, - ProjectIndexers projectIndexers) { + ProjectIndexers projectIndexers, UuidFactory uuidFactory) { this.dbClient = dbClient; this.i18n = i18n; this.system2 = system2; this.permissionTemplateService = permissionTemplateService; this.favoriteUpdater = favoriteUpdater; this.projectIndexers = projectIndexers; + this.uuidFactory = uuidFactory; } /** @@ -105,7 +107,7 @@ public class ComponentUpdater { "Could not create %s, key already exists: %s", getQualifierToDisplay(newComponent.qualifier()), newComponent.key()); long now = system2.now(); - String uuid = Uuids.create(); + String uuid = uuidFactory.create(); ComponentDto component = new ComponentDto() .setOrganizationUuid(newComponent.getOrganizationUuid()) .setUuid(uuid) @@ -116,6 +118,7 @@ public class ComponentUpdater { .setProjectUuid(uuid) .setDbKey(newComponent.key()) .setName(newComponent.name()) + .setDescription(newComponent.description()) .setLongName(newComponent.name()) .setScope(Scopes.PROJECT) .setQualifier(newComponent.qualifier()) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java index 99db6475cca..0208be489de 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java @@ -19,6 +19,8 @@ */ package org.sonar.server.component; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import static java.util.Objects.requireNonNull; @@ -33,6 +35,7 @@ public class NewComponent { private final String key; private final String qualifier; private final String name; + private final String description; private final boolean isPrivate; private NewComponent(NewComponent.Builder builder) { @@ -41,6 +44,7 @@ public class NewComponent { this.qualifier = builder.qualifier; this.name = builder.name; this.isPrivate = builder.isPrivate; + this.description = builder.description; } public static Builder newComponentBuilder() { @@ -67,7 +71,13 @@ public class NewComponent { return isPrivate; } + @CheckForNull + public String description() { + return description; + } + public static class Builder { + private String description; private String organizationUuid; private String key; private String qualifier = PROJECT; @@ -103,6 +113,11 @@ public class NewComponent { return this; } + public Builder setDescription(@Nullable String description) { + this.description = description; + return this; + } + public NewComponent build() { requireNonNull(organizationUuid, "organization uuid can't be null"); checkComponentKey(key); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java index c7468e0623a..f6f804a8088 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java @@ -34,6 +34,7 @@ import org.sonar.ce.queue.CeQueue; import org.sonar.ce.queue.CeQueueImpl; import org.sonar.ce.queue.CeTaskSubmit; import org.sonar.core.i18n.I18n; +import org.sonar.core.util.SequenceUuidFactory; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.ce.CeTaskTypes; @@ -92,7 +93,7 @@ public class ReportSubmitterTest { private TestProjectIndexers projectIndexers = new TestProjectIndexers(); private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class); private ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), mock(System2.class), permissionTemplateService, - new FavoriteUpdater(db.getDbClient()), projectIndexers); + new FavoriteUpdater(db.getDbClient()), projectIndexers, new SequenceUuidFactory()); private BranchSupport ossEditionBranchSupport = new BranchSupport(); private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), ossEditionBranchSupport); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentCleanerServiceTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentCleanerServiceTest.java index 74645ba954b..da3d4384d56 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentCleanerServiceTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentCleanerServiceTest.java @@ -49,7 +49,7 @@ import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_DELETION; public class ComponentCleanerServiceTest { - private System2 system2 = System2.INSTANCE; + private final System2 system2 = System2.INSTANCE; @Rule public DbTester db = DbTester.create(system2); @@ -57,16 +57,16 @@ public class ComponentCleanerServiceTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - private DbClient dbClient = db.getDbClient(); - private DbSession dbSession = db.getSession(); - private TestProjectIndexers projectIndexers = new TestProjectIndexers(); - private ResourceTypes mockResourceTypes = mock(ResourceTypes.class); - private ComponentCleanerService underTest = new ComponentCleanerService(dbClient, mockResourceTypes, projectIndexers); + private final DbClient dbClient = db.getDbClient(); + private final DbSession dbSession = db.getSession(); + private final TestProjectIndexers projectIndexers = new TestProjectIndexers(); + private final ResourceTypes mockResourceTypes = mock(ResourceTypes.class); + private final ComponentCleanerService underTest = new ComponentCleanerService(dbClient, mockResourceTypes, projectIndexers); @Test public void delete_project_from_db_and_index() { - DbData data1 = insertData(); - DbData data2 = insertData(); + DbData data1 = insertProjectData(); + DbData data2 = insertProjectData(); underTest.delete(dbSession, data1.project); @@ -76,9 +76,9 @@ public class ComponentCleanerServiceTest { @Test public void delete_list_of_projects_from_db_and_index() { - DbData data1 = insertData(); - DbData data2 = insertData(); - DbData data3 = insertData(); + DbData data1 = insertProjectData(); + DbData data2 = insertProjectData(); + DbData data3 = insertProjectData(); underTest.delete(dbSession, asList(data1.project, data2.project)); dbSession.commit(); @@ -89,10 +89,34 @@ public class ComponentCleanerServiceTest { } @Test + public void delete_application_from_db_and_index() { + DbData data1 = insertProjectData(); + DbData data2 = insertProjectData(); + DbData data3 = insertProjectData(); + ProjectDto app1 = insertApplication(data2.project); + ProjectDto app2 = insertApplication(data3.project); + + underTest.deleteApplication(dbSession, app1); + dbSession.commit(); + + assertProjectOrAppExists(app1, false); + assertProjectOrAppExists(app2, true); + assertExists(data1); + assertExists(data2); + assertExists(data3); + } + + private ProjectDto insertApplication(ProjectDto project) { + ProjectDto app = db.components().insertPublicApplicationDto(); + db.components().addApplicationProject(app, project); + return app; + } + + @Test public void delete_branch() { - DbData data1 = insertData(); - DbData data2 = insertData(); - DbData data3 = insertData(); + DbData data1 = insertProjectData(); + DbData data2 = insertProjectData(); + DbData data3 = insertProjectData(); underTest.deleteBranch(dbSession, data1.branch); dbSession.commit(); @@ -139,15 +163,15 @@ public class ComponentCleanerServiceTest { underTest.delete(dbSession, file); } - private DbData insertData() { + private DbData insertProjectData() { OrganizationDto organization = db.organizations().insert(); ComponentDto componentDto = db.components().insertPublicProject(organization); ProjectDto project = dbClient.projectDao().selectByUuid(dbSession, componentDto.uuid()).get(); BranchDto branch = dbClient.branchDao().selectByUuid(dbSession, project.getUuid()).get(); - ComponentDto component = dbClient.componentDao().selectByKey(dbSession, project.getKey()).get(); + RuleDefinitionDto rule = db.rules().insert(); - IssueDto issue = db.issues().insert(rule, project, component); - SnapshotDto analysis = db.components().insertSnapshot(component); + IssueDto issue = db.issues().insert(rule, project, componentDto); + SnapshotDto analysis = db.components().insertSnapshot(componentDto); mockResourceTypeAsValidProject(); return new DbData(project, branch, analysis, issue); } @@ -169,12 +193,17 @@ public class ComponentCleanerServiceTest { } private void assertDataInDb(DbData data, boolean exists) { - assertThat(dbClient.componentDao().selectByUuid(dbSession, data.project.getUuid()).isPresent()).isEqualTo(exists); - assertThat(dbClient.branchDao().selectByUuid(dbSession, data.branch.getUuid()).isPresent()).isEqualTo(exists); + assertProjectOrAppExists(data.project, exists); assertThat(dbClient.snapshotDao().selectByUuid(dbSession, data.snapshot.getUuid()).isPresent()).isEqualTo(exists); assertThat(dbClient.issueDao().selectByKey(dbSession, data.issue.getKey()).isPresent()).isEqualTo(exists); } + private void assertProjectOrAppExists(ProjectDto appOrProject, boolean exists) { + assertThat(dbClient.projectDao().selectByUuid(dbSession, appOrProject.getUuid()).isPresent()).isEqualTo(exists); + assertThat(dbClient.componentDao().selectByUuid(dbSession, appOrProject.getUuid()).isPresent()).isEqualTo(exists); + assertThat(dbClient.branchDao().selectByUuid(dbSession, appOrProject.getUuid()).isPresent()).isEqualTo(exists); + } + private static class DbData { final ProjectDto project; final BranchDto branch; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java index 915b6adea8c..7fa9a4617bd 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java @@ -26,6 +26,7 @@ import org.junit.rules.ExpectedException; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.System2; +import org.sonar.core.util.SequenceUuidFactory; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.component.BranchDto; @@ -70,7 +71,7 @@ public class ComponentUpdaterTest { private ComponentUpdater underTest = new ComponentUpdater(db.getDbClient(), i18n, system2, permissionTemplateService, new FavoriteUpdater(db.getDbClient()), - projectIndexers); + projectIndexers, new SequenceUuidFactory()); @Test public void persist_and_index_when_creating_project() { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/CreateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/CreateActionTest.java index ebc7449281e..369ff2ecbcb 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/CreateActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/CreateActionTest.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; +import org.sonar.core.util.SequenceUuidFactory; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; @@ -92,7 +93,7 @@ public class CreateActionTest { new ProjectsWsSupport(db.getDbClient(), defaultOrganizationProvider, billingValidations), db.getDbClient(), userSession, new ComponentUpdater(db.getDbClient(), i18n, system2, permissionTemplateService, new FavoriteUpdater(db.getDbClient()), - projectIndexers))); + projectIndexers, new SequenceUuidFactory()))); @Test public void create_project() { |