com.haulmont.cuba.web.app.folders.CubaFoldersPane.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.web.app.folders.CubaFoldersPane.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.haulmont.cuba.web.app.folders;

import com.haulmont.cuba.core.app.DataService;
import com.haulmont.cuba.core.app.FoldersService;
import com.haulmont.cuba.core.entity.AbstractSearchFolder;
import com.haulmont.cuba.core.entity.AppFolder;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.entity.Folder;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.gui.ComponentsHelper;
import com.haulmont.cuba.gui.WindowManager;
import com.haulmont.cuba.gui.app.core.file.FileUploadDialog;
import com.haulmont.cuba.gui.components.Action.Status;
import com.haulmont.cuba.gui.components.DialogAction;
import com.haulmont.cuba.gui.components.DialogAction.Type;
import com.haulmont.cuba.gui.components.Frame;
import com.haulmont.cuba.gui.config.WindowConfig;
import com.haulmont.cuba.gui.executors.BackgroundTask;
import com.haulmont.cuba.gui.executors.BackgroundTaskWrapper;
import com.haulmont.cuba.gui.executors.TaskLifeCycle;
import com.haulmont.cuba.gui.export.ByteArrayDataProvider;
import com.haulmont.cuba.gui.export.ExportFormat;
import com.haulmont.cuba.gui.upload.FileUploadingAPI;
import com.haulmont.cuba.security.app.UserSettingService;
import com.haulmont.cuba.security.entity.SearchFolder;
import com.haulmont.cuba.web.App;
import com.haulmont.cuba.web.AppUI;
import com.haulmont.cuba.web.WebConfig;
import com.haulmont.cuba.web.app.UserSettingsTools;
import com.haulmont.cuba.web.filestorage.WebExportDisplay;
import com.haulmont.cuba.web.gui.components.WebComponentsHelper;
import com.haulmont.cuba.web.toolkit.ui.CubaTimer;
import com.haulmont.cuba.web.toolkit.ui.CubaTree;
import com.vaadin.event.Action;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutListener;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.ui.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;

import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;

import static com.haulmont.cuba.gui.components.Window.COMMIT_ACTION_ID;

/**
 * Left panel containing application and search folders.
 */
public class CubaFoldersPane extends VerticalLayout {

    private static final long serialVersionUID = 6666603397626574763L;
    public static final String C_FOLDERS_PANE = "c-folders-pane";

    protected boolean visible;

    protected Tree appFoldersTree;
    protected Tree searchFoldersTree;

    protected Label appFoldersLabel;
    protected Label searchFoldersLabel;

    protected Object appFoldersRoot;
    protected Object searchFoldersRoot;
    protected FoldersPaneTimer timer;

    protected static final int DEFAULT_VERT_SPLIT_POS = 50;
    protected int verticalSplitPos;

    protected VerticalSplitPanel vertSplit;

    protected WebConfig webConfig = AppBeans.<Configuration>get(Configuration.NAME).getConfig(WebConfig.class);

    protected Messages messages = AppBeans.get(Messages.NAME);

    protected Metadata metadata = AppBeans.get(Metadata.NAME);

    protected UserSessionSource userSessionSource = AppBeans.get(UserSessionSource.NAME);

    protected UserSettingService userSettingService = AppBeans.get(UserSettingService.NAME);

    protected FoldersService foldersService = AppBeans.get(FoldersService.NAME);

    protected DataService dataService = AppBeans.get(DataService.NAME);

    protected UserSettingsTools userSettingsTools = AppBeans.get(UserSettingsTools.NAME);

    protected Folders folders = AppBeans.get(Folders.NAME);

    protected BackgroundTaskWrapper<Integer, List<AppFolder>> folderUpdateBackgroundTaskWrapper;

    protected Frame frame;

    public CubaFoldersPane() {
        setSizeFull();
        setMargin(false);
        setSpacing(true);

        setStyleName(C_FOLDERS_PANE);
        //noinspection unchecked
        folderUpdateBackgroundTaskWrapper = new BackgroundTaskWrapper(new AppFolderUpdateBackgroundTask(10));
    }

    public void loadFolders() {
        UserSettingsTools.FoldersState state = userSettingsTools.loadFoldersState();
        if (state == null) {
            verticalSplitPos = DEFAULT_VERT_SPLIT_POS;
        } else {
            verticalSplitPos = state.verticalSplit;
        }

        showFolders(true);
    }

    protected void showFolders(boolean show) {
        if (show == visible)
            return;

        if (show) {
            Component appFoldersPane = createAppFoldersPane();
            if (appFoldersPane != null) {
                setupAppFoldersPane(appFoldersPane);
                setupUpdateTimer();
            }

            Component searchFoldersPane = createSearchFoldersPane();
            if (searchFoldersPane != null) {
                setupSearchFoldersPane(searchFoldersPane);
            }

            createFoldersPaneLayout(appFoldersPane, searchFoldersPane);
            adjustLayout();

            if (getParent() != null)
                getParent().markAsDirty();

            if (appFoldersTree != null) {
                collapseItemInTree(appFoldersTree, "appFoldersCollapse");
            }
            if (searchFoldersTree != null) {
                collapseItemInTree(searchFoldersTree, "searchFoldersCollapse");
            }

        } else {
            if (timer != null)
                timer.stop();

            removeAllComponents();
            setMargin(false);

            savePosition();

            appFoldersTree = null;
            searchFoldersTree = null;
        }

        visible = show;
    }

    protected void createFoldersPaneLayout(Component appFoldersPane, Component searchFoldersPane) {
        if (appFoldersPane != null && searchFoldersPane != null) {
            vertSplit = new VerticalSplitPanel();
            vertSplit.setSplitPosition(verticalSplitPos);

            VerticalLayout afLayout = new VerticalLayout();
            afLayout.setSpacing(true);
            afLayout.setSizeFull();
            if (appFoldersLabel != null)
                addFoldersLabel(afLayout, appFoldersLabel);
            afLayout.addComponent(appFoldersPane);
            afLayout.setExpandRatio(appFoldersPane, 1);
            vertSplit.setFirstComponent(afLayout);

            VerticalLayout sfLayout = new VerticalLayout();
            sfLayout.setSpacing(true);
            sfLayout.setSizeFull();
            if (searchFoldersLabel != null)
                addFoldersLabel(sfLayout, searchFoldersLabel);
            sfLayout.addComponent(searchFoldersPane);
            sfLayout.setExpandRatio(searchFoldersPane, 1);
            vertSplit.setSecondComponent(sfLayout);

            addComponent(vertSplit);
        } else {
            if (appFoldersPane != null) {
                if (appFoldersLabel != null)
                    addFoldersLabel(this, appFoldersLabel);
                addComponent(appFoldersPane);
                setExpandRatio(appFoldersPane, 1);
            }
            if (searchFoldersPane != null) {
                if (searchFoldersLabel != null)
                    addFoldersLabel(this, searchFoldersLabel);
                addComponent(searchFoldersPane);
                setExpandRatio(searchFoldersPane, 1);
            }
        }
    }

    protected void setupSearchFoldersPane(Component searchFoldersPane) {
        searchFoldersPane.setHeight("100%");
        searchFoldersPane.setWidth("100%");
        if (isNeedFoldersTitle()) {
            searchFoldersLabel = new Label(messages.getMainMessage("folders.searchFoldersRoot"));
            searchFoldersLabel.setStyleName("c-folders-pane-caption");
        } else {
            searchFoldersLabel = null;
        }
    }

    protected void setupUpdateTimer() {
        int period = webConfig.getAppFoldersRefreshPeriodSec() * 1000;

        timer = new FoldersPaneTimer();
        timer.setRepeating(true);
        timer.setDelay(period);
        timer.addActionListener(createAppFolderUpdater());
        timer.start();

        if (this.isAttached()) {
            AppUI ui = AppUI.getCurrent();
            stopExistingFoldersPaneTimer(ui);
            ui.addTimer(timer);
        } else if (frame != null) {
            com.haulmont.cuba.gui.components.Window window = ComponentsHelper.getWindowImplementation(frame);
            if (window == null) {
                throw new IllegalStateException("Null window for CubaFoldersPane");
            }
            AbstractComponent topLevelFrame = window.unwrapComposition(AbstractComponent.class);
            timer.extend(topLevelFrame);
        }
    }

    protected void stopExistingFoldersPaneTimer(AppUI ui) {
        for (CubaTimer t : ui.getTimers()) {
            if (t instanceof FoldersPaneTimer) {
                t.stop();
            }
        }
    }

    protected void setupAppFoldersPane(Component appFoldersPane) {
        appFoldersPane.setHeight("100%");
        appFoldersPane.setWidth("100%");
        if (isNeedFoldersTitle()) {
            appFoldersLabel = new Label(messages.getMainMessage("folders.appFoldersRoot"));
            appFoldersLabel.setStyleName("c-folders-pane-caption");
        } else {
            appFoldersLabel = null;
        }
    }

    protected void collapseItemInTree(Tree tree, final String foldersCollapse) {
        String s = userSettingService.loadSetting(foldersCollapse);
        List<UUID> idFolders = strToIds(s);
        //noinspection unchecked, RedundantCast
        for (AbstractSearchFolder folder : (Collection<AbstractSearchFolder>) tree.getItemIds()) {
            if (idFolders.contains(folder.getId())) {
                tree.collapseItem(folder);
            }
        }
        tree.addExpandListener(new Tree.ExpandListener() {
            @Override
            public void nodeExpand(Tree.ExpandEvent event) {
                if (event.getItemId() instanceof AbstractSearchFolder) {
                    UUID uuid = ((AbstractSearchFolder) event.getItemId()).getId();
                    String str = userSettingService.loadSetting(foldersCollapse);
                    userSettingService.saveSetting(foldersCollapse, removeIdInStr(str, uuid));
                }
            }
        });
        tree.addCollapseListener(new Tree.CollapseListener() {
            @Override
            public void nodeCollapse(Tree.CollapseEvent event) {
                if (event.getItemId() instanceof AbstractSearchFolder) {
                    UUID uuid = ((AbstractSearchFolder) event.getItemId()).getId();
                    String str = userSettingService.loadSetting(foldersCollapse);
                    userSettingService.saveSetting(foldersCollapse, addIdInStr(str, uuid));
                }
            }
        });
    }

    protected String addIdInStr(String inputStr, UUID uuid) {
        if (inputStr == null)
            inputStr = "";
        String str = uuid != null ? uuid.toString() : "";
        if (!inputStr.contains(str)) {
            if (inputStr.length() == 0)
                return str;
            else
                return inputStr + ":" + str;
        } else
            return inputStr;
    }

    protected String removeIdInStr(String inputStr, UUID uuid) {
        if (inputStr == null)
            inputStr = "";
        List<UUID> uuids = strToIds(inputStr);
        if (uuid != null)
            uuids.remove(uuid);
        return idsToStr(uuids);
    }

    protected List<UUID> strToIds(String inputStr) {
        if (inputStr == null)
            inputStr = "";
        String[] args = StringUtils.split(inputStr, ':');
        ArrayList<UUID> uuids = new ArrayList<>();
        for (String str : args) {
            uuids.add(UUID.fromString(str));
        }
        return uuids;
    }

    protected String idsToStr(List<UUID> uuids) {
        if (uuids == null)
            return "";
        StringBuilder sb = new StringBuilder();
        for (UUID uuid : uuids) {
            sb.append(":").append(uuid.toString());
        }
        if (sb.length() != 0) {
            sb.deleteCharAt(0);
        }
        return sb.toString();
    }

    protected Component addFoldersLabel(AbstractLayout layout, Label label) {
        HorizontalLayout l = new HorizontalLayout();
        l.setMargin(new MarginInfo(false, true, false, true));
        l.addComponent(label);
        l.setWidth("100%");
        layout.addComponent(l);
        return l;
    }

    public void savePosition() {
        if (visible) {
            if (vertSplit != null)
                verticalSplitPos = (int) vertSplit.getSplitPosition();
        }
        userSettingsTools.saveFoldersState(visible, -1, verticalSplitPos);
    }

    protected CubaTimer.ActionListener createAppFolderUpdater() {
        return new AppFoldersUpdater();
    }

    public void refreshFolders() {
        if (visible) {
            showFolders(false);
            showFolders(true);
        }
    }

    public void reloadAppFolders() {
        if (appFoldersTree == null)
            return;

        List<AppFolder> reloadedFolders = getReloadedFolders();
        reloadParentFolders(reloadedFolders);
        updateFolders(reloadedFolders);
    }

    public void asyncReloadAppFolders() {
        if (appFoldersTree == null)
            return;
        folderUpdateBackgroundTaskWrapper.restart();
    }

    protected void reloadParentFolders(List<AppFolder> reloadedFolders) {
        for (AppFolder folder : reloadedFolders) {
            if (StringUtils.isBlank(folder.getQuantityScript())) {
                if (appFoldersTree.isExpanded(folder)) {
                    folder.setQuantity(null);
                    folder.setItemStyle("");
                } else {
                    reloadSingleParentFolder(folder, reloadedFolders);
                }
            }
        }
    }

    protected Collection<AppFolder> getChildFolders(AppFolder parentFolder) {
        Collection<AppFolder> result = new LinkedList<>();
        //noinspection unchecked
        Collection<AppFolder> childFolders = (Collection<AppFolder>) appFoldersTree.getChildren(parentFolder);
        if (childFolders != null) {
            result.addAll(childFolders);
            for (AppFolder folder : childFolders)
                result.addAll(getChildFolders(folder));
        }
        return result;
    }

    protected void reloadSingleParentFolder(AppFolder parentFolder, @Nullable List<AppFolder> reloadedFolders) {
        Collection<AppFolder> childFolders = getChildFolders(parentFolder);
        int sumOfChildQuantity = 0;
        Set<String> childFoldersStyleSet = new HashSet<>();
        for (AppFolder childFolder : childFolders) {
            if (reloadedFolders != null) {
                childFolder = reloadedFolders.get(reloadedFolders.indexOf(childFolder));
            }
            sumOfChildQuantity += !StringUtils.isBlank(childFolder.getQuantityScript())
                    && childFolder.getQuantity() != null ? childFolder.getQuantity() : 0;
            if (childFolder.getItemStyle() != null)
                childFoldersStyleSet.add(childFolder.getItemStyle());
        }
        parentFolder.setQuantity(sumOfChildQuantity);
        if (!childFoldersStyleSet.isEmpty()) {
            parentFolder.setItemStyle(StringUtils.join(childFoldersStyleSet, " "));
        } else
            parentFolder.setItemStyle("");
    }

    protected List<AppFolder> getReloadedFolders() {
        @SuppressWarnings("unchecked")
        List<AppFolder> folders = new ArrayList(appFoldersTree.getItemIds());
        FoldersService service = AppBeans.get(FoldersService.NAME);
        return service.reloadAppFolders(folders);
    }

    protected void updateFolders(List<AppFolder> reloadedFolders) {
        @SuppressWarnings("unchecked")
        List<AppFolder> folders = new ArrayList(appFoldersTree.getItemIds());
        for (AppFolder folder : reloadedFolders) {
            int index = reloadedFolders.indexOf(folder);
            AppFolder f = folders.get(index);
            if (f != null) {
                f.setItemStyle(folder.getItemStyle());
                f.setQuantity(folder.getQuantity());
            }
            setFolderTreeItemCaption(appFoldersTree, folder);
        }
    }

    protected void adjustLayout() {
    }

    protected Component createAppFoldersPane() {
        List<AppFolder> appFolders = foldersService.loadAppFolders();
        if (appFolders.isEmpty())
            return null;

        appFoldersTree = new CubaTree();
        appFoldersTree.setCubaId("appFoldersTree");
        appFoldersTree.setSelectable(true);
        appFoldersTree.setItemStyleGenerator(new FolderTreeStyleProvider());
        appFoldersTree.addShortcutListener(
                new ShortcutListener("applyAppFolder", ShortcutAction.KeyCode.ENTER, (int[]) null) {
                    @Override
                    public void handleAction(Object sender, Object target) {
                        if (target == appFoldersTree) {
                            AbstractSearchFolder folder = (AbstractSearchFolder) appFoldersTree.getValue();
                            if (folder != null) {
                                openFolder(folder);
                            }
                        }
                    }
                });
        appFoldersTree.addExpandListener(new Tree.ExpandListener() {
            @Override
            public void nodeExpand(Tree.ExpandEvent event) {
                AppFolder folder = (AppFolder) event.getItemId();
                if (StringUtils.isBlank(folder.getQuantityScript())) {
                    folder.setQuantity(null);
                    folder.setItemStyle(null);
                    setFolderTreeItemCaption(appFoldersTree, folder);
                }
            }
        });
        appFoldersTree.addCollapseListener(new Tree.CollapseListener() {
            @Override
            public void nodeCollapse(Tree.CollapseEvent event) {
                AppFolder folder = (AppFolder) event.getItemId();
                if (StringUtils.isBlank(folder.getQuantityScript())) {
                    reloadSingleParentFolder(folder, null);
                    setFolderTreeItemCaption(appFoldersTree, folder);
                }
            }
        });

        appFoldersRoot = messages.getMainMessage("folders.appFoldersRoot");
        fillTree(appFoldersTree, appFolders, isNeedRootAppFolder() ? appFoldersRoot : null);
        appFoldersTree.addItemClickListener(new FolderClickListener());
        appFoldersTree.addActionHandler(new AppFolderActionsHandler());

        for (Object itemId : appFoldersTree.rootItemIds()) {
            appFoldersTree.expandItemsRecursively(itemId);
        }

        return appFoldersTree;
    }

    protected <T extends Folder> void setFolderTreeItemCaption(Tree tree, T folder) {
        tree.setItemCaption(folder, folder.getCaption());
    }

    protected Component createSearchFoldersPane() {
        searchFoldersTree = new CubaTree();
        searchFoldersTree.setCubaId("searchFoldersTree");
        searchFoldersTree.setSelectable(true);
        searchFoldersTree.setItemStyleGenerator(new FolderTreeStyleProvider());
        searchFoldersTree.addShortcutListener(
                new ShortcutListener("applySearchFolder", ShortcutAction.KeyCode.ENTER, (int[]) null) {
                    @Override
                    public void handleAction(Object sender, Object target) {
                        if (target == searchFoldersTree) {
                            AbstractSearchFolder folder = (AbstractSearchFolder) searchFoldersTree.getValue();
                            if (folder != null) {
                                openFolder(folder);
                            }
                        }
                    }
                });

        List<SearchFolder> searchFolders = foldersService.loadSearchFolders();
        searchFoldersRoot = messages.getMainMessage("folders.searchFoldersRoot");
        searchFoldersTree.addItemClickListener(new FolderClickListener());
        searchFoldersTree.addActionHandler(new SearchFolderActionsHandler());
        if (!searchFolders.isEmpty()) {
            fillTree(searchFoldersTree, searchFolders, isNeedRootSearchFolder() ? searchFoldersRoot : null);
        }

        for (Object itemId : searchFoldersTree.rootItemIds()) {
            searchFoldersTree.expandItemsRecursively(itemId);
        }
        return searchFoldersTree;
    }

    protected void fillTree(Tree tree, List<? extends Folder> folders, Object rootItemId) {
        if (rootItemId != null) {
            tree.addItem(rootItemId);
        }
        for (Folder folder : folders) {
            tree.addItem(folder);
            setFolderTreeItemCaption(tree, folder);
            if (webConfig.getShowFolderIcons()) {
                if (folder instanceof SearchFolder) {
                    if (BooleanUtils.isTrue(((SearchFolder) folder).getIsSet())) {
                        tree.setItemIcon(folder, WebComponentsHelper.getIcon("icons/set-small.png"));
                    } else {
                        tree.setItemIcon(folder, WebComponentsHelper.getIcon("icons/search-folder-small.png"));
                    }
                } else if (folder instanceof AppFolder) {
                    tree.setItemIcon(folder, WebComponentsHelper.getIcon("icons/app-folder-small.png"));
                }
            }
        }
        for (Folder folder : folders) {
            if (folder.getParent() == null) {
                tree.setParent(folder, rootItemId);
            } else {
                if (tree.getItem(folder.getParent()) != null)
                    tree.setParent(folder, folder.getParent());
                else
                    tree.setParent(folder, rootItemId);
            }
        }
        for (Folder folder : folders) {
            if (!tree.hasChildren(folder)) {
                tree.setChildrenAllowed(folder, false);
            }
        }
    }

    protected void openFolder(AbstractSearchFolder folder) {
        folders.openFolder(folder);
    }

    protected boolean isNeedRootAppFolder() {
        return false;
    }

    protected boolean isNeedRootSearchFolder() {
        return false;
    }

    protected boolean isNeedFoldersTitle() {
        return true;
    }

    public Folder saveFolder(Folder folder) {
        CommitContext commitContext = new CommitContext(Collections.singleton(folder));
        Set<Entity> res = dataService.commit(commitContext);
        for (Entity entity : res) {
            if (entity.equals(folder))
                return (Folder) entity;
        }
        return null;
    }

    public void removeFolder(Folder folder) {
        CommitContext commitContext = new CommitContext(Collections.emptySet(), Collections.singleton(folder));
        dataService.commit(commitContext);
    }

    public Tree getSearchFoldersTree() {
        return searchFoldersTree;
    }

    public Tree getAppFoldersTree() {
        return appFoldersTree;
    }

    public Collection<SearchFolder> getSearchFolders() {
        if (searchFoldersTree == null) {
            return Collections.emptyList();
        } else {
            @SuppressWarnings("unchecked")
            List result = new ArrayList(searchFoldersTree.getItemIds());
            result.remove(searchFoldersRoot);
            //noinspection unchecked
            return result;
        }
    }

    protected boolean getItemClickable(Folder folder) {
        return folder instanceof AbstractSearchFolder
                && !StringUtils.isBlank(((AbstractSearchFolder) folder).getFilterComponentId());
    }

    protected boolean isItemExpandable(Folder folder) {
        return folder instanceof AbstractSearchFolder
                && StringUtils.isBlank(((AbstractSearchFolder) folder).getFilterComponentId());
    }

    public void setFrame(Frame frame) {
        this.frame = frame;
    }

    public Frame getFrame() {
        return frame;
    }

    protected class FolderTreeStyleProvider implements Tree.ItemStyleGenerator {
        @Override
        public String getStyle(Tree source, Object itemId) {
            Folder folder = ((Folder) itemId);
            if (folder != null) {
                String style;
                // clickable tree item
                if (getItemClickable(folder))
                    style = "c-clickable-folder";
                else
                    style = "c-nonclickable-folder";
                // handle custom styles
                if (StringUtils.isNotBlank(folder.getItemStyle())) {
                    if (style.equals(""))
                        style = folder.getItemStyle();
                    else
                        style += " " + folder.getItemStyle();
                }

                return style;
            }
            return "";
        }
    }

    protected class FolderClickListener implements ItemClickEvent.ItemClickListener {
        @Override
        public void itemClick(ItemClickEvent event) {
            if (event.getButton() == MouseEventDetails.MouseButton.LEFT) {
                Folder folder = (Folder) event.getItemId();
                if (getItemClickable(folder)) {
                    openFolder((AbstractSearchFolder) event.getItemId());
                } else if (isItemExpandable(folder)) {
                    Component tree = event.getComponent();
                    if (tree instanceof Tree) {
                        if (((Tree) tree).isExpanded(folder))
                            ((Tree) tree).collapseItem(folder);
                        else
                            ((Tree) tree).expandItem(folder);
                    }
                }
            }
        }
    }

    protected class AppFolderActionsHandler implements Action.Handler {
        protected OpenAction openAction = new OpenAction();
        protected CreateAction createAction = new CreateAction(true);
        protected CopyAction copyAction = new CopyAction();
        protected EditAction editAction = new EditAction();
        protected RemoveAction removeAction = new RemoveAction();
        protected ExportAction exportAction = new ExportAction();
        protected ImportAction importAction = new ImportAction();

        @Override
        public Action[] getActions(Object target, Object sender) {
            if (target instanceof Folder) {
                if (isGlobalAppFolderPermitted()) {
                    return new Action[] { openAction, createAction, copyAction, editAction, removeAction,
                            exportAction, importAction };
                } else {
                    return new Action[] { openAction };
                }
            } else {
                if (isGlobalAppFolderPermitted()) {
                    return new Action[] { importAction };
                } else {
                    return null;
                }
            }
        }

        @Override
        public void handleAction(Action action, Object sender, Object target) {
            if (target instanceof Folder)
                ((FolderAction) action).perform((Folder) target);
            else
                ((FolderAction) action).perform(null);
        }

        protected boolean isGlobalAppFolderPermitted() {
            return userSessionSource.getUserSession().isSpecificPermitted("cuba.gui.appFolder.global");
        }
    }

    protected class SearchFolderActionsHandler extends AppFolderActionsHandler {

        protected CreateAction searchFolderCreateAction = new CreateAction(false);

        @Override
        public Action[] getActions(Object target, Object sender) {
            if (target instanceof SearchFolder) {
                if (isGlobalFolder((SearchFolder) target)) {
                    if (isFilterFolder((SearchFolder) target)) {
                        if (isGlobalSearchFolderPermitted()) {
                            return createAllActions();
                        } else {
                            return createOpenCreateAction();
                        }
                    } else {
                        if (isGlobalSearchFolderPermitted()) {
                            return createWithoutOpenActions();
                        } else {
                            return createOnlyCreateAction();
                        }
                    }
                } else {
                    if (isFilterFolder((SearchFolder) target)) {
                        if (isOwner((SearchFolder) target)) {
                            return createAllActions();
                        } else {
                            return createOpenCreateAction();
                        }
                    } else {
                        if (isOwner((SearchFolder) target)) {
                            return createWithoutOpenActions();
                        } else {
                            return createOnlyCreateAction();
                        }
                    }
                }
            } else {
                return createImportCreateAction();
            }
        }

        protected boolean isGlobalFolder(SearchFolder folder) {
            return (folder.getUser() == null);
        }

        protected boolean isFilterFolder(SearchFolder folder) {
            return (folder.getFilterComponentId() != null);
        }

        protected boolean isOwner(SearchFolder folder) {
            return userSessionSource.getUserSession().getCurrentOrSubstitutedUser().equals(folder.getUser());
        }

        protected boolean isGlobalSearchFolderPermitted() {
            return userSessionSource.getUserSession().isSpecificPermitted("cuba.gui.searchFolder.global");
        }

        protected Action[] createAllActions() {
            return new Action[] { openAction, copyAction, searchFolderCreateAction, editAction, removeAction,
                    exportAction, importAction };
        }

        protected Action[] createWithoutOpenActions() {
            return new Action[] { searchFolderCreateAction, editAction, removeAction };
        }

        protected Action[] createOnlyCreateAction() {
            return new Action[] { searchFolderCreateAction };
        }

        protected Action[] createImportCreateAction() {
            return new Action[] { searchFolderCreateAction, importAction };
        }

        protected Action[] createOpenCreateAction() {
            return new Action[] { openAction, searchFolderCreateAction, copyAction };
        }
    }

    protected abstract class FolderAction extends Action {

        public FolderAction(String caption) {
            super(caption);
        }

        public abstract void perform(Folder folder);
    }

    protected class OpenAction extends FolderAction {

        public OpenAction() {
            super(messages.getMainMessage("folders.openFolderAction"));
        }

        @Override
        public void perform(Folder folder) {
            if (folder instanceof AbstractSearchFolder)
                openFolder((AbstractSearchFolder) folder);
        }
    }

    protected class CreateAction extends FolderAction {

        private boolean isAppFolder;

        public CreateAction(boolean isAppFolder) {
            super(messages.getMainMessage("folders.createFolderAction"));
            this.isAppFolder = isAppFolder;
        }

        @Override
        public void perform(final Folder folder) {
            final Folder newFolder = isAppFolder ? metadata.create(AppFolder.class)
                    : metadata.create(SearchFolder.class);
            newFolder.setName("");
            newFolder.setTabName("");
            newFolder.setParent(folder);
            final FolderEditWindow window = AppFolderEditWindow.create(isAppFolder, true, newFolder, null,
                    new Runnable() {
                        @Override
                        public void run() {
                            saveFolder(newFolder);
                            refreshFolders();
                        }
                    });
            AppUI.getCurrent().addWindow(window);
        }
    }

    protected class CopyAction extends FolderAction {

        public CopyAction() {
            super(messages.getMainMessage("folders.copyFolderAction"));
        }

        @Override
        public void perform(final Folder folder) {
            AbstractSearchFolder newFolder = (AbstractSearchFolder) metadata.create(folder.getMetaClass());
            newFolder.copyFrom((AbstractSearchFolder) folder);
            new EditAction().perform(newFolder);
        }
    }

    protected class EditAction extends FolderAction {

        public EditAction() {
            super(messages.getMainMessage("folders.editFolderAction"));
        }

        @Override
        public void perform(final Folder folder) {
            final FolderEditWindow window;
            if (folder instanceof SearchFolder) {
                window = AppFolderEditWindow.create(false, false, folder, null, () -> {
                    saveFolder(folder);
                    refreshFolders();
                });
            } else {
                if (folder instanceof AppFolder) {
                    window = AppFolderEditWindow.create(true, false, folder, null, () -> {
                        saveFolder(folder);
                        refreshFolders();
                    });
                } else {
                    return;
                }
            }
            AppUI.getCurrent().addWindow(window);
        }
    }

    protected class RemoveAction extends FolderAction {

        public RemoveAction() {
            super(messages.getMainMessage("folders.removeFolderAction"));
        }

        @Override
        public void perform(final Folder folder) {
            App.getInstance().getWindowManager().showOptionDialog(messages.getMainMessage("dialogs.Confirmation"),
                    messages.getMainMessage("folders.removeFolderConfirmation"), Frame.MessageType.CONFIRMATION,
                    new com.haulmont.cuba.gui.components.Action[] {
                            new DialogAction(Type.YES).withHandler(event -> {
                                removeFolder(folder);
                                refreshFolders();
                            }), new DialogAction(Type.NO, Status.PRIMARY) });
        }
    }

    protected class ExportAction extends FolderAction {

        public ExportAction() {
            super(messages.getMainMessage("folders.exportFolderAction"));
        }

        @Override
        public void perform(Folder folder) {
            try {
                byte[] data = foldersService.exportFolder(folder);
                AppBeans.get(WebExportDisplay.class).show(new ByteArrayDataProvider(data), "Folders",
                        ExportFormat.ZIP);
            } catch (IOException ignored) {
            }
        }
    }

    protected class ImportAction extends FolderAction {

        public ImportAction() {
            super(messages.getMainMessage("folders.importFolderAction"));
        }

        @Override
        public void perform(final Folder folder) {
            WindowConfig windowConfig = AppBeans.get(WindowConfig.NAME);

            final FileUploadDialog dialog = (FileUploadDialog) App.getInstance().getWindowManager()
                    .openWindow(windowConfig.getWindowInfo("fileUploadDialog"), WindowManager.OpenType.DIALOG);

            dialog.addCloseListener(actionId -> {
                if (COMMIT_ACTION_ID.equals(actionId)) {
                    try {
                        FileUploadingAPI fileUploading = AppBeans.get(FileUploadingAPI.NAME);
                        byte[] data = FileUtils.readFileToByteArray(fileUploading.getFile(dialog.getFileId()));
                        fileUploading.deleteFile(dialog.getFileId());
                        foldersService.importFolder(folder, data);
                    } catch (AccessDeniedException ex) {
                        throw ex;
                    } catch (Exception ex) {
                        dialog.showNotification(messages.getMainMessage("folders.importFailedNotification"),
                                ex.getMessage(), Frame.NotificationType.ERROR);
                    }
                    refreshFolders();
                }
            });
        }
    }

    public class AppFolderUpdateBackgroundTask extends BackgroundTask<Integer, List<AppFolder>> {

        public AppFolderUpdateBackgroundTask(long timeoutSeconds) {
            super(timeoutSeconds);
        }

        @Override
        public List<AppFolder> run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
            return getReloadedFolders();
        }

        @Override
        public void done(List<AppFolder> reloadedFolders) {
            reloadParentFolders(reloadedFolders);
            updateFolders(reloadedFolders);
        }
    }

    public class AppFoldersUpdater implements CubaTimer.ActionListener {
        @Override
        public void timerAction(CubaTimer timer) {
            reloadAppFolders();
        }
    }

    // used for instance of to detect folders pane timer
    protected static class FoldersPaneTimer extends CubaTimer {
    }
}