From 6ad72a24b224d7949ad790b70ef4ae8352ba1899 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Wed, 11 Aug 2021 12:17:26 -0500 Subject: [PATCH] SONAR-15259 Create new tables, daos, dtos and mappers --- .../src/main/java/org/sonar/db/MyBatis.java | 9 + .../org/sonar/db/portfolio/PortfolioDao.java | 126 ++++++++++ .../org/sonar/db/portfolio/PortfolioDto.java | 182 ++++++++++++++ .../sonar/db/portfolio/PortfolioMapper.java | 69 +++++ .../db/portfolio/PortfolioProjectDto.java | 63 +++++ .../db/portfolio/PortfolioReferenceDto.java | 64 +++++ .../sonar/db/portfolio/PortfolioService.java | 212 ++++++++++++++++ .../org/sonar/db/portfolio/ReferenceDto.java | 60 +++++ .../sonar/db/portfolio/PortfolioMapper.xml | 208 ++++++++++++++++ server/sonar-db-dao/src/schema/schema-sq.ddl | 31 +++ .../sonar/db/portfolio/PortfolioDaoTest.java | 235 ++++++++++++++++++ .../sonar/db/portfolio/PortfolioDtoTest.java | 55 ++++ .../db/migration/step/CreateTableChange.java | 48 ++++ .../v91/CreatePortfolioProjectsTable.java | 46 ++++ .../v91/CreatePortfolioReferencesTable.java | 46 ++++ .../version/v91/CreatePortfoliosTable.java | 55 ++++ .../db/migration/version/v91/DbVersion91.java | 6 +- .../v91/CreatePortfolioProjectsTableTest.java | 55 ++++ .../CreatePortfolioReferencesTableTest.java | 55 ++++ .../v91/CreatePortfoliosTableTest.java | 55 ++++ .../version/v91/DbVersion91Test.java | 2 +- 21 files changed, 1680 insertions(+), 2 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioProjectDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioReferenceDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioService.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/ReferenceDto.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDaoTest.java create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDtoTest.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/CreateTableChange.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTable.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioReferencesTable.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfoliosTable.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioReferencesTableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfoliosTableTest.java 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 2f070acc78b..5d25b239f8f 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 @@ -96,6 +96,10 @@ import org.sonar.db.permission.template.PermissionTemplateMapper; import org.sonar.db.permission.template.PermissionTemplateUserDto; import org.sonar.db.plugin.PluginDto; import org.sonar.db.plugin.PluginMapper; +import org.sonar.db.portfolio.PortfolioDto; +import org.sonar.db.portfolio.PortfolioMapper; +import org.sonar.db.portfolio.PortfolioProjectDto; +import org.sonar.db.portfolio.PortfolioReferenceDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.project.ProjectMapper; import org.sonar.db.property.InternalComponentPropertiesMapper; @@ -193,6 +197,10 @@ public class MyBatis implements Startable { confBuilder.loadAlias("PermissionTemplate", PermissionTemplateDto.class); confBuilder.loadAlias("PermissionTemplateUser", PermissionTemplateUserDto.class); confBuilder.loadAlias("Plugin", PluginDto.class); + confBuilder.loadAlias("Portfolio", PortfolioDto.class); + confBuilder.loadAlias("PortfolioProject", PortfolioProjectDto.class); + confBuilder.loadAlias("PortfolioReference", PortfolioReferenceDto.class); + confBuilder.loadAlias("PrIssue", PrIssueDto.class); confBuilder.loadAlias("ProjectQgateAssociation", ProjectQgateAssociationDto.class); confBuilder.loadAlias("Project", ProjectDto.class); @@ -257,6 +265,7 @@ public class MyBatis implements Startable { PermissionTemplateCharacteristicMapper.class, PermissionTemplateMapper.class, PluginMapper.class, + PortfolioMapper.class, ProjectAlmSettingMapper.class, ProjectLinkMapper.class, ProjectMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java new file mode 100644 index 00000000000..602ca4b6144 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +import static java.util.Collections.singleton; + +import java.util.List; +import java.util.Optional; +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; + +public class PortfolioDao implements Dao { + private final System2 system2; + private final UuidFactory uuidFactory; + + public PortfolioDao(System2 system2, UuidFactory uuidFactory) { + // TODO: Audits missing + this.system2 = system2; + this.uuidFactory = uuidFactory; + } + + public List selectAllRoots(DbSession dbSession) { + return mapper(dbSession).selectAllRoots(); + } + + public Optional selectByKey(DbSession dbSession, String key) { + return Optional.ofNullable(mapper(dbSession).selectByKey(key)); + + } + + public Optional selectByUuid(DbSession dbSession, String uuid) { + return Optional.ofNullable(mapper(dbSession).selectByUuid(uuid)); + + } + + public void insert(DbSession dbSession, PortfolioDto portfolio) { + mapper(dbSession).insert(portfolio); + } + + public void delete(DbSession dbSession, String portfolioUuid) { + mapper(dbSession).deletePortfoliosByUuids(singleton(portfolioUuid)); + mapper(dbSession).deleteReferencesByPortfolioOrReferenceUuids(singleton(portfolioUuid)); + mapper(dbSession).deleteProjectsByPortfolioUuids(singleton(portfolioUuid)); + } + + + public void deleteByUuids(DbSession dbSession, Set portfolioUuids) { + if (portfolioUuids.isEmpty()) { + return; + } + mapper(dbSession).deleteByUuids(portfolioUuids); + } + + public List selectAllReferencesToPortfolios(DbSession dbSession) { + return mapper(dbSession).selectAllReferencesToPortfolios(); + } + + public List selectTree(DbSession dbSession, String portfolioUuid) { + return mapper(dbSession).selectTree(portfolioUuid); + } + + public void addReference(DbSession dbSession, String portfolioUuid, String referenceUuid) { + mapper(dbSession).insertReference(new PortfolioReferenceDto() + .setUuid(uuidFactory.create()) + .setPortfolioUuid(portfolioUuid) + .setReferenceUuid(referenceUuid) + .setCreatedAt(system2.now())); + } + + public Set getReferences(DbSession dbSession, String portfolioUuid) { + return mapper(dbSession).selectReferences(portfolioUuid); + } + + public List selectReferencersByKey(DbSession dbSession, String referenceKey) { + return mapper(dbSession).selectReferencersByKey(referenceKey); + } + + public Set getProjects(DbSession dbSession, String portfolioUuid) { + return mapper(dbSession).selectProjects(portfolioUuid); + } + + Set getAllProjectsInHierarchy(DbSession dbSession, String rootUuid) { + return mapper(dbSession).selectAllProjectsInHierarchy(rootUuid); + } + + public void addProject(DbSession dbSession, String portfolioUuid, String projectUuid) { + mapper(dbSession).insertProject(new PortfolioProjectDto() + .setUuid(uuidFactory.create()) + .setPortfolioUuid(portfolioUuid) + .setProjectUuid(projectUuid) + .setCreatedAt(system2.now())); + } + + public void update(DbSession dbSession, PortfolioDto portfolio) { + portfolio.setUpdatedAt(system2.now()); + mapper(dbSession).update(portfolio); + } + + private static PortfolioMapper mapper(DbSession session) { + return session.getMapper(PortfolioMapper.class); + } + + public void deleteProjects(DbSession dbSession, String portfolioUuid) { + mapper(dbSession).deleteProjects(portfolioUuid); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDto.java new file mode 100644 index 00000000000..80ae264af5e --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDto.java @@ -0,0 +1,182 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +import java.util.Objects; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class PortfolioDto { + public enum SelectionMode { + NONE, MANUAL, REGEXP, REST, TAGS + } + + private String uuid; + private String kee; + private String name; + private String description; + private boolean isPrivate = false; + + private String rootUuid; + private String parentUuid; + private String selectionMode; + private String selectionExpression; + + private long createdAt; + private long updatedAt; + + public PortfolioDto() { + // nothing to do here + } + + public String getRootUuid() { + return rootUuid; + } + + public PortfolioDto setRootUuid(String rootUuid) { + this.rootUuid = rootUuid; + return this; + } + + @CheckForNull + public String getParentUuid() { + return parentUuid; + } + + public PortfolioDto setParentUuid(@Nullable String parentUuid) { + this.parentUuid = parentUuid; + return this; + } + + public String getSelectionMode() { + return selectionMode; + } + + public PortfolioDto setSelectionMode(String selectionMode) { + this.selectionMode = selectionMode; + return this; + } + + @CheckForNull + public String getSelectionExpression() { + return selectionExpression; + } + + public PortfolioDto setSelectionExpression(@Nullable String selectionExpression) { + this.selectionExpression = selectionExpression; + return this; + } + + public long getCreatedAt() { + return createdAt; + } + + public PortfolioDto setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } + + public long getUpdatedAt() { + return updatedAt; + } + + public PortfolioDto setUpdatedAt(long updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public String getUuid() { + return uuid; + } + + public PortfolioDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + /** + * This is the getter used by MyBatis mapper. + */ + public String getKee() { + return kee; + } + + public String getKey() { + return getKee(); + } + + /** + * This is the setter used by MyBatis mapper. + */ + public PortfolioDto setKee(String kee) { + this.kee = kee; + return this; + } + + public PortfolioDto setKey(String key) { + return setKee(key); + } + + public boolean isPrivate() { + return isPrivate; + } + + public PortfolioDto setPrivate(boolean aPrivate) { + isPrivate = aPrivate; + return this; + } + + public String getName() { + return name; + } + + public PortfolioDto setName(String name) { + this.name = name; + return this; + } + + @CheckForNull + public String getDescription() { + return description; + } + + public PortfolioDto setDescription(@Nullable String description) { + this.description = description; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PortfolioDto that = (PortfolioDto) o; + return Objects.equals(uuid, that.uuid); + } + + @Override + public int hashCode() { + return uuid != null ? uuid.hashCode() : 0; + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java new file mode 100644 index 00000000000..929ab51f82e --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +import java.util.List; +import java.util.Set; +import javax.annotation.CheckForNull; +import org.apache.ibatis.annotations.Param; + +public interface PortfolioMapper { + @CheckForNull + PortfolioDto selectByKey(String key); + + @CheckForNull + PortfolioDto selectByUuid(String uuid); + + void insert(PortfolioDto portfolio); + + void deleteByUuids(String portfolioUuid); + + void deleteByUuids(@Param("uuids") Set uuids); + + void deletePortfoliosByUuids(@Param("uuids") Set uuids); + + void deleteReferencesByPortfolioOrReferenceUuids(@Param("uuids") Set uuids); + + void deleteProjectsByPortfolioUuids(@Param("uuids") Set uuids); + + void insertReference(PortfolioReferenceDto portfolioReference); + + void insertProject(PortfolioProjectDto portfolioProject); + + List selectTree(String portfolioUuid); + + Set selectReferences(String portfolioUuid); + + List selectReferencersByKey(String referenceKey); + + Set selectProjects(String portfolioUuid); + + List selectAllReferencesToPortfolios(); + + Set selectAllProjectsInHierarchy(String rootUuid); + + void update(PortfolioDto portfolio); + + List selectAllRoots(); + + void deleteReferencesTo(String referenceUuid); + + void deleteProjects(String portfolioUuid); +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioProjectDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioProjectDto.java new file mode 100644 index 00000000000..ccdf338235b --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioProjectDto.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +public class PortfolioProjectDto { + private String uuid; + private String portfolioUuid; + private String projectUuid; + private long createdAt; + + public String getUuid() { + return uuid; + } + + public PortfolioProjectDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public String getPortfolioUuid() { + return portfolioUuid; + } + + public PortfolioProjectDto setPortfolioUuid(String portfolioUuid) { + this.portfolioUuid = portfolioUuid; + return this; + } + + public String getProjectUuid() { + return projectUuid; + } + + public PortfolioProjectDto setProjectUuid(String projectUuid) { + this.projectUuid = projectUuid; + return this; + } + + public long getCreatedAt() { + return createdAt; + } + + public PortfolioProjectDto setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioReferenceDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioReferenceDto.java new file mode 100644 index 00000000000..1a39ad08881 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioReferenceDto.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +public class PortfolioReferenceDto { + private String uuid; + private String portfolioUuid; + private String referenceUuid; + private long createdAt; + + public String getUuid() { + return uuid; + } + + public PortfolioReferenceDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public String getPortfolioUuid() { + return portfolioUuid; + } + + public PortfolioReferenceDto setPortfolioUuid(String portfolioUuid) { + this.portfolioUuid = portfolioUuid; + return this; + } + + public String getReferenceUuid() { + return referenceUuid; + } + + public PortfolioReferenceDto setReferenceUuid(String referenceUuid) { + this.referenceUuid = referenceUuid; + return this; + } + + + public long getCreatedAt() { + return createdAt; + } + + public PortfolioReferenceDto setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioService.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioService.java new file mode 100644 index 00000000000..2c16358f579 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioService.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2016-2021 SonarSource SA + * All rights reserved + * mailto:info AT sonarsource DOT com + */ +package org.sonar.db.portfolio; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.project.ProjectDao; +import org.sonar.db.project.ProjectDto; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; + +public class PortfolioService { + private final DbClient dbClient; + private final PortfolioDao portfolioDao; + private final ProjectDao projectDao; + + public PortfolioService(DbClient dbClient, PortfolioDao portfolioDao, ProjectDao projectDao) { + this.dbClient = dbClient; + this.portfolioDao = portfolioDao; + this.projectDao = projectDao; + } + + public void deleteReferencersTo(String referenceKey) { + try (DbSession dbSession = dbClient.openSession(false)) { + List referencers = portfolioDao.selectReferencersByKey(dbSession, referenceKey); + } + } + + public List getRoots() { + try (DbSession dbSession = dbClient.openSession(false)) { + return portfolioDao.selectAllRoots(dbSession); + } + } + + public List getReferencersByKey(String referenceKey) { + try (DbSession dbSession = dbClient.openSession(false)) { + return portfolioDao.selectReferencersByKey(dbSession, referenceKey); + } + } + + public void addReferenceToPortfolio(String portfolioKey, PortfolioDto refPortfolio) { + try (DbSession dbSession = dbClient.openSession(false)) { + PortfolioDto portfolio = selectByKeyOrFail(dbSession, portfolioKey); + Set treeReferenceConnections = getTreeReferenceConnections(dbSession, portfolio.getRootUuid()); + if (treeReferenceConnections.contains(refPortfolio.getRootUuid())) { + throw new IllegalArgumentException(format("Can't reference portfolio '%s' in portfolio '%s' - hierarchy would be inconsistent", + refPortfolio.getKey(), portfolio.getKey())); + } + portfolioDao.addReference(dbSession, portfolio.getUuid(), refPortfolio.getUuid()); + } + } + + public void addReferenceToApplication(String portfolioKey, ProjectDto app) { + // TODO + } + + public void refresh(List portfolios) { + Set rootUuids = portfolios.stream().map(PortfolioDto::getRootUuid).collect(Collectors.toSet()); + // TODO eliminate duplicates based on references + + + } + + public void appendProject(String portfolioKey, String projectKey) { + try (DbSession dbSession = dbClient.openSession(false)) { + PortfolioDto portfolio = selectByKeyOrFail(dbSession, portfolioKey); + ProjectDto project = projectDao.selectProjectByKey(dbSession, projectKey).orElseThrow(() -> new IllegalArgumentException(format("Project '%s' not found", projectKey))); + + String rootUuid = portfolio.getRootUuid(); + Set allProjectUuids = portfolioDao.getAllProjectsInHierarchy(dbSession, rootUuid); + if (allProjectUuids.contains(project.getUuid())) { + // TODO specify which portfolio? + throw new IllegalArgumentException("The project with key %s is already selected in a portfolio in the hierarchy"); + } + portfolioDao.addProject(dbSession, portfolio.getUuid(), project.getUuid()); + } + } + + public void removeProject(String portfolioKey, String projectKey) { + + } + + public void updateSelectionMode(String portfolioKey, String selectionMode, @Nullable String selectionExpression) { + PortfolioDto.SelectionMode mode = PortfolioDto.SelectionMode.valueOf(selectionMode); + try (DbSession dbSession = dbClient.openSession(false)) { + PortfolioDto portfolio = selectByKeyOrFail(dbSession, portfolioKey); + + if (mode.equals(PortfolioDto.SelectionMode.REST)) { + for (PortfolioDto p : portfolioDao.selectTree(dbSession, portfolio.getUuid())) { + if (!p.getUuid().equals(portfolio.getUuid()) && mode.name().equals(portfolio.getSelectionMode())) { + p.setSelectionMode(PortfolioDto.SelectionMode.NONE.name()); + p.setSelectionExpression(null); + portfolioDao.update(dbSession, p); + } + } + } + + portfolio.setSelectionMode(selectionMode); + portfolio.setSelectionExpression(selectionExpression); + + if (!mode.equals(PortfolioDto.SelectionMode.MANUAL)) { + portfolioDao.deleteProjects(dbSession, portfolio.getUuid()); + } + + portfolioDao.update(dbSession, portfolio); + } + } + + /** + * Deletes a portfolio and all of it's children. + * Also deletes references from/to the deleted portfolios and the projects manually assigned to them. + */ + public void delete(String portfolioKey) { + try (DbSession dbSession = dbClient.openSession(false)) { + PortfolioDto portfolio = selectByKeyOrFail(dbSession, portfolioKey); + Set subTree = getChildrenRecursively(dbSession, portfolio.getUuid()).stream().map(PortfolioDto::getUuid).collect(Collectors.toSet()); + portfolioDao.deleteByUuids(dbSession, subTree); + // TODO trigger refresh + } + } + + private PortfolioDto selectByKeyOrFail(DbSession dbSession, String portfolioKey) { + return portfolioDao.selectByKey(dbSession, portfolioKey).orElseThrow(() -> new IllegalArgumentException(format("Portfolio '%s' not found", portfolioKey))); + } + + /** + * Gets all portfolios belonging to a subtree, given its root + */ + private List getChildrenRecursively(DbSession dbSession, String portfolioUuid) { + Map tree = portfolioDao.selectTree(dbSession, portfolioUuid).stream().collect(Collectors.toMap(PortfolioDto::getUuid, x -> x)); + Map> childrenMap = new HashMap<>(); + + for (PortfolioDto dto : tree.values()) { + if (dto.getParentUuid() != null) { + childrenMap.computeIfAbsent(dto.getParentUuid(), x -> new HashSet<>()).add(dto.getUuid()); + } + } + + List subTree = new ArrayList<>(); + LinkedList stack = new LinkedList<>(); + stack.add(portfolioUuid); + + while (!stack.isEmpty()) { + Set children = childrenMap.get(stack.removeFirst()); + for (String child : children) { + subTree.add(tree.get(child)); + stack.add(child); + } + } + + return subTree; + } + + /** + * Returns the root UUIDs of all trees with which the tree with the given root uuid is connected through a reference. + * The connection can be incoming or outgoing, and it can be direct or indirect (through other trees). + * + * As an example, let's consider we have the following hierarchies of portfolios: + * + * A D - - -> F + * |\ | |\ + * B C - - - > E G H + * + * Where C references E and D references F. + * All 3 tree roots are connected to all the other roots. + * + * getTreeReferenceConnections(A) would return [D,F] + * getTreeReferenceConnections(D) would return [A,F] + * getTreeReferenceConnections(F) would return [A,D] + */ + private Set getTreeReferenceConnections(DbSession dbSession, String treeRootUuid) { + List references = portfolioDao.selectAllReferencesToPortfolios(dbSession); + + Map> rootConnections = new HashMap<>(); + + for (ReferenceDto ref : references) { + rootConnections.computeIfAbsent(ref.getSourceRootUuid(), x -> new ArrayList<>()).add(ref.getTargetRootUuid()); + rootConnections.computeIfAbsent(ref.getTargetRootUuid(), x -> new ArrayList<>()).add(ref.getSourceRootUuid()); + } + + LinkedList queue = new LinkedList<>(); + Set transitiveReferences = new HashSet<>(); + + // add all direct references + queue.addAll(rootConnections.getOrDefault(treeRootUuid, emptyList())); + + // resolve all transitive references + while (!queue.isEmpty()) { + String uuid = queue.remove(); + if (!transitiveReferences.contains(uuid)) { + queue.addAll(rootConnections.getOrDefault(treeRootUuid, emptyList())); + transitiveReferences.add(uuid); + } + } + + return transitiveReferences; + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/ReferenceDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/ReferenceDto.java new file mode 100644 index 00000000000..1aa627165fe --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/ReferenceDto.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +public class ReferenceDto { + private String sourceUuid; + private String sourceRootUuid; + private String targetUuid; + private String targetRootUuid; + + public String getSourceUuid() { + return sourceUuid; + } + + public void setSourceUuid(String sourceUuid) { + this.sourceUuid = sourceUuid; + } + + public String getSourceRootUuid() { + return sourceRootUuid; + } + + public void setSourceRootUuid(String sourceRootUuid) { + this.sourceRootUuid = sourceRootUuid; + } + + public String getTargetUuid() { + return targetUuid; + } + + public void setTargetUuid(String targetUuid) { + this.targetUuid = targetUuid; + } + + public String getTargetRootUuid() { + return targetRootUuid; + } + + public void setTargetRootUuid(String targetRootUuid) { + this.targetRootUuid = targetRootUuid; + } + +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml new file mode 100644 index 00000000000..e2a2451f349 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml @@ -0,0 +1,208 @@ + + + + + + p.uuid as uuid, + p.kee as kee, + p.name as name, + p.description as description, + p.private as isPrivate, + p.root_uuid as rootUuid, + p.parent_uuid as parentUuid, + p.selection_mode as selectionMode, + p.selection_expression as selectionExpression, + p.created_at as createdAt, + p.updated_at as updatedAt + + + + + + + + + + + + + + + + + + + + INSERT INTO portfolios ( + kee, + uuid, + name, + description, + private, + root_uuid, + parent_uuid, + selection_mode, + selection_expression, + created_at, + updated_at + ) + VALUES ( + #{kee,jdbcType=VARCHAR}, + #{uuid,jdbcType=VARCHAR}, + #{name,jdbcType=VARCHAR}, + #{description,jdbcType=VARCHAR}, + #{isPrivate,jdbcType=BOOLEAN}, + #{rootUuid,jdbcType=VARCHAR}, + #{parentUuid,jdbcType=VARCHAR}, + #{selectionMode,jdbcType=VARCHAR}, + #{selectionExpression,jdbcType=VARCHAR}, + #{createdAt,jdbcType=BIGINT}, + #{updatedAt,jdbcType=BIGINT} + ) + + + + DELETE FROM portfolios WHERE uuid = #{portfolioUuid,jdbcType=VARCHAR}; + DELETE FROM portfolio_references WHERE portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR}; + DELETE FROM portfolio_projects WHERE portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR}; + + + + + + DELETE FROM portfolios WHERE uuid in + #{uuid,jdbcType=VARCHAR} + + + + DELETE FROM portfolio_references WHERE portfolio_uuid in + #{uuid,jdbcType=VARCHAR} + OR reference_uuid in + #{uuid,jdbcType=VARCHAR} + + + + DELETE FROM portfolio_projects WHERE portfolio_uuid in + #{uuid,jdbcType=VARCHAR} + + + + INSERT INTO portfolio_references ( + uuid, + portfolio_uuid, + reference_uuid, + created_at + ) + VALUES ( + #{uuid,jdbcType=VARCHAR}, + #{portfolioUuid,jdbcType=VARCHAR}, + #{referenceUuid,jdbcType=VARCHAR}, + #{createdAt,jdbcType=BIGINT} + ) + + + + + delete from portfolio_references + where reference_uuid = #{referenceUuid,jdbcType=VARCHAR} + + + + delete from portfolio_projects + where portfolio_uuid = #{portfolioUuid,jdbcType=VARCHAR} + + + + INSERT INTO portfolio_projects ( + uuid, + portfolio_uuid, + project_uuid, + created_at + ) + VALUES ( + #{uuid,jdbcType=VARCHAR}, + #{portfolioUuid,jdbcType=VARCHAR}, + #{projectUuid,jdbcType=VARCHAR}, + #{createdAt,jdbcType=BIGINT} + ) + + + + update portfolios set + name = #{name,jdbcType=VARCHAR}, + description = #{description,jdbcType=VARCHAR}, + selection_mode = #{selectionMode,jdbcType=VARCHAR}, + selection_expression = #{selectionExpression,jdbcType=VARCHAR}, + updated_at = #{updatedAt,jdbcType=BIGINT} + where + uuid = #{uuid,jdbcType=VARCHAR} + + diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl index ed1a1416676..84adb575198 100644 --- a/server/sonar-db-dao/src/schema/schema-sq.ddl +++ b/server/sonar-db-dao/src/schema/schema-sq.ddl @@ -562,6 +562,37 @@ CREATE TABLE "PLUGINS"( ALTER TABLE "PLUGINS" ADD CONSTRAINT "PK_PLUGINS" PRIMARY KEY("UUID"); CREATE UNIQUE INDEX "PLUGINS_KEY" ON "PLUGINS"("KEE"); +CREATE TABLE "PORTFOLIO_PROJECTS"( + "UUID" VARCHAR(40) NOT NULL, + "PORTFOLIO_UUID" VARCHAR(40) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "PORTFOLIO_PROJECTS" ADD CONSTRAINT "PK_PORTFOLIO_PROJECTS" PRIMARY KEY("UUID"); + +CREATE TABLE "PORTFOLIO_REFERENCES"( + "UUID" VARCHAR(40) NOT NULL, + "PORTFOLIO_UUID" VARCHAR(40) NOT NULL, + "REFERENCE_UUID" VARCHAR(40) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "PORTFOLIO_REFERENCES" ADD CONSTRAINT "PK_PORTFOLIO_REFERENCES" PRIMARY KEY("UUID"); + +CREATE TABLE "PORTFOLIOS"( + "UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(2000) NOT NULL, + "DESCRIPTION" VARCHAR(2000), + "ROOT_UUID" VARCHAR(40) NOT NULL, + "PARENT_UUID" VARCHAR(40), + "PRIVATE" BOOLEAN NOT NULL, + "SELECTION_MODE" VARCHAR(50) NOT NULL, + "SELECTION_EXPRESSION" VARCHAR(50), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +ALTER TABLE "PORTFOLIOS" ADD CONSTRAINT "PK_PORTFOLIOS" PRIMARY KEY("UUID"); + CREATE TABLE "PROJECT_ALM_SETTINGS"( "UUID" VARCHAR(40) NOT NULL, "ALM_SETTING_UUID" VARCHAR(40) NOT NULL, diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDaoTest.java new file mode 100644 index 00000000000..bc07b4287e5 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDaoTest.java @@ -0,0 +1,235 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +import java.util.Set; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbTester; +import org.sonar.db.project.ProjectDto; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PortfolioDaoTest { + private final System2 system2 = new AlwaysIncreasingSystem2(1L, 1); + + @Rule + public DbTester db = DbTester.create(system2); + + private final PortfolioDao portfolioDao = new PortfolioDao(system2, UuidFactoryFast.getInstance()); + + @Test + public void selectByKey_returns_empty_if_no_match() { + assertThat(portfolioDao.selectByKey(db.getSession(), "nonexisting")).isEmpty(); + } + + @Test + public void selectAllRoots() { + createPortfolio("p1", null, "p1"); + createPortfolio("p11", "p1", "p1"); + createPortfolio("p111", "p11", "p1"); + createPortfolio("p2", null, "p2"); + + assertThat(portfolioDao.selectAllRoots(db.getSession())).extracting("uuid") + .containsExactlyInAnyOrder("p1", "p2"); + } + + @Test + public void selectByKey_returns_match() { + createPortfolio("name"); + assertThat(portfolioDao.selectByKey(db.getSession(), "key_name")).isNotEmpty(); + } + + @Test + public void selectByUuid_returns_empty_if_no_match() { + assertThat(portfolioDao.selectByUuid(db.getSession(), "nonexisting")).isEmpty(); + } + + @Test + public void selectByUuid_returns_match() { + createPortfolio("name"); + assertThat(portfolioDao.selectByUuid(db.getSession(), "name")).isNotEmpty(); + assertThat(portfolioDao.selectByUuid(db.getSession(), "name").get()) + .extracting("name", "key", "uuid", "description", "rootUuid", "parentUuid", "selectionMode", "selectionExpression", "createdAt", "updatedAt") + .containsExactly("name_name", "key_name", "name", "desc_name", "root", "parent", "mode", "exp", 1000L, 2000L); + } + + @Test + public void update_portfolio() { + createPortfolio("name"); + PortfolioDto dto = portfolioDao.selectByUuid(db.getSession(), "name").get(); + dto.setSelectionMode("newMode"); + dto.setSelectionExpression("newExp"); + dto.setDescription("newDesc"); + dto.setName("newName"); + portfolioDao.update(db.getSession(), dto); + + assertThat(portfolioDao.selectByUuid(db.getSession(), "name").get()) + .extracting("name", "key", "uuid", "description", "private", "rootUuid", "parentUuid", "selectionMode", "selectionExpression", "createdAt", "updatedAt") + .containsExactly("newName", "key_name", "name", "newDesc", true, "root", "parent", "newMode", "newExp", 1000L, 1L); + } + + @Test + public void selectTree() { + createPortfolio("p1", null, "p1"); + createPortfolio("p11", "p1", "p1"); + createPortfolio("p111", "p11", "p1"); + createPortfolio("p12", "p1", "p1"); + + createPortfolio("p2", null, "p2"); + + assertThat(portfolioDao.selectTree(db.getSession(), "p1")).extracting("uuid").containsOnly("p1", "p11", "p111", "p12"); + assertThat(portfolioDao.selectTree(db.getSession(), "p11")).extracting("uuid").containsOnly("p1", "p11", "p111", "p12"); + assertThat(portfolioDao.selectTree(db.getSession(), "p111")).extracting("uuid").containsOnly("p1", "p11", "p111", "p12"); + assertThat(portfolioDao.selectTree(db.getSession(), "p2")).extracting("uuid").containsOnly("p2"); + } + + @Test + public void deleteByUuids() { + createPortfolio("p1"); + createPortfolio("p2" ); + createPortfolio("p3"); + createPortfolio("p4"); + + + portfolioDao.addProject(db.getSession(), "p1", "proj1"); + portfolioDao.addProject(db.getSession(), "p2", "proj1"); + + portfolioDao.addReference(db.getSession(), "p1", "app1"); + portfolioDao.addReference(db.getSession(), "p2", "app1"); + portfolioDao.addReference(db.getSession(), "p4", "p1"); + + portfolioDao.deleteByUuids(db.getSession(), Set.of("p1", "p3")); + assertThat(db.select(db.getSession(), "select uuid from portfolios")).extracting(m -> m.values().iterator().next()) + .containsOnly("p2", "p4"); + assertThat(db.select(db.getSession(), "select portfolio_uuid from portfolio_references")).extracting(m -> m.values().iterator().next()) + .containsOnly("p2"); + assertThat(db.select(db.getSession(), "select portfolio_uuid from portfolio_projects")).extracting(m -> m.values().iterator().next()) + .containsOnly("p2"); + + } + + @Test + public void selectReferencersByKey() { + createPortfolio("portfolio1"); + createPortfolio("portfolio2"); + ProjectDto app1 = db.components().insertPrivateApplicationDto(p -> p.setDbKey("app1")); + portfolioDao.addReference(db.getSession(), "portfolio1", "portfolio2"); + portfolioDao.addReference(db.getSession(), "portfolio1", app1.getUuid()); + portfolioDao.addReference(db.getSession(), "portfolio2", app1.getUuid()); + + assertThat(portfolioDao.selectReferencersByKey(db.getSession(), "key_portfolio2")) + .extracting("uuid").containsOnly("portfolio1"); + + assertThat(portfolioDao.selectReferencersByKey(db.getSession(), "app1")) + .extracting("uuid").containsOnly("portfolio1", "portfolio2"); + + } + + @Test + public void insert_and_select_references() { + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio1")).isEmpty(); + portfolioDao.addReference(db.getSession(), "portfolio1", "app1"); + portfolioDao.addReference(db.getSession(), "portfolio1", "app2"); + portfolioDao.addReference(db.getSession(), "portfolio2", "app3"); + db.commit(); + assertThat(portfolioDao.getReferences(db.getSession(), "portfolio1")).containsExactlyInAnyOrder("app1", "app2"); + assertThat(portfolioDao.getReferences(db.getSession(), "portfolio2")).containsExactlyInAnyOrder("app3"); + assertThat(portfolioDao.getReferences(db.getSession(), "portfolio3")).isEmpty(); + + assertThat(db.countRowsOfTable("portfolio_references")).isEqualTo(3); + assertThat(db.select(db.getSession(), "select created_at from portfolio_references")) + .extracting(m -> m.values().iterator().next()) + .containsExactlyInAnyOrder(1L, 2L, 3L); + } + + @Test + public void insert_and_select_projects() { + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio1")).isEmpty(); + portfolioDao.addProject(db.getSession(), "portfolio1", "project1"); + portfolioDao.addProject(db.getSession(), "portfolio1", "project2"); + portfolioDao.addProject(db.getSession(), "portfolio2", "project2"); + db.commit(); + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio1")).containsExactlyInAnyOrder("project1", "project2"); + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio2")).containsExactlyInAnyOrder("project2"); + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio3")).isEmpty(); + + assertThat(db.countRowsOfTable("portfolio_projects")).isEqualTo(3); + assertThat(db.select(db.getSession(), "select created_at from portfolio_projects")) + .extracting(m -> m.values().iterator().next()) + .containsExactlyInAnyOrder(1L, 2L, 3L); + } + + @Test + public void delete_projects() { + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio1")).isEmpty(); + portfolioDao.addProject(db.getSession(), "portfolio1", "project1"); + portfolioDao.addProject(db.getSession(), "portfolio1", "project2"); + portfolioDao.addProject(db.getSession(), "portfolio2", "project2"); + + portfolioDao.deleteProjects(db.getSession(), "portfolio1"); + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio1")).isEmpty(); + assertThat(portfolioDao.getProjects(db.getSession(), "portfolio2")).containsExactlyInAnyOrder("project2"); + + } + + @Test + public void getAllProjectsInHierarchy() { + createPortfolio("root", null, "root"); + createPortfolio("child1", null, "root"); + createPortfolio("child11", "child1", "root"); + + createPortfolio("root2", null, "root2"); + + portfolioDao.addProject(db.getSession(), "root", "p1"); + portfolioDao.addProject(db.getSession(), "child1", "p2"); + portfolioDao.addProject(db.getSession(), "child11", "p3"); + portfolioDao.addProject(db.getSession(), "root2", "p4"); + + assertThat(portfolioDao.getAllProjectsInHierarchy(db.getSession(), "root")).containsOnly("p1", "p2", "p3"); + assertThat(portfolioDao.getAllProjectsInHierarchy(db.getSession(), "nonexisting")).isEmpty(); + } + + private PortfolioDto createPortfolio(String uuid, @Nullable String parentUuid, String rootUuid) { + PortfolioDto p = new PortfolioDto() + .setName("name_" + uuid) + .setKey("key_" + uuid) + .setUuid(uuid) + .setDescription("desc_" + uuid) + .setRootUuid(rootUuid) + .setParentUuid(parentUuid) + .setPrivate(true) + .setSelectionExpression("exp") + .setSelectionMode("mode") + .setCreatedAt(1000L) + .setUpdatedAt(2000L); + + portfolioDao.insert(db.getSession(), p); + return p; + } + + private PortfolioDto createPortfolio(String name) { + return createPortfolio(name, "parent", "root"); + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDtoTest.java new file mode 100644 index 00000000000..ddf4831301f --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/portfolio/PortfolioDtoTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.portfolio; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PortfolioDtoTest { + @Test + public void setters_and_getters() { + PortfolioDto dto = new PortfolioDto(); + + dto.setDescription("desc") + .setName("name") + .setKee("kee") + .setParentUuid("parent") + .setRootUuid("root") + .setSelectionExpression("exp") + .setSelectionMode("mode") + .setCreatedAt(1000L) + .setUpdatedAt(2000L); + + assertThat(dto.getDescription()).isEqualTo("desc"); + assertThat(dto.getName()).isEqualTo("name"); + assertThat(dto.getKee()).isEqualTo("kee"); + assertThat(dto.getKey()).isEqualTo("kee"); + assertThat(dto.getParentUuid()).isEqualTo("parent"); + assertThat(dto.getRootUuid()).isEqualTo("root"); + assertThat(dto.getSelectionExpression()).isEqualTo("exp"); + assertThat(dto.getSelectionMode()).isEqualTo("mode"); + assertThat(dto.getCreatedAt()).isEqualTo(1000L); + assertThat(dto.getUpdatedAt()).isEqualTo(2000L); + + dto.setKey("new_key"); + assertThat(dto.getKey()).isEqualTo("new_key"); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/CreateTableChange.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/CreateTableChange.java new file mode 100644 index 00000000000..e588d0ea782 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/CreateTableChange.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.step; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; + +public abstract class CreateTableChange extends DdlChange { + protected final String tableName; + + public CreateTableChange(Database db, String tableName) { + super(db); + this.tableName = tableName; + } + + @Override + public final void execute(Context context) throws SQLException { + if (!tableExists()) { + execute(context, tableName); + } + } + + public abstract void execute(Context context, String tableName) throws SQLException; + + private boolean tableExists() throws SQLException { + try (var connection = getDatabase().getDataSource().getConnection()) { + return DatabaseUtils.tableExists(tableName, connection); + } + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTable.java new file mode 100644 index 00000000000..78042f325ef --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTable.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.CreateTableChange; + +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class CreatePortfolioProjectsTable extends CreateTableChange { + + public CreatePortfolioProjectsTable(Database db) { + super(db, "portfolio_projects"); + } + + @Override + public void execute(Context context, String tableName) throws SQLException { + context.execute(new CreateTableBuilder(getDialect(), tableName) + .addPkColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("portfolio_uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("project_uuid").setIsNullable(false).setLimit(UUID_SIZE).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/v91/CreatePortfolioReferencesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioReferencesTable.java new file mode 100644 index 00000000000..13f883cad0d --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioReferencesTable.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.CreateTableChange; + +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class CreatePortfolioReferencesTable extends CreateTableChange { + + public CreatePortfolioReferencesTable(Database db) { + super(db, "portfolio_references"); + } + + @Override + public void execute(Context context, String tableName) throws SQLException { + context.execute(new CreateTableBuilder(getDialect(), tableName) + .addPkColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("portfolio_uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("reference_uuid").setIsNullable(false).setLimit(UUID_SIZE).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/v91/CreatePortfoliosTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfoliosTable.java new file mode 100644 index 00000000000..a25d3b1adcf --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfoliosTable.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.BooleanColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.CreateTableChange; + +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class CreatePortfoliosTable extends CreateTableChange { + + public CreatePortfoliosTable(Database db) { + super(db, "portfolios"); + } + + @Override + public void execute(Context context, String tableName) throws SQLException { + context.execute(new CreateTableBuilder(getDialect(), tableName) + .addPkColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("kee").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("name").setIsNullable(false).setLimit(2000).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("description").setIsNullable(true).setLimit(2000).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("root_uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("parent_uuid").setIsNullable(true).setLimit(UUID_SIZE).build()) + .addColumn(newBooleanColumnDefBuilder().setColumnName("private").setIsNullable(false).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("selection_mode").setIsNullable(false).setLimit(50).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("selection_expression").setIsNullable(true).setLimit(50).build()) + .addColumn(newBigIntegerColumnDefBuilder().setColumnName("created_at").setIsNullable(false).build()) + .addColumn(newBigIntegerColumnDefBuilder().setColumnName("updated_at").setIsNullable(false).build()) + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java index cb77356c1dd..45dc9309a74 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java @@ -35,6 +35,10 @@ public class DbVersion91 implements DbVersion { .add(6007, "Create Audit table", CreateAuditTable.class) .add(6008, "Add column 'removed' to 'plugins' table", AddColumnRemovedToPlugins.class) .add(6009, "Alter column 'client_secret' of 'alm_settings' table to length 160", AlterClientSecretColumnLengthOfAlmSettingsTable.class) - .add(6010, "Alter column 'private_key' of 'alm_settings' table to length 2500", AlterPrivateKeyColumnLengthOfAlmSettingsTable.class); + .add(6010, "Alter column 'private_key' of 'alm_settings' table to length 2500", AlterPrivateKeyColumnLengthOfAlmSettingsTable.class) + .add(6011, "Create 'portfolios' table", CreatePortfoliosTable.class) + .add(6012, "Create 'portfolio_references' table", CreatePortfolioReferencesTable.class) + .add(6013, "Create 'portfolio_projects' table", CreatePortfolioProjectsTable.class) + ; } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTableTest.java new file mode 100644 index 00000000000..f084d2f4cb3 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioProjectsTableTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +public class CreatePortfolioProjectsTableTest { + private static final String TABLE_NAME = "portfolio_projects"; + + @Rule + public final CoreDbTester db = CoreDbTester.createEmpty(); + + private final CreatePortfolioProjectsTable underTest = new CreatePortfolioProjectsTable(db.database()); + + @Test + public void migration_should_create_a_table() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + + underTest.execute(); + + db.assertTableExists(TABLE_NAME); + } + + @Test + public void migration_should_be_reentrant() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + + underTest.execute(); + //re-entrant + underTest.execute(); + + db.assertTableExists(TABLE_NAME); + db.assertPrimaryKey(TABLE_NAME, "pk_portfolio_projects", "uuid"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioReferencesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioReferencesTableTest.java new file mode 100644 index 00000000000..f2bf0814290 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfolioReferencesTableTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +public class CreatePortfolioReferencesTableTest { + private static final String TABLE_NAME = "portfolio_references"; + + @Rule + public final CoreDbTester db = CoreDbTester.createEmpty(); + + private final CreatePortfolioReferencesTable underTest = new CreatePortfolioReferencesTable(db.database()); + + @Test + public void migration_should_create_a_table() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + + underTest.execute(); + + db.assertTableExists(TABLE_NAME); + db.assertPrimaryKey(TABLE_NAME, "pk_portfolio_references", "uuid"); + } + + @Test + public void migration_should_be_reentrant() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + + underTest.execute(); + //re-entrant + underTest.execute(); + + db.assertTableExists(TABLE_NAME); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfoliosTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfoliosTableTest.java new file mode 100644 index 00000000000..d7692207270 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreatePortfoliosTableTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +public class CreatePortfoliosTableTest { + private static final String TABLE_NAME = "portfolios"; + + @Rule + public final CoreDbTester db = CoreDbTester.createEmpty(); + + private final CreatePortfoliosTable underTest = new CreatePortfoliosTable(db.database()); + + @Test + public void migration_should_create_a_table() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + + underTest.execute(); + + db.assertTableExists(TABLE_NAME); + db.assertPrimaryKey(TABLE_NAME, "pk_portfolios", "uuid"); + } + + @Test + public void migration_should_be_reentrant() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + + underTest.execute(); + //re-entrant + underTest.execute(); + + db.assertTableExists(TABLE_NAME); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java index 6bdb2bc880a..e5b51a680b6 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java @@ -41,7 +41,7 @@ public class DbVersion91Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 10); + verifyMigrationCount(underTest, 13); } } -- 2.39.5