/** * This file is part of lavagna. * * lavagna is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * lavagna 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with lavagna. If not, see <http://www.gnu.org/licenses/>. */ package io.lavagna.service; import com.google.gson.reflect.TypeToken; import io.lavagna.common.Read; import io.lavagna.model.*; import io.lavagna.model.CardLabel.LabelDomain; import io.lavagna.model.CardLabel.LabelType; import io.lavagna.query.StatisticsQuery; import io.lavagna.service.PermissionService.RoleAndPermissionsWithUsers; import org.apache.commons.lang3.builder.CompareToBuilder; import org.springframework.stereotype.Component; import java.nio.file.Path; import java.util.*; import java.util.Map.Entry; import static io.lavagna.common.Read.readMatchingObjects; import static io.lavagna.common.Read.readObject; import static java.util.Collections.singletonList; @Component class LavagnaImporter { private final ConfigurationRepository configurationRepository; private final UserRepository userRepository; private final PermissionService permissionService; private final ProjectService projectService; private final BoardRepository boardRepository; private final BoardColumnRepository boardColumnRepository; private final CardLabelRepository cardLabelRepository; private final CardRepository cardRepository; private final CardDataRepository cardDataRepository; private final StatisticsQuery statisticsQuery; private final ImportEvent importEvent; public LavagnaImporter(ConfigurationRepository configurationRepository, UserRepository userRepository, PermissionService permissionService, ProjectService projectService, BoardRepository boardRepository, BoardColumnRepository boardColumnRepository, CardLabelRepository cardLabelRepository, CardDataRepository cardDataRepository, CardRepository cardRepository, ImportEvent importEvent, StatisticsQuery statisticsQuery) { this.configurationRepository = configurationRepository; this.userRepository = userRepository; this.permissionService = permissionService; this.projectService = projectService; this.boardRepository = boardRepository; this.boardColumnRepository = boardColumnRepository; this.cardLabelRepository = cardLabelRepository; this.importEvent = importEvent; this.cardRepository = cardRepository; this.cardDataRepository = cardDataRepository; this.statisticsQuery = statisticsQuery; } public void importData(boolean overrideConfiguration, Path tempFile) { importConfiguration(overrideConfiguration, tempFile); importMissingUsers(tempFile); importBasePermissions(tempFile); ImportContext context = new ImportContext(); importProjects(tempFile, context); importBoards(tempFile, context); // int eventPages = readObject("events-page-count.json", tempFile, new TypeToken<Integer>() { }); for (int i = 0; i < eventPages; i++) { processEvents(readObject("events-" + i + ".json", tempFile, new TypeToken<List<EventFull>>() { }), context, tempFile); } orderAll(tempFile, context); } private void orderAll(Path tempFile, ImportContext context) { for (String shortName : context.getImportedBoard()) { orderCards(readObject("boards/" + shortName + "/cards.json", tempFile, new TypeToken<List<CardFull>>() { })); } for (CardDataIdAndOrder idOrder : readObject("card-data-types-order.json", tempFile, new TypeToken<List<CardDataIdAndOrder>>() { })) { int oldId = idOrder.getFirst(); int order = idOrder.getSecond(); if (context.getActionItemId().containsKey(oldId)) { cardDataRepository.updateOrderById(context.getActionItemId().get(oldId), order); } else if (context.getActionListId().containsKey(oldId)) { cardDataRepository.updateOrderById(context.getActionListId().get(oldId), order); } } } private void orderCards(List<CardFull> cards) { for (CardFull cf : cards) { int cardId = cardRepository.findCardIdByBoardNameAndSeq(cf.getBoardShortName(), cf.getSequence()); cardRepository.updateCardOrder(cardId, cf.getOrder()); } } private void importConfiguration(boolean overrideConfiguration, Path tempFile) { if (overrideConfiguration) { configurationRepository.updateOrCreate(readObject("config.json", tempFile, new TypeToken<List<ConfigurationKeyValue>>() { })); } } private void importBoards(Path tempFile, ImportContext context) { for (Pair<String, BoardInfo> p : readMatchingObjects("boards/[^/]+\\.json", tempFile, new TypeToken<Pair<String, BoardInfo>>() { })) { String projectShortName = p.getFirst(); BoardInfo boardInfo = p.getSecond(); if (context.getImportedProject().contains(projectShortName)) { Project project = projectService.findByShortName(projectShortName); if (!boardRepository.existsWithShortName(boardInfo.getShortName())) { importMissingBoard(project, boardInfo, tempFile, context); context.getImportedBoard().add(boardInfo.getShortName()); } } } } private void importProjects(Path tempFile, ImportContext context) { for (Project project : readMatchingObjects("projects/[^/]+\\.json", tempFile, new TypeToken<Project>() { })) { if (importProject(project, tempFile)) { context.getImportedProject().add(project.getShortName()); } } } private void processEvents(List<EventFull> events, ImportContext idMapping, Path tempFile) { for (EventFull e : events) { processEvent(idMapping, e, tempFile); } } private void processEvent(ImportContext context, EventFull e, Path tempFile) { if (!context.getImportedBoard().contains(e.getBoardShortName())) { return; } importEvent.processEvent(e, context, tempFile); } private void importBasePermissions(Path tempFile) { // add missing base permissions Map<String, RoleAndPermissionsWithUsers> permissions = readObject("permissions.json", tempFile, new TypeToken<Map<String, RoleAndPermissionsWithUsers>>() { }); permissionService.createMissingRolesWithPermissions(from(permissions)); // add users to roles for (RoleAndPermissionsWithUsers v : permissions.values()) { List<UserIdentifier> assignedUsers = v.getAssignedUsers(); Role role = new Role(v.getName()); permissionService.assignRoleToUsers(role, removeUserWithRole(role, assignedUsers)); } // } private Set<Integer> removeUserWithRole(Role role, List<UserIdentifier> users) { Set<Integer> userIds = new HashSet<>(); for (UserIdentifier ui : users) { User u = userRepository.findUserByName(ui.getProvider(), ui.getUsername()); if (!permissionService.findBaseRoleAndPermissionByUserId(u.getId()).containsKey(role.getName())) { userIds.add(u.getId()); } } return userIds; } private Map<RoleAndPermission, Set<Permission>> from(Map<String, RoleAndPermissionsWithUsers> from) { Map<RoleAndPermission, Set<Permission>> res = new HashMap<>(); for (Entry<String, RoleAndPermissionsWithUsers> kv : from.entrySet()) { RoleAndPermissionsWithUsers rpu = kv.getValue(); RoleAndPermission rap = new RoleAndPermission(rpu.getName(), rpu.isRemovable(), rpu.isHidden(), rpu.isReadOnly(), null); if (!res.containsKey(rap)) { res.put(rap, EnumSet.noneOf(Permission.class)); } for (RoleAndPermission rp : kv.getValue().getRoleAndPermissions()) { //we can have null permission enum value if the enum is no more present if(rp.getPermission() != null) { res.get(rap).add(rp.getPermission()); } } } return res; } /** * Import only the users that are not present in the system. */ private void importMissingUsers(Path tempFile) { List<User> users = readObject("users.json", tempFile, new TypeToken<List<User>>() { }); SortedSet<User> usersToImport = new TreeSet<>(new Comparator<User>() { @Override public int compare(User o1, User o2) { return new CompareToBuilder().append(o1.getProvider(), o2.getProvider()) .append(o1.getUsername(), o2.getUsername()).toComparison(); } }); usersToImport.addAll(users); usersToImport.removeAll(userRepository.findAll()); userRepository.createUsers(usersToImport); if(Read.hasMatchingObject("users-password-hash.json", tempFile)) { Map<String, String> usersWithPassword = Read.readObject("users-password-hash.json", tempFile, new TypeToken<Map<String, String>>() { }); for(User u : usersToImport) { if("password".equals(u.getProvider()) && usersWithPassword.containsKey(u.getUsername())) { userRepository.setUserPassword(userRepository.findUserByName("password", u.getUsername()).getId(), usersWithPassword.get(u.getUsername())); } } } } private boolean importProject(Project project, Path tempFile) { boolean created = projectService.createMissing(singletonList(project)).getRight().isEmpty(); if (created) { Project createdProject = projectService.findByShortName(project.getShortName()); String projectNameDir = "projects/" + project.getShortName(); importColumnDefinitionColor(tempFile, createdProject, projectNameDir); importLabels(tempFile, createdProject, projectNameDir); importProjectPermissions(tempFile, createdProject, projectNameDir); return true; } else { return false; } } private void importProjectPermissions(Path tempFile, Project createdProject, String projectNameDir) { Map<String, RoleAndPermissionsWithUsers> permissions = readObject(projectNameDir + "/permissions.json", tempFile, new TypeToken<Map<String, RoleAndPermissionsWithUsers>>() { }); permissionService.createMissingRolesWithPermissionForProject(createdProject.getId(), from(permissions)); // add users to roles for (RoleAndPermissionsWithUsers v : permissions.values()) { List<UserIdentifier> assignedUsers = v.getAssignedUsers(); Role role = new Role(v.getName()); permissionService.assignRoleToUsersInProjectId(role, toUserIds(assignedUsers), createdProject.getId()); } } private Set<Integer> toUserIds(List<UserIdentifier> users) { Set<Integer> userIds = new HashSet<>(); for (UserIdentifier ui : users) { userIds.add(userRepository.findUserByName(ui.getProvider(), ui.getUsername()).getId()); } return userIds; } private void importLabels(Path tempFile, Project createdProject, String projectNameDir) { List<Pair<CardLabel, List<LabelListValueWithMetadata>>> labels = readObject(projectNameDir + "/labels.json", tempFile, new TypeToken<List<Pair<CardLabel, List<LabelListValueWithMetadata>>>>() { }); for (Pair<CardLabel, List<LabelListValueWithMetadata>> pLabel : labels) { CardLabel label = pLabel.getFirst(); if (label.getDomain() == LabelDomain.USER) { cardLabelRepository.addLabel(createdProject.getId(), label.getUnique(), label.getType(), label.getDomain(), label.getName(), label.getColor()); } // import label list value if (label.getType() == LabelType.LIST && !pLabel.getSecond().isEmpty()) { CardLabel importedCl = cardLabelRepository.findLabelByName(createdProject.getId(), label.getName(), label.getDomain()); for (LabelListValueWithMetadata llv : pLabel.getSecond()) { LabelListValue addedLabeListValue = cardLabelRepository.addLabelListValue(importedCl.getId(), llv.getValue()); if(llv.getMetadata() != null) { for(Entry<String, String> metadataKV : llv.getMetadata().entrySet()) { cardLabelRepository.createLabelListMetadata(addedLabeListValue.getId(), metadataKV.getKey(), metadataKV.getValue()); } } } } } } private void importColumnDefinitionColor(Path tempFile, Project createdProject, String projectNameDir) { Map<ColumnDefinition, BoardColumnDefinition> importedColDef = readObject(projectNameDir + "/column-definitions.json", tempFile, new TypeToken<Map<ColumnDefinition, BoardColumnDefinition>>() { }); Map<ColumnDefinition, BoardColumnDefinition> currentColDef = projectService .findMappedColumnDefinitionsByProjectId(createdProject.getId()); for (Entry<ColumnDefinition, BoardColumnDefinition> kv : currentColDef.entrySet()) { projectService.updateColumnDefinition(createdProject.getId(), kv.getValue().getId(), importedColDef.get(kv.getKey()).getColor()); } } private void importMissingBoard(Project project, BoardInfo boardInfo, Path tempFile, ImportContext idMapping) { Board createdBoard = boardRepository.createEmptyBoard(boardInfo.getName(), boardInfo.getShortName(), boardInfo.getDescription(), project.getId()); boardRepository.updateBoard(createdBoard.getId(), createdBoard.getName(), createdBoard.getDescription(), boardInfo.getArchived()); List<BoardColumn> boardColumns = readObject("boards/" + boardInfo.getShortName() + "/columns.json", tempFile, new TypeToken<List<BoardColumn>>() { }); int boardId = boardRepository.findBoardIdByShortName(boardInfo.getShortName()); Map<ColumnDefinition, BoardColumnDefinition> colsDef = projectService .findMappedColumnDefinitionsByProjectId(project.getId()); for (BoardColumn bc : boardColumns) { BoardColumn added = boardColumnRepository.addColumnToBoard(bc.getName(), colsDef.get(bc.getStatus()) .getId(), bc.getLocation(), boardId); boardColumnRepository.updateOrder(added.getId(), bc.getOrder()); // save the old column to new column id mapping. idMapping.getColumns().put(bc.getId(), added.getId()); } List<StatisticForExport> stats = readObject("boards/" + boardInfo.getShortName() + "/statistics.json", tempFile, new TypeToken<List<StatisticForExport>>() { }); // TODO: not optimal in term of performance, use a bulk insert for (StatisticForExport stat : stats) { statisticsQuery.addFromImport(stat.getDate(), boardId, colsDef.get(stat.getColumnDefinition()).getId(), stat.getLocation().toString(), stat.getCount()); } } }