io.lavagna.service.LavagnaImporter.java Source code

Java tutorial

Introduction

Here is the source code for io.lavagna.service.LavagnaImporter.java

Source

/**
 * 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 static io.lavagna.common.Read.readMatchingObjects;
import static io.lavagna.common.Read.readObject;
import static java.util.Collections.singletonList;
import io.lavagna.model.Board;
import io.lavagna.model.BoardColumn;
import io.lavagna.model.BoardColumnDefinition;
import io.lavagna.model.BoardInfo;
import io.lavagna.model.CardDataIdAndOrder;
import io.lavagna.model.CardFull;
import io.lavagna.model.CardLabel;
import io.lavagna.model.CardLabel.LabelDomain;
import io.lavagna.model.CardLabel.LabelType;
import io.lavagna.model.ColumnDefinition;
import io.lavagna.model.ConfigurationKeyValue;
import io.lavagna.model.EventFull;
import io.lavagna.model.ImportContext;
import io.lavagna.model.LabelListValue;
import io.lavagna.model.LabelListValueWithMetadata;
import io.lavagna.model.Pair;
import io.lavagna.model.Permission;
import io.lavagna.model.Project;
import io.lavagna.model.Role;
import io.lavagna.model.RoleAndPermission;
import io.lavagna.model.StatisticForExport;
import io.lavagna.model.User;
import io.lavagna.model.UserIdentifier;
import io.lavagna.query.StatisticsQuery;
import io.lavagna.service.PermissionService.RoleAndPermissionsWithUsers;

import java.nio.file.Path;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang3.builder.CompareToBuilder;
import org.springframework.stereotype.Component;

import com.google.gson.reflect.TypeToken;

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

    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.isUnique(), 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.isArchived());
        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());
        }
    }

}