import javax.annotation.concurrent.Immutable;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.portfolio.PortfolioDto;
import org.sonar.db.project.ProjectDto;
import static com.google.common.base.MoreObjects.firstNonNull;
return new Component(dto.getUuid(), dto.getUuid());
}
+ public static Component fromDto(PortfolioDto dto) {
+ return new Component(dto.getUuid(), dto.getUuid());
+ }
+
public static Component fromDto(BranchDto dto) {
return new Component(dto.getUuid(), dto.getProjectUuid());
}
"perm_templates_groups",
"perm_tpl_characteristics",
"plugins",
+ "portfolios",
+ "portfolio_projects",
+ "portfolio_references",
"projects",
"project_alm_settings",
"project_branches",
import org.sonar.db.permission.template.PermissionTemplateCharacteristicDao;
import org.sonar.db.permission.template.PermissionTemplateDao;
import org.sonar.db.plugin.PluginDao;
+import org.sonar.db.portfolio.PortfolioDao;
import org.sonar.db.project.ProjectDao;
import org.sonar.db.property.InternalComponentPropertiesDao;
import org.sonar.db.property.InternalPropertiesDao;
PermissionTemplateDao.class,
PluginDao.class,
ProjectDao.class,
+ PortfolioDao.class,
ProjectLinkDao.class,
ProjectMappingsDao.class,
ProjectQgateAssociationDao.class,
import org.sonar.db.permission.template.PermissionTemplateCharacteristicDao;
import org.sonar.db.permission.template.PermissionTemplateDao;
import org.sonar.db.plugin.PluginDao;
+import org.sonar.db.portfolio.PortfolioDao;
import org.sonar.db.project.ProjectDao;
import org.sonar.db.property.InternalComponentPropertiesDao;
import org.sonar.db.property.InternalPropertiesDao;
private final ProjectMappingsDao projectMappingsDao;
private final NewCodePeriodDao newCodePeriodDao;
private final ProjectDao projectDao;
+ private final PortfolioDao portfolioDao;
private final SessionTokensDao sessionTokensDao;
private final SamlMessageIdDao samlMessageIdDao;
private final UserDismissedMessagesDao userDismissedMessagesDao;
internalComponentPropertiesDao = getDao(map, InternalComponentPropertiesDao.class);
newCodePeriodDao = getDao(map, NewCodePeriodDao.class);
projectDao = getDao(map, ProjectDao.class);
+ portfolioDao = getDao(map, PortfolioDao.class);
sessionTokensDao = getDao(map, SessionTokensDao.class);
samlMessageIdDao = getDao(map, SamlMessageIdDao.class);
userDismissedMessagesDao = getDao(map, UserDismissedMessagesDao.class);
return projectDao;
}
+ public PortfolioDao portfolioDao() {
+ return portfolioDao;
+ }
+
public ComponentKeyUpdaterDao componentKeyUpdaterDao() {
return componentKeyUpdaterDao;
}
*/
package org.sonar.db.portfolio;
-import static java.util.Collections.singleton;
-
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
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;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+import static java.util.stream.Collectors.toSet;
+import static org.sonar.db.DatabaseUtils.executeLargeInputs;
public class PortfolioDao implements Dao {
private final System2 system2;
}
public void insert(DbSession dbSession, PortfolioDto portfolio) {
+ if (portfolio.getUuid() == null) {
+ portfolio.setUuid(uuidFactory.create());
+ }
mapper(dbSession).insert(portfolio);
}
mapper(dbSession).deleteProjectsByPortfolioUuids(singleton(portfolioUuid));
}
-
public void deleteByUuids(DbSession dbSession, Set<String> portfolioUuids) {
if (portfolioUuids.isEmpty()) {
return;
return mapper(dbSession).selectReferencersByKey(referenceKey);
}
- public Set<String> getProjects(DbSession dbSession, String portfolioUuid) {
+ public List<ProjectDto> getProjects(DbSession dbSession, String portfolioUuid) {
return mapper(dbSession).selectProjects(portfolioUuid);
}
- Set<String> getAllProjectsInHierarchy(DbSession dbSession, String rootUuid) {
- return mapper(dbSession).selectAllProjectsInHierarchy(rootUuid);
+ public Map<String, Set<String>> getAllProjectsInHierarchy(DbSession dbSession, String rootUuid) {
+ return mapper(dbSession).selectAllProjectsInHierarchy(rootUuid)
+ .stream()
+ .collect(groupingBy(
+ PortfolioProjectDto::getProjectUuid,
+ mapping(PortfolioProjectDto::getPortfolioUuid, toSet())));
}
public void addProject(DbSession dbSession, String portfolioUuid, String projectUuid) {
public void deleteProjects(DbSession dbSession, String portfolioUuid) {
mapper(dbSession).deleteProjects(portfolioUuid);
}
+
+ public Map<String, String> selectKeysByUuids(DbSession dbSession, Collection<String> uuids) {
+ return executeLargeInputs(uuids, uuids1 -> mapper(dbSession).selectByUuids(uuids1)).stream()
+ .collect(Collectors.toMap(PortfolioDto::getUuid, PortfolioDto::getKey));
+ }
}
return this;
}
+ public PortfolioDto setSelectionMode(SelectionMode selectionMode) {
+ this.selectionMode = selectionMode.name();
+ return this;
+ }
+
@CheckForNull
public String getSelectionExpression() {
return selectionExpression;
*/
package org.sonar.db.portfolio;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.apache.ibatis.annotations.Param;
+import org.sonar.db.project.ProjectDto;
public interface PortfolioMapper {
@CheckForNull
List<PortfolioDto> selectReferencersByKey(String referenceKey);
- Set<String> selectProjects(String portfolioUuid);
+ List<ProjectDto> selectProjects(String portfolioUuid);
List<ReferenceDto> selectAllReferencesToPortfolios();
- Set<String> selectAllProjectsInHierarchy(String rootUuid);
+ Set<PortfolioProjectDto> selectAllProjectsInHierarchy(String rootUuid);
+
+ List<PortfolioDto> selectByUuids(@Param("uuids") Collection<String> uuids);
void update(PortfolioDto portfolio);
+++ /dev/null
-/*
- * 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<PortfolioDto> referencers = portfolioDao.selectReferencersByKey(dbSession, referenceKey);
- }
- }
-
- public List<PortfolioDto> getRoots() {
- try (DbSession dbSession = dbClient.openSession(false)) {
- return portfolioDao.selectAllRoots(dbSession);
- }
- }
-
- public List<PortfolioDto> 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<String> 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<PortfolioDto> portfolios) {
- Set<String> 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<String> 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<String> 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<PortfolioDto> getChildrenRecursively(DbSession dbSession, String portfolioUuid) {
- Map<String, PortfolioDto> tree = portfolioDao.selectTree(dbSession, portfolioUuid).stream().collect(Collectors.toMap(PortfolioDto::getUuid, x -> x));
- Map<String, Set<String>> childrenMap = new HashMap<>();
-
- for (PortfolioDto dto : tree.values()) {
- if (dto.getParentUuid() != null) {
- childrenMap.computeIfAbsent(dto.getParentUuid(), x -> new HashSet<>()).add(dto.getUuid());
- }
- }
-
- List<PortfolioDto> subTree = new ArrayList<>();
- LinkedList<String> stack = new LinkedList<>();
- stack.add(portfolioUuid);
-
- while (!stack.isEmpty()) {
- Set<String> 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<String> getTreeReferenceConnections(DbSession dbSession, String treeRootUuid) {
- List<ReferenceDto> references = portfolioDao.selectAllReferencesToPortfolios(dbSession);
-
- Map<String, List<String>> 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<String> queue = new LinkedList<>();
- Set<String> 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;
- }
-
-}
p.updated_at as updatedAt
</sql>
+ <sql id="projectColumns">
+ p.uuid as uuid,
+ 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="selectByUuid" parameterType="String" resultType="Portfolio">
SELECT
<include refid="portfolioColumns"/>
p.uuid=#{uuid,jdbcType=VARCHAR}
</select>
+ <select id="selectByUuids" parameterType="String" resultType="Portfolio">
+ SELECT
+ <include refid="portfolioColumns"/>
+ FROM portfolios p
+ where
+ p.uuid in
+ <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">
+ #{uuid,jdbcType=VARCHAR}
+ </foreach>
+ </select>
+
<select id="selectByKey" parameterType="String" resultType="Portfolio">
SELECT
<include refid="portfolioColumns"/>
p.parent_uuid is null
</select>
- <select id="selectProjects" resultType="String">
- SELECT
- p.project_uuid
- FROM portfolio_projects p
- where
- p.portfolio_uuid=#{portfolioUuid,jdbcType=VARCHAR}
+ <select id="selectProjects" resultType="Project">
+ SELECT
+ <include refid="projectColumns"/>
+ FROM portfolio_projects pp
+ INNER JOIN projects p on p.uuid = pp.project_uuid
+ WHERE
+ pp.portfolio_uuid=#{portfolioUuid,jdbcType=VARCHAR}
</select>
- <select id="selectAllProjectsInHierarchy" resultType="String">
+ <select id="selectAllProjectsInHierarchy" resultType="org.sonar.db.portfolio.PortfolioProjectDto">
SELECT
- pp.project_uuid
+ pp.uuid,
+ pp.project_uuid as projectUuid,
+ pp.portfolio_uuid as portfolioUuid,
+ pp.project_uuid as projectUuid,
+ pp.created_at as createdAt
FROM portfolio_projects pp
INNER JOIN portfolios p
ON pp.portfolio_uuid = p.uuid
CREATE TABLE "PORTFOLIOS"(
"UUID" VARCHAR(40) NOT NULL,
- "KEE" VARCHAR(40) NOT NULL,
+ "KEE" VARCHAR(400) NOT NULL,
"NAME" VARCHAR(2000) NOT NULL,
"DESCRIPTION" VARCHAR(2000),
"ROOT_UUID" VARCHAR(40) NOT NULL,
@Test
public void deleteByUuids() {
createPortfolio("p1");
- createPortfolio("p2" );
+ createPortfolio("p2");
createPortfolio("p3");
createPortfolio("p4");
-
portfolioDao.addProject(db.getSession(), "p1", "proj1");
portfolioDao.addProject(db.getSession(), "p2", "proj1");
@Test
public void insert_and_select_projects() {
+ db.components().insertPrivateProject("project1");
+ db.components().insertPrivateProject("project2");
+
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(), "portfolio1")).extracting(ProjectDto::getUuid).containsExactlyInAnyOrder("project1", "project2");
+ assertThat(portfolioDao.getProjects(db.getSession(), "portfolio2")).extracting(ProjectDto::getUuid).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);
+ .containsExactlyInAnyOrder(3L, 4L, 5L);
}
@Test
public void delete_projects() {
+ db.components().insertPrivateProject("project1");
+ db.components().insertPrivateProject("project2");
+
assertThat(portfolioDao.getProjects(db.getSession(), "portfolio1")).isEmpty();
portfolioDao.addProject(db.getSession(), "portfolio1", "project1");
portfolioDao.addProject(db.getSession(), "portfolio1", "project2");
portfolioDao.deleteProjects(db.getSession(), "portfolio1");
assertThat(portfolioDao.getProjects(db.getSession(), "portfolio1")).isEmpty();
- assertThat(portfolioDao.getProjects(db.getSession(), "portfolio2")).containsExactlyInAnyOrder("project2");
-
+ assertThat(portfolioDao.getProjects(db.getSession(), "portfolio2")).extracting(ProjectDto::getUuid)
+ .containsExactlyInAnyOrder("project2");
}
@Test
public void getAllProjectsInHierarchy() {
+ db.components().insertPrivateProject("p1");
+ db.components().insertPrivateProject("p2");
+ db.components().insertPrivateProject("p3");
+ db.components().insertPrivateProject("p4");
+
createPortfolio("root", null, "root");
createPortfolio("child1", null, "root");
createPortfolio("child11", "child1", "root");
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(), "root").keySet()).containsExactly("p1", "p2", "p3");
assertThat(portfolioDao.getAllProjectsInHierarchy(db.getSession(), "nonexisting")).isEmpty();
}
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
+import org.sonar.db.portfolio.PortfolioDto;
import org.sonar.db.project.ProjectDto;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Arrays.asList;
import static org.sonar.db.component.BranchType.BRANCH;
+import static org.sonar.db.portfolio.PortfolioDto.SelectionMode.NONE;
public class ComponentDbTester {
private final DbTester db;
}
public final ComponentDto insertPublicPortfolio() {
- return insertPublicPortfolio(defaults());
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio().setPrivate(false), false, defaults(), defaults());
}
public final ComponentDto insertPublicPortfolio(String uuid, Consumer<ComponentDto> dtoPopulator) {
- return insertComponentImpl(ComponentTesting.newPortfolio(uuid).setPrivate(false), false, dtoPopulator);
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio(uuid).setPrivate(false), false, dtoPopulator, defaults());
}
public final ComponentDto insertPublicPortfolio(Consumer<ComponentDto> dtoPopulator) {
- return insertComponentImpl(ComponentTesting.newPortfolio().setPrivate(false), false, dtoPopulator);
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio().setPrivate(false), false, dtoPopulator, defaults());
+ }
+
+ public final ComponentDto insertPublicPortfolio(Consumer<ComponentDto> dtoPopulator, Consumer<PortfolioDto> portfolioPopulator) {
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio().setPrivate(false), false, dtoPopulator, portfolioPopulator);
+ }
+
+ public ComponentDto insertComponentAndPortfolio(ComponentDto componentDto, boolean isPrivate, Consumer<ComponentDto> componentPopulator,
+ Consumer<PortfolioDto> portfolioPopulator) {
+ insertComponentImpl(componentDto, isPrivate, componentPopulator);
+
+ PortfolioDto portfolioDto = toPortfolioDto(componentDto, System2.INSTANCE.now());
+ portfolioPopulator.accept(portfolioDto);
+ dbClient.portfolioDao().insert(dbSession, portfolioDto);
+ db.commit();
+ return componentDto;
}
public final ComponentDto insertPrivatePortfolio() {
- return insertComponentImpl(ComponentTesting.newPortfolio().setPrivate(true), true, defaults());
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio().setPrivate(true), true, defaults(), defaults());
}
public final ComponentDto insertPrivatePortfolio(String uuid, Consumer<ComponentDto> dtoPopulator) {
- return insertComponentImpl(ComponentTesting.newPortfolio(uuid).setPrivate(true), true, dtoPopulator);
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio(uuid).setPrivate(true), true, dtoPopulator, defaults());
}
public final ComponentDto insertPrivatePortfolio(Consumer<ComponentDto> dtoPopulator) {
- return insertComponentImpl(ComponentTesting.newPortfolio().setPrivate(true), true, dtoPopulator);
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio().setPrivate(true), true, dtoPopulator, defaults());
+ }
+
+ public final ComponentDto insertPrivatePortfolio(Consumer<ComponentDto> dtoPopulator, Consumer<PortfolioDto> portfolioPopulator) {
+ return insertComponentAndPortfolio(ComponentTesting.newPortfolio().setPrivate(true), true, dtoPopulator, portfolioPopulator);
+ }
+
+ public void addPortfolioProject(ComponentDto portfolio, String... projectUuids) {
+ for (String uuid : projectUuids) {
+ dbClient.portfolioDao().addProject(dbSession, portfolio.uuid(), uuid);
+ }
+ db.commit();
+ }
+
+ public void addPortfolioProject(ComponentDto portfolio, ComponentDto... projects) {
+ for (ComponentDto project : projects) {
+ dbClient.portfolioDao().addProject(dbSession, portfolio.uuid(), project.uuid());
+ }
+ db.commit();
+ }
+
+ public void addPortfolioProject(PortfolioDto portfolioDto, ProjectDto... projects) {
+ for (ProjectDto project : projects) {
+ dbClient.portfolioDao().addProject(dbSession, portfolioDto.getUuid(), project.getUuid());
+ }
+ db.commit();
}
public final ComponentDto insertPublicApplication() {
.setName(componentDto.name());
}
+ public static PortfolioDto toPortfolioDto(ComponentDto componentDto, long createTime) {
+ return new PortfolioDto()
+ .setUuid(componentDto.uuid())
+ .setKey(componentDto.getDbKey())
+ .setRootUuid(componentDto.projectUuid())
+ .setSelectionMode(NONE.name())
+ .setCreatedAt(createTime)
+ .setUpdatedAt(createTime)
+ .setPrivate(componentDto.isPrivate())
+ .setDescription(componentDto.description())
+ .setName(componentDto.name());
+ }
+
private static <T> Consumer<T> defaults() {
return t -> {
};
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("kee").setIsNullable(false).setLimit(400).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())
.add(6011, "Create 'portfolios' table", CreatePortfoliosTable.class)
.add(6012, "Create 'portfolio_references' table", CreatePortfolioReferencesTable.class)
.add(6013, "Create 'portfolio_projects' table", CreatePortfolioProjectsTable.class)
+ .add(6014, "Migrate portfolios to new tables", MigratePortfoliosToNewTables.class)
;
}
}
--- /dev/null
+/*
+ * 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 com.google.common.collect.Sets;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+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.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.Upsert;
+import org.sonar.server.platform.db.migration.version.v91.MigratePortfoliosToNewTables.ViewXml.ViewDef;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Optional.ofNullable;
+import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
+import static org.apache.commons.io.IOUtils.toInputStream;
+import static org.apache.commons.lang.StringUtils.trim;
+
+public class MigratePortfoliosToNewTables extends DataChange {
+ private static final String SELECT_DEFAULT_VISIBILITY = "select text_value from properties where prop_key = 'projects.default.visibility'";
+ private static final String SELECT_UUID_VISIBILITY_BY_COMPONENT_KEY = "select c.uuid, c.private from components c where c.kee = ?";
+ private static final String SELECT_PORTFOLIO_UUID_AND_SELECTION_MODE_BY_KEY = "select uuid,selection_mode from portfolios where kee = ?";
+ private static final String SELECT_PROJECT_KEYS_BY_PORTFOLIO_UUID = "select p.kee from portfolio_projects pp "
+ + "join projects p on pp.project_uuid = p.uuid where pp.portfolio_uuid = ?";
+ private static final String SELECT_PROJECT_UUIDS_BY_KEYS = "select p.uuid,p.kee from projects p where p.kee in (PLACEHOLDER)";
+ private static final String VIEWS_DEF_KEY = "views.def";
+ private static final String PLACEHOLDER = "PLACEHOLDER";
+
+ private final UuidFactory uuidFactory;
+ private final System2 system;
+
+ private boolean defaultPrivateFlag;
+
+ public enum SelectionMode {
+ NONE, MANUAL, REGEXP, REST, TAGS
+ }
+
+ public MigratePortfoliosToNewTables(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;
+ }
+
+ this.defaultPrivateFlag = ofNullable(context.prepareSelect(SELECT_DEFAULT_VISIBILITY)
+ .get(row -> "private".equals(row.getString(1))))
+ .orElse(false);
+
+ try {
+ Map<String, ViewXml.ViewDef> portfolioXmlMap = ViewXml.parse(xml);
+ List<ViewXml.ViewDef> portfolios = new LinkedList<>(portfolioXmlMap.values());
+
+ Map<String, PortfolioDb> portfolioDbMap = new HashMap<>();
+ for (ViewXml.ViewDef portfolio : portfolios) {
+ PortfolioDb createdPortfolio = insertPortfolio(context, portfolio);
+ if (createdPortfolio.selectionMode == SelectionMode.MANUAL) {
+ insertPortfolioProjects(context, portfolio, createdPortfolio);
+ }
+ portfolioDbMap.put(createdPortfolio.kee, createdPortfolio);
+ }
+ // all portfolio has been created and new uuids assigned
+ // update portfolio hierarchy parent/root
+ insertReferences(context, portfolioXmlMap, portfolioDbMap);
+ updateHierarchy(context, portfolioXmlMap, portfolioDbMap);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to migrate views definitions property.", e);
+ }
+ }
+
+ private PortfolioDb insertPortfolio(Context context, ViewXml.ViewDef portfolioFromXml) throws SQLException {
+ long now = system.now();
+ PortfolioDb portfolioDb = context.prepareSelect(SELECT_PORTFOLIO_UUID_AND_SELECTION_MODE_BY_KEY)
+ .setString(1, portfolioFromXml.key)
+ .get(r -> new PortfolioDb(r.getString(1), portfolioFromXml.key, SelectionMode.valueOf(r.getString(2))));
+
+ Optional<ComponentDb> componentDbOpt = ofNullable(context.prepareSelect(SELECT_UUID_VISIBILITY_BY_COMPONENT_KEY)
+ .setString(1, portfolioFromXml.key)
+ .get(row -> new ComponentDb(row.getString(1), row.getBoolean(2))));
+
+ // no portfolio -> insert
+ if (portfolioDb == null) {
+ Upsert insertPortfolioQuery = context.prepareUpsert("insert into " +
+ "portfolios(uuid, kee, private, name, description, root_uuid, parent_uuid, selection_mode, selection_expression, updated_at, created_at) " +
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+
+ String portfolioUuid = componentDbOpt.map(c -> c.uuid).orElse(uuidFactory.create());
+ insertPortfolioQuery.setString(1, portfolioUuid)
+ .setString(2, portfolioFromXml.key)
+ .setBoolean(3, componentDbOpt.map(c -> c.visibility).orElse(this.defaultPrivateFlag))
+ .setString(4, portfolioFromXml.name)
+ .setString(5, portfolioFromXml.desc)
+ .setString(6, PLACEHOLDER)
+ .setString(7, PLACEHOLDER);
+ SelectionMode selectionMode = SelectionMode.NONE;
+ String selectionExpression = null;
+ if (portfolioFromXml.getProjects() != null && !portfolioFromXml.getProjects().isEmpty()) {
+ selectionMode = SelectionMode.MANUAL;
+ } else if (portfolioFromXml.regexp != null && !portfolioFromXml.regexp.isBlank()) {
+ selectionMode = SelectionMode.REGEXP;
+ selectionExpression = portfolioFromXml.regexp;
+ } else if (portfolioFromXml.def) {
+ selectionMode = SelectionMode.REST;
+ } else if (portfolioFromXml.tagsAssociation != null && !portfolioFromXml.tagsAssociation.isEmpty()) {
+ selectionMode = SelectionMode.TAGS;
+ selectionExpression = String.join(",", portfolioFromXml.tagsAssociation);
+ }
+
+ insertPortfolioQuery.setString(8, selectionMode.name())
+ .setString(9, selectionExpression)
+ // set dates
+ .setLong(10, now)
+ .setLong(11, now)
+ .execute()
+ .commit();
+ return new PortfolioDb(portfolioUuid, portfolioFromXml.key, selectionMode);
+ }
+ return portfolioDb;
+ }
+
+ private void insertPortfolioProjects(Context context, ViewDef portfolio, PortfolioDb createdPortfolio) throws SQLException {
+ long now = system.now();
+ // select all already added project uuids
+ Set<String> alreadyAddedPortfolioProjects = new HashSet<>(
+ context.prepareSelect(SELECT_PROJECT_KEYS_BY_PORTFOLIO_UUID)
+ .setString(1, createdPortfolio.uuid)
+ .list(r -> r.getString(1)));
+
+ Set<String> projectKeysFromXml = new HashSet<>(portfolio.getProjects());
+ Set<String> projectKeysToBeAdded = Sets.difference(projectKeysFromXml, alreadyAddedPortfolioProjects);
+
+ if (!projectKeysToBeAdded.isEmpty()) {
+ List<ProjectDb> projects = findPortfolioProjects(context, projectKeysToBeAdded);
+
+ var upsert = context.prepareUpsert("insert into " +
+ "portfolio_projects(uuid, portfolio_uuid, project_uuid, created_at) " +
+ "values (?, ?, ?, ?)");
+ for (ProjectDb projectDb : projects) {
+ upsert.setString(1, uuidFactory.create())
+ .setString(2, createdPortfolio.uuid)
+ .setString(3, projectDb.uuid)
+ .setLong(4, now)
+ .addBatch();
+ }
+ if (!projects.isEmpty()) {
+ upsert.execute()
+ .commit();
+ }
+ }
+ }
+
+ private static List<ProjectDb> findPortfolioProjects(Context context, Set<String> projectKeysToBeAdded) {
+ return DatabaseUtils.executeLargeInputs(projectKeysToBeAdded, keys -> {
+ try {
+ String selectQuery = SELECT_PROJECT_UUIDS_BY_KEYS.replace(PLACEHOLDER,
+ keys.stream().map(key -> "'" + key + "'").collect(
+ Collectors.joining(",")));
+ return context.prepareSelect(selectQuery)
+ .list(r -> new ProjectDb(r.getString(1), r.getString(2)));
+ } catch (SQLException e) {
+ throw new IllegalStateException("Could not execute 'in' query", e);
+ }
+ });
+ }
+
+ private void insertReferences(Context context, Map<String, ViewDef> portfolioXmlMap,
+ Map<String, PortfolioDb> portfolioDbMap) throws SQLException {
+ Upsert insertQuery = context.prepareUpsert("insert into portfolio_references(uuid, portfolio_uuid, reference_uuid, created_at) values (?, ?, ?, ?)");
+
+ long now = system.now();
+ boolean shouldExecuteQuery = false;
+ for (ViewDef portfolio : portfolioXmlMap.values()) {
+ var currentPortfolioUuid = portfolioDbMap.get(portfolio.key).uuid;
+ Set<String> referencesFromXml = new HashSet<>(portfolio.getReferences());
+ Set<String> referencesFromDb = new HashSet<>(context.prepareSelect("select pr.reference_uuid from portfolio_references pr where pr.portfolio_uuid = ?")
+ .setString(1, currentPortfolioUuid)
+ .list(row -> row.getString(1)));
+
+ for (String appOrPortfolio : referencesFromXml) {
+ // if portfolio and hasn't been added already
+ if (portfolioDbMap.containsKey(appOrPortfolio) && !referencesFromDb.contains(portfolioDbMap.get(appOrPortfolio).uuid)) {
+ insertQuery
+ .setString(1, uuidFactory.create())
+ .setString(2, currentPortfolioUuid)
+ .setString(3, portfolioDbMap.get(appOrPortfolio).uuid)
+ .setLong(4, now)
+ .addBatch();
+ shouldExecuteQuery = true;
+ } else {
+ // if application exist and haven't been added
+ String appUuid = context.prepareSelect("select p.uuid from projects p where p.kee = ?")
+ .setString(1, appOrPortfolio)
+ .get(row -> row.getString(1));
+ if (appUuid != null && !referencesFromDb.contains(appUuid)) {
+ insertQuery
+ .setString(1, uuidFactory.create())
+ .setString(2, currentPortfolioUuid)
+ .setString(3, appUuid)
+ .setLong(4, now)
+ .addBatch();
+ shouldExecuteQuery = true;
+ }
+ }
+ }
+ }
+ if (shouldExecuteQuery) {
+ insertQuery
+ .execute()
+ .commit();
+ }
+
+ }
+
+ private static void updateHierarchy(Context context, Map<String, ViewXml.ViewDef> defs, Map<String, PortfolioDb> portfolioDbMap) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("select uuid, kee from portfolios where root_uuid = ? or parent_uuid = ?")
+ .setString(1, PLACEHOLDER)
+ .setString(2, PLACEHOLDER);
+ massUpdate.update("update portfolios set root_uuid = ?, parent_uuid = ? where uuid = ?");
+ massUpdate.execute((row, update) -> {
+ String currentPortfolioUuid = row.getString(1);
+ String currentPortfolioKey = row.getString(2);
+
+ var currentPortfolio = defs.get(currentPortfolioKey);
+ String parentUuid = ofNullable(currentPortfolio.parent).map(parent -> portfolioDbMap.get(parent).uuid).orElse(null);
+ String rootUuid = ofNullable(currentPortfolio.root).map(root -> portfolioDbMap.get(root).uuid).orElse(currentPortfolioUuid);
+ update.setString(1, rootUuid)
+ .setString(2, parentUuid)
+ .setString(3, currentPortfolioUuid);
+ return true;
+ });
+ }
+
+ @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 class ComponentDb {
+ String uuid;
+ boolean visibility;
+
+ public ComponentDb(String uuid, boolean visibility) {
+ this.uuid = uuid;
+ this.visibility = visibility;
+ }
+ }
+
+ private static class PortfolioDb {
+ String uuid;
+ String kee;
+ SelectionMode selectionMode;
+
+ PortfolioDb(String uuid, String kee,
+ SelectionMode selectionMode) {
+ this.uuid = uuid;
+ this.kee = kee;
+ this.selectionMode = selectionMode;
+ }
+ }
+
+ private static class ProjectDb {
+ String uuid;
+ String kee;
+
+ ProjectDb(String uuid, String kee) {
+ this.uuid = uuid;
+ this.kee = kee;
+ }
+ }
+
+ 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.key, 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 = MigratePortfoliosToNewTables.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, "tagsAssociation")) {
+ parseTagsAssociation(viewDef, viewCursor);
+ }
+ }
+
+ 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);
+ }
+
+ 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;
+
+ Set<String> tagsAssociation = new TreeSet<>();
+
+ public List<String> getProjects() {
+ return p;
+ }
+
+ public List<String> getReferences() {
+ return vwRef;
+ }
+
+ 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 addProject(String project) {
+ this.p.add(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 setQualifier(@Nullable String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ public ViewDef addTagAssociation(String tag) {
+ this.tagsAssociation.add(tag);
+ 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;
+ }
+ }
+ }
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 13);
+ verifyMigrationCount(underTest, 14);
}
}
--- /dev/null
+/*
+ * 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 java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.resources.Qualifiers;
+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.DataChange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class MigratePortfoliosToNewTablesTest {
+ static final int TEXT_VALUE_MAX_LENGTH = 4000;
+ private static final long NOW = 10_000_000L;
+
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(MigratePortfoliosToNewTablesTest.class, "schema.sql");
+
+ private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+ private final System2 system2 = new TestSystem2().setNow(NOW);
+
+ private final DataChange underTest = new MigratePortfoliosToNewTables(db.database(), uuidFactory, system2);
+
+ private final String SIMPLE_XML_ONE_PORTFOLIO = "<views>" +
+ "<vw key=\"port1\" def=\"false\">\n" +
+ " <name><![CDATA[name]]></name>\n" +
+ " <desc><![CDATA[description]]></desc>\n" +
+ " <qualifier><![CDATA[VW]]></qualifier>\n" +
+ " </vw>\n" +
+ "<vw key=\"port2\" def=\"false\">\n" +
+ " <name><![CDATA[name2]]></name>\n" +
+ " <desc><![CDATA[description2]]></desc>\n" +
+ " <qualifier><![CDATA[VW]]></qualifier>\n" +
+ " </vw>\n" +
+ "</views>";
+
+ private final String SIMPLE_XML_ONLY_PORTFOLIOS = "<views>" +
+ "<vw key=\"port1\" def=\"true\">\n" +
+ " <name><![CDATA[port1]]></name>\n" +
+ " <desc><![CDATA[]]></desc>\n" +
+ " <qualifier><![CDATA[VW]]></qualifier>\n" +
+ " </vw>\n" +
+ "<vw key=\"port3\" def=\"false\">\n" +
+ " <name><![CDATA[port3]]></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[port1]]></vw-ref>\n" +
+ " <vw-ref><![CDATA[port3]]></vw-ref>\n" +
+ " </vw>\n" +
+ "</views>";
+
+ private final String SIMPLE_XML_PORTFOLIOS_AND_APP = "<views>" +
+ "<vw key=\"port1\" def=\"true\">\n" +
+ " <name><![CDATA[port1]]></name>\n" +
+ " <desc><![CDATA[]]></desc>\n" +
+ " <qualifier><![CDATA[VW]]></qualifier>\n" +
+ " </vw>\n" +
+ "<vw key=\"port3\" def=\"false\">\n" +
+ " <name><![CDATA[port3]]></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[port1]]></vw-ref>\n" +
+ " <vw-ref><![CDATA[port3]]></vw-ref>\n" +
+ " </vw>\n" +
+ "<vw key=\"port4\" def=\"false\">\n" +
+ " <name><![CDATA[port2]]></name>\n" +
+ " <desc><![CDATA[]]></desc>\n" +
+ " <qualifier><![CDATA[VW]]></qualifier>\n" +
+ " <vw-ref><![CDATA[port2]]></vw-ref>\n" +
+ " <vw-ref><![CDATA[app1-key]]></vw-ref>\n" +
+ " <vw-ref><![CDATA[app2-key]]></vw-ref>\n" +
+ " <vw-ref><![CDATA[app3-key-not-existing]]></vw-ref>\n" +
+ " </vw>\n" +
+ "</views>";
+
+ private final String SIMPLE_XML_PORTFOLIOS_HIERARCHY = "<views>" +
+ " <vw key=\"port1\" def=\"false\">\n"
+ + " <name><![CDATA[port1]]></name>\n"
+ + " <desc><![CDATA[port1]]></desc>\n"
+ + " <qualifier><![CDATA[VW]]></qualifier>\n"
+ + " </vw>\n"
+ + " <vw key=\"port2\" def=\"false\" root=\"port1\" parent=\"port1\">\n"
+ + " <name><![CDATA[port2]]></name>\n"
+ + " <desc><![CDATA[port2]]></desc>\n"
+ + " </vw>\n"
+ + " <vw key=\"port3\" def=\"false\" root=\"port1\" parent=\"port1\">\n"
+ + " <name><![CDATA[port3]]></name>\n"
+ + " <desc><![CDATA[port3]]></desc>\n"
+ + " </vw>\n"
+ + " <vw key=\"port4\" def=\"false\" root=\"port1\" parent=\"port1\">\n"
+ + " <name><![CDATA[port4]]></name>\n"
+ + " <desc><![CDATA[port4]]></desc>\n"
+ + " </vw>\n"
+ + " <vw key=\"port5\" def=\"false\" root=\"port1\" parent=\"port2\">\n"
+ + " <name><![CDATA[port5]]></name>\n"
+ + " </vw>\n"
+ + " <vw key=\"port6\" def=\"false\" root=\"port1\" parent=\"port3\">\n"
+ + " <name><![CDATA[port6]]></name>\n"
+ + " <desc><![CDATA[port6]]></desc>\n"
+ + " </vw>\n"
+ + " <vw key=\"port7\" def=\"false\" root=\"port1\" parent=\"port3\">\n"
+ + " <name><![CDATA[port7]]></name>\n"
+ + " <desc><![CDATA[port7]]></desc>\n"
+ + " </vw>" +
+ "</views>";
+
+ private final String SIMPLE_XML_PORTFOLIOS_DIFFERENT_SELECTIONS = "<views>"
+ + " <vw key=\"port-regexp\" def=\"false\">\n"
+ + " <name><![CDATA[port-regexp]]></name>\n"
+ + " <desc><![CDATA[port-regexp]]></desc>\n"
+ + " <regexp><![CDATA[.*port.*]]></regexp>\n"
+ + " <qualifier><![CDATA[VW]]></qualifier>\n"
+ + " </vw>"
+ + " <vw key=\"port-tags\" def=\"false\">\n"
+ + " <name><![CDATA[port-tags]]></name>\n"
+ + " <desc><![CDATA[port-tags]]></desc>\n"
+ + " <qualifier><![CDATA[VW]]></qualifier>\n"
+ + " <tagsAssociation>\n"
+ + " <tag>tag1</tag>\n"
+ + " <tag>tag2</tag>\n"
+ + " <tag>tag3</tag>\n"
+ + " </tagsAssociation>\n"
+ + " </vw>"
+ + " <vw key=\"port-projects\" def=\"false\">\n"
+ + " <name><![CDATA[port-projects]]></name>\n"
+ + " <desc><![CDATA[port-projects]]></desc>\n"
+ + " <qualifier><![CDATA[VW]]></qualifier>\n"
+ + " <p>p1-key</p>\n"
+ + " <p>p2-key</p>\n"
+ + " <p>p3-key</p>\n"
+ + " <p>p4-key-not-existing</p>\n"
+ + " </vw>"
+ + "</views>";
+
+ private final String XML_PORTFOLIOS_LEGACY_SELECTIONS = "<views>"
+ + " <vw key=\"port-language\" def=\"false\">\n"
+ + " <name><![CDATA[port-language]]></name>\n"
+ + " <desc><![CDATA[port-language]]></desc>\n"
+ + " <language><![CDATA[javascript]]></language>\n"
+ + " <qualifier><![CDATA[VW]]></qualifier>\n"
+ + " </vw>"
+ + " <vw key=\"port-tag-value\" def=\"false\">\n"
+ + " <name><![CDATA[port-tag-value]]></name>\n"
+ + " <desc><![CDATA[port-tag-value]]></desc>\n"
+ + " <qualifier><![CDATA[VW]]></qualifier>\n"
+ + " <tag_key>tag-key</tag_key>\n"
+ + " <tag_value>tag-value</tag_value>\n"
+ + " </vw>"
+ + "</views>";
+
+ @Test
+ public void does_not_fail_when_nothing_to_migrate() throws SQLException {
+ assertThatCode(underTest::execute)
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void migrate_single_portfolio_with_default_visibility() throws SQLException {
+ insertViewsDefInternalProperty(SIMPLE_XML_ONE_PORTFOLIO);
+ insertDefaultVisibilityProperty(true);
+ insertComponent("uuid", "port2", false);
+
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ assertThat(db.select("select kee, private, name, description, "
+ + "selection_mode, selection_expression, "
+ + "updated_at, created_at from portfolios"))
+ .extracting(
+ row -> row.get("KEE"),
+ row -> row.get("PRIVATE"),
+ row -> row.get("NAME"),
+ row -> row.get("DESCRIPTION"),
+ row -> row.get("SELECTION_MODE"),
+ row -> row.get("SELECTION_EXPRESSION"),
+ row -> row.get("UPDATED_AT"),
+ row -> row.get("CREATED_AT"))
+ .containsExactlyInAnyOrder(
+ tuple("port1", true, "name", "description", "NONE", null, NOW, NOW),
+ tuple("port2", false, "name2", "description2", "NONE", null, NOW, NOW));
+ }
+
+ @Test
+ public void migrate_portfolios_should_assign_component_uuid() throws SQLException {
+ insertViewsDefInternalProperty(SIMPLE_XML_ONE_PORTFOLIO);
+ insertComponent("uuid1", "port1", true);
+ insertComponent("uuid2", "port2", false);
+
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ assertThat(db.select("select uuid, kee, private, name, description, "
+ + "selection_mode, selection_expression, "
+ + "updated_at, created_at from portfolios"))
+ .extracting(
+ row -> row.get("UUID"),
+ row -> row.get("KEE"),
+ row -> row.get("PRIVATE"),
+ row -> row.get("NAME"),
+ row -> row.get("DESCRIPTION"),
+ row -> row.get("SELECTION_MODE"),
+ row -> row.get("SELECTION_EXPRESSION"),
+ row -> row.get("UPDATED_AT"),
+ row -> row.get("CREATED_AT"))
+ .containsExactlyInAnyOrder(
+ tuple("uuid1", "port1", true, "name", "description", "NONE", null, NOW, NOW),
+ tuple("uuid2", "port2", false, "name2", "description2", "NONE", null, NOW, NOW));
+ }
+
+ @Test
+ public void migrate_simple_xml_only_portfolios_references() throws SQLException {
+ insertViewsDefInternalProperty(SIMPLE_XML_ONLY_PORTFOLIOS);
+
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ assertThat(db.select("select kee from portfolios"))
+ .extracting(row -> row.get("KEE"))
+ .containsExactlyInAnyOrder("port1", "port2", "port3");
+
+ var portfolioKeyByUuid = db.select("select uuid, kee from portfolios").stream()
+ .collect(Collectors.toMap(row -> row.get("KEE"), row -> row.get("UUID").toString()));
+
+ String portfolio1Uuid = portfolioKeyByUuid.get("port1");
+ String portfolio2Uuid = portfolioKeyByUuid.get("port2");
+ String portfolio3Uuid = portfolioKeyByUuid.get("port3");
+
+ assertThat(db.select("select portfolio_uuid, reference_uuid from portfolio_references"))
+ .extracting(row -> row.get("PORTFOLIO_UUID"), row -> row.get("REFERENCE_UUID"))
+ .containsExactlyInAnyOrder(tuple(portfolio2Uuid, portfolio1Uuid), tuple(portfolio2Uuid, portfolio3Uuid));
+ }
+
+ @Test
+ public void migrate_simple_xml_portfolios_and_apps_references() throws SQLException {
+ insertViewsDefInternalProperty(SIMPLE_XML_PORTFOLIOS_AND_APP);
+
+ insertProject("app1-uuid", "app1-key", Qualifiers.APP);
+ insertProject("app2-uuid", "app2-key", Qualifiers.APP);
+ insertProject("proj1", "proj1-key", Qualifiers.PROJECT);
+
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ assertThat(db.select("select kee from portfolios"))
+ .extracting(row -> row.get("KEE"))
+ .containsExactlyInAnyOrder("port1", "port2", "port3", "port4");
+
+ var portfolioKeyByUuid = db.select("select uuid, kee from portfolios").stream()
+ .collect(Collectors.toMap(row -> row.get("KEE"), row -> row.get("UUID").toString()));
+
+ String portfolio1Uuid = portfolioKeyByUuid.get("port1");
+ String portfolio2Uuid = portfolioKeyByUuid.get("port2");
+ String portfolio3Uuid = portfolioKeyByUuid.get("port3");
+ String portfolio4Uuid = portfolioKeyByUuid.get("port4");
+
+ assertThat(db.select("select portfolio_uuid, reference_uuid from portfolio_references"))
+ .extracting(row -> row.get("PORTFOLIO_UUID"), row -> row.get("REFERENCE_UUID"))
+ .containsExactlyInAnyOrder(
+ tuple(portfolio2Uuid, portfolio1Uuid),
+ tuple(portfolio2Uuid, portfolio3Uuid),
+ tuple(portfolio4Uuid, portfolio2Uuid),
+ tuple(portfolio4Uuid, "app1-uuid"),
+ tuple(portfolio4Uuid, "app2-uuid"))
+ .doesNotContain(tuple(portfolio4Uuid, "app3-key-not-existing"));
+ }
+
+ @Test
+ public void migrate_xml_portfolios_hierarchy() throws SQLException {
+ insertViewsDefInternalProperty(SIMPLE_XML_PORTFOLIOS_HIERARCHY);
+
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ assertThat(db.select("select kee from portfolios"))
+ .extracting(row -> row.get("KEE"))
+ .containsExactlyInAnyOrder("port1", "port2", "port3", "port4", "port5", "port6", "port7");
+
+ var portfolioKeyByUuid = db.select("select uuid, kee from portfolios").stream()
+ .collect(Collectors.toMap(row -> row.get("KEE"), row -> row.get("UUID").toString()));
+
+ String portfolio1Uuid = portfolioKeyByUuid.get("port1");
+ String portfolio2Uuid = portfolioKeyByUuid.get("port2");
+ String portfolio3Uuid = portfolioKeyByUuid.get("port3");
+ String portfolio4Uuid = portfolioKeyByUuid.get("port4");
+ String portfolio5Uuid = portfolioKeyByUuid.get("port5");
+ String portfolio6Uuid = portfolioKeyByUuid.get("port6");
+ String portfolio7Uuid = portfolioKeyByUuid.get("port7");
+
+ assertThat(db.select("select uuid, parent_uuid, root_uuid from portfolios"))
+ .extracting(row -> row.get("UUID"), row -> row.get("PARENT_UUID"), row -> row.get("ROOT_UUID"))
+ .containsExactlyInAnyOrder(
+ tuple(portfolio1Uuid, null, portfolio1Uuid),
+ tuple(portfolio2Uuid, portfolio1Uuid, portfolio1Uuid),
+ tuple(portfolio3Uuid, portfolio1Uuid, portfolio1Uuid),
+ tuple(portfolio4Uuid, portfolio1Uuid, portfolio1Uuid),
+ tuple(portfolio5Uuid, portfolio2Uuid, portfolio1Uuid),
+ tuple(portfolio6Uuid, portfolio3Uuid, portfolio1Uuid),
+ tuple(portfolio7Uuid, portfolio3Uuid, portfolio1Uuid));
+ }
+
+ @Test
+ public void migrate_xml_portfolios_different_selections() throws SQLException {
+ insertViewsDefInternalProperty(SIMPLE_XML_PORTFOLIOS_DIFFERENT_SELECTIONS);
+
+ insertProject("p1-uuid", "p1-key", Qualifiers.PROJECT);
+ insertProject("p2-uuid", "p2-key", Qualifiers.PROJECT);
+ insertProject("p3-uuid", "p3-key", Qualifiers.PROJECT);
+
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ assertThat(db.select("select kee, selection_mode, selection_expression from portfolios"))
+ .extracting(row -> row.get("KEE"), row -> row.get("SELECTION_MODE"), row -> row.get("SELECTION_EXPRESSION"))
+ .containsExactlyInAnyOrder(
+ tuple("port-regexp", "REGEXP", ".*port.*"),
+ tuple("port-projects", "MANUAL", null),
+ tuple("port-tags", "TAGS", "tag1,tag2,tag3"));
+
+ // verify projects
+ assertThat(db.select("select p.kee, pp.project_uuid from "
+ + "portfolio_projects pp join portfolios p on pp.portfolio_uuid = p.uuid where p.kee = 'port-projects'"))
+ .extracting(row -> row.get("KEE"), row -> row.get("PROJECT_UUID"))
+ .containsExactlyInAnyOrder(
+ tuple("port-projects", "p1-uuid"),
+ tuple("port-projects", "p2-uuid"),
+ tuple("port-projects", "p3-uuid"))
+ .doesNotContain(tuple("port-projects", "p4-uuid-not-existing"));
+ }
+
+ @Test
+ public void migrate_xml_portfolios_legacy_selections() throws SQLException {
+ insertViewsDefInternalProperty(XML_PORTFOLIOS_LEGACY_SELECTIONS);
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+
+ assertThat(db.select("select kee, selection_mode, selection_expression from portfolios"))
+ .extracting(row -> row.get("KEE"), row -> row.get("SELECTION_MODE"), row -> row.get("SELECTION_EXPRESSION"))
+ .containsExactlyInAnyOrder(
+ tuple("port-language", "NONE", null),
+ tuple("port-tag-value", "NONE", null));
+ }
+
+ 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 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 insertComponent(String uuid, String kee, boolean isPrivate) {
+ db.executeInsert("components",
+ "uuid", uuid,
+ "kee", kee,
+ "enabled", false,
+ "private", isPrivate,
+ "root_uuid", uuid,
+ "uuid_path", uuid,
+ "project_uuid", uuid);
+ }
+
+ private void insertDefaultVisibilityProperty(boolean isPrivate) {
+ db.executeInsert("properties",
+ "uuid", uuidFactory.create(),
+ "prop_key", "projects.default.visibility",
+ "IS_EMPTY", false,
+ "text_value", isPrivate ? "private" : "public",
+ "created_at", system2.now());
+ }
+
+}
--- /dev/null
+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 "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 "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(400) 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 "PROPERTIES"(
+ "UUID" VARCHAR(40) NOT NULL,
+ "PROP_KEY" VARCHAR(512) NOT NULL,
+ "IS_EMPTY" BOOLEAN NOT NULL,
+ "TEXT_VALUE" VARCHAR(4000),
+ "CLOB_VALUE" CLOB,
+ "CREATED_AT" BIGINT NOT NULL,
+ "COMPONENT_UUID" VARCHAR(40),
+ "USER_UUID" VARCHAR(255)
+);
+ALTER TABLE "PROPERTIES" ADD CONSTRAINT "PK_PROPERTIES" PRIMARY KEY("UUID");
+CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES"("PROP_KEY");
+
+CREATE TABLE "COMPONENTS"(
+ "UUID" VARCHAR(50) NOT NULL,
+ "KEE" VARCHAR(400),
+ "DEPRECATED_KEE" VARCHAR(400),
+ "NAME" VARCHAR(2000),
+ "LONG_NAME" VARCHAR(2000),
+ "DESCRIPTION" VARCHAR(2000),
+ "ENABLED" BOOLEAN DEFAULT TRUE NOT NULL,
+ "SCOPE" VARCHAR(3),
+ "QUALIFIER" VARCHAR(10),
+ "PRIVATE" BOOLEAN NOT NULL,
+ "ROOT_UUID" VARCHAR(50) NOT NULL,
+ "LANGUAGE" VARCHAR(20),
+ "COPY_COMPONENT_UUID" VARCHAR(50),
+ "PATH" VARCHAR(2000),
+ "UUID_PATH" VARCHAR(1500) NOT NULL,
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "MODULE_UUID" VARCHAR(50),
+ "MODULE_UUID_PATH" VARCHAR(1500),
+ "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
+ "B_CHANGED" BOOLEAN,
+ "B_NAME" VARCHAR(500),
+ "B_LONG_NAME" VARCHAR(500),
+ "B_DESCRIPTION" VARCHAR(2000),
+ "B_ENABLED" BOOLEAN,
+ "B_QUALIFIER" VARCHAR(10),
+ "B_LANGUAGE" VARCHAR(20),
+ "B_COPY_COMPONENT_UUID" VARCHAR(50),
+ "B_PATH" VARCHAR(2000),
+ "B_UUID_PATH" VARCHAR(1500),
+ "B_MODULE_UUID" VARCHAR(50),
+ "B_MODULE_UUID_PATH" VARCHAR(1500),
+ "CREATED_AT" TIMESTAMP
+);
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "COMPONENTS"("KEE");
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "COMPONENTS"("MODULE_UUID");
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "COMPONENTS"("PROJECT_UUID");
+CREATE INDEX "PROJECTS_QUALIFIER" ON "COMPONENTS"("QUALIFIER");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "COMPONENTS"("ROOT_UUID");
+CREATE INDEX "PROJECTS_UUID" ON "COMPONENTS"("UUID");
+CREATE INDEX "IDX_MAIN_BRANCH_PRJ_UUID" ON "COMPONENTS"("MAIN_BRANCH_PROJECT_UUID");
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.portfolio.PortfolioDto;
+import org.sonar.db.portfolio.PortfolioDto.SelectionMode;
import org.sonar.db.project.ProjectDto;
import org.sonar.server.es.ProjectIndexer.Cause;
import org.sonar.server.es.ProjectIndexers;
* Don't forget to call commitAndIndex(...) when ready to commit.
*/
public ComponentDto createWithoutCommit(DbSession dbSession, NewComponent newComponent,
- @Nullable String userUuid, @Nullable String userLogin, Consumer<ComponentDto> componentModifier) {
+ @Nullable String userUuid, @Nullable String userLogin, Consumer<ComponentDto> componentModifier) {
return createWithoutCommit(dbSession, newComponent, userUuid, userLogin, null, componentModifier);
}
ProjectDto projectDto = toProjectDto(component, now);
dbClient.projectDao().insert(session, projectDto);
}
+
+ if (isRootView(component)) {
+ PortfolioDto portfolioDto = toPortfolioDto(component, now);
+ dbClient.portfolioDao().insert(session, portfolioDto);
+ }
+
return component;
}
.setCreatedAt(now);
}
+ private static PortfolioDto toPortfolioDto(ComponentDto component, long now) {
+ return new PortfolioDto()
+ .setUuid(component.uuid())
+ .setRootUuid(component.projectUuid())
+ .setKey(component.getKey())
+ .setName(component.name())
+ .setPrivate(component.isPrivate())
+ .setDescription(component.description())
+ .setSelectionMode(SelectionMode.NONE.name())
+ .setUpdatedAt(now)
+ .setCreatedAt(now);
+ }
+
private static boolean isRootProject(ComponentDto componentDto) {
return Scopes.PROJECT.equals(componentDto.scope())
&& MAIN_BRANCH_QUALIFIERS.contains(componentDto.qualifier());
}
+ private static boolean isRootView(ComponentDto componentDto) {
+ return Scopes.PROJECT.equals(componentDto.scope())
+ && Qualifiers.VIEW.contains(componentDto.qualifier());
+ }
+
private void createMainBranch(DbSession session, String componentUuid, @Nullable String mainBranch) {
BranchDto branch = new BranchDto()
.setBranchType(BranchType.BRANCH)