com.aurel.track.admin.customize.category.CategoryBL.java Source code

Java tutorial

Introduction

Here is the source code for com.aurel.track.admin.customize.category.CategoryBL.java

Source

/**
 * Genji Scrum Tool and Issue Tracker
 * Copyright (C) 2015 Steinbeis GmbH & Co. KG Task Management Solutions
    
 * <a href="http://www.trackplus.com">Genji Scrum Tool</a>
 *
 * This program 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.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/* $Id:$ */

package com.aurel.track.admin.customize.category;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import com.aurel.track.accessControl.AccessBeans;
import com.aurel.track.admin.customize.category.filter.FilterBL;
import com.aurel.track.admin.customize.objectStatus.SystemStatusBL;
import com.aurel.track.admin.project.ProjectBL;
import com.aurel.track.admin.user.person.PersonBL;
import com.aurel.track.beans.ILabelBean;
import com.aurel.track.beans.TPersonBean;
import com.aurel.track.beans.TProjectBean;
import com.aurel.track.beans.TSystemStateBean;
import com.aurel.track.fieldType.constants.SystemFields;
import com.aurel.track.fieldType.runtime.base.LookupContainer;
import com.aurel.track.fieldType.runtime.bl.FieldRuntimeBL;
import com.aurel.track.json.JSONUtility;
import com.aurel.track.json.JSONUtility.DELETE_ERROR_CODES;
import com.aurel.track.resources.LocalizationKeyPrefixes;
import com.aurel.track.resources.LocalizeUtil;
import com.aurel.track.util.GeneralUtils;
import com.aurel.track.util.TreeNode;

public class CategoryBL {

    private static final Logger LOGGER = LogManager.getLogger(CategoryBL.class);

    /**
     * the categoryType constants
     */
    public static interface CATEGORY_TYPE {
        public static final String ISSUE_FILTER_CATEGORY = "issueFilter";
        public static final String NOTIFICATION_FILTER_CATEGORY = "notifyFilter";
        public static final String REPORT_CATEGORY = "report";
    }

    /**
     * The repository type
     * @author Tamas
     *
     */
    public static interface REPOSITORY_TYPE {
        public static final int PRIVATE = 1;
        public static final int PUBLIC = 2;
        public static final int PROJECT = 3;
    }

    public interface TYPE {
        //project folder
        public static final int PROJECT = 0;
        //user definable category
        public static final int CATEGORY = 1;
        //leaf (report or filter) depending on category type
        public static final int LEAF = 2;
    }

    static String getLocaleKeyByCategoryKey(String categorykey) {
        if (CATEGORY_TYPE.ISSUE_FILTER_CATEGORY.equals(categorykey)) {
            return "admin.customize.queryFilter.lbl.issueFilter";
        }
        if (CATEGORY_TYPE.NOTIFICATION_FILTER_CATEGORY.equals(categorykey)) {
            return "admin.customize.automail.filter.lblAlone";
        }
        if (CATEGORY_TYPE.REPORT_CATEGORY.equals(categorykey)) {
            return "admin.customize.reportTemplate.lbl";
        }
        return null;
    }

    /**
     * Get the children of a node
     * @param nodeID
     * @param locale
     * @return
     */
    static String getLeafDetail(String nodeID, Locale locale) {
        CategoryTokens categoryTokens = CategoryTokens.decodeNode(nodeID);
        String categoryType = categoryTokens.getCategoryType();
        Integer objectID = categoryTokens.getObjectID();
        LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType, objectID);
        return leafFacade.getFilterExpressionString(objectID, locale);
    }

    /**
     * Get the children of a node
     * @param nodeID
     * @param personBean
     * @param tree children list for tree or for grid
     * @param configuredProjectID
     * @param excludePrivate
      * @param allIfSystemAdmin
      * @param fromIssueNavigator
     * @param locale
     * @return
     */
    static List<CategoryTO> getChildren(String nodeID, TPersonBean personBean, boolean tree,
            Integer configuredProjectID, boolean excludePrivate, boolean allIfSystemAdmin,
            boolean fromIssueNavigator, Locale locale) {
        CategoryTokens categoryTokens = CategoryTokens.decodeNode(nodeID);
        String categoryType = categoryTokens.getCategoryType();
        Integer repository = categoryTokens.getRepository();
        Integer type = categoryTokens.getType();
        Integer objectID = categoryTokens.getObjectID();
        Integer personID = personBean.getObjectID();
        Map<Integer, TSystemStateBean> projectStatusMap = GeneralUtils.createMapFromList(
                SystemStatusBL.getSystemStatesByByEntityAndStateFlags(TSystemStateBean.ENTITYFLAGS.PROJECTSTATE,
                        new int[] { TSystemStateBean.STATEFLAGS.ACTIVE, TSystemStateBean.STATEFLAGS.INACTIVE,
                                TSystemStateBean.STATEFLAGS.CLOSED }));
        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
        if (repository == null) {
            //get repository nodes
            return getFirstLevelNodes(personBean, locale, categoryType, configuredProjectID, excludePrivate);
        } else {
            if (type == null) {
                //repository node
                if (REPOSITORY_TYPE.PROJECT == repository.intValue()) {
                    //project nodes   
                    List<TProjectBean> readableMainProjectList = null;
                    List<TProjectBean> adminMainProjectsList;
                    if (configuredProjectID == null) {
                        //all accessible projects
                        adminMainProjectsList = ProjectBL
                                .getActiveInactiveFirstLevelProjectsByProjectAdmin(personBean, allIfSystemAdmin);
                        readableMainProjectList = ProjectBL.getActiveInactiveFirstLevelProjectsByRights(personBean,
                                allIfSystemAdmin, new int[] { AccessBeans.AccessFlagIndexes.PROJECTADMIN,
                                        AccessBeans.AccessFlagIndexes.READANYTASK });
                    } else {
                        //automail filter: project is specified
                        adminMainProjectsList = new LinkedList<TProjectBean>();
                        TProjectBean projectBean = LookupContainer.getProjectBean(configuredProjectID);
                        if (projectBean != null) {
                            adminMainProjectsList.add(projectBean);
                            readableMainProjectList = adminMainProjectsList;
                        }
                    }
                    Set<Integer> adminProjectIDs = GeneralUtils.createIntegerSetFromBeanList(adminMainProjectsList);
                    return getProjectNodes(readableMainProjectList, adminProjectIDs, projectStatusMap, categoryType,
                            repository, tree, locale);

                } else {
                    //get the first level categories and reports/filters without a category for public and private repository
                    boolean modifiable = isModifiable(categoryFacade, categoryType, repository, type, objectID,
                            false, personBean);
                    return getRootCategoriesAndLeafs(categoryType, repository, null, personID, modifiable, tree,
                            fromIssueNavigator, locale);
                }
            } else {
                if (Integer.valueOf(TYPE.PROJECT).equals(type)) {
                    //get the first level categories and reports/filters without a category for a project
                    Integer projectID = objectID;
                    List<CategoryTO> childNodes = new LinkedList<CategoryTO>();
                    if (configuredProjectID == null) {
                        //get the subprojects only when we are not in a project specific category config 
                        //the readable ones are get without verifying the rights: all subprojects are readable
                        //because each have a readable ancestor
                        List<TProjectBean> readableSubprojectList = ProjectBL
                                .loadActiveInactiveSubrojects(projectID);
                        //get the admin subprojects: either implicit (has an admin ancestor) or explicit admin subproject
                        if (readableSubprojectList != null && !readableSubprojectList.isEmpty()) {
                            List<TProjectBean> adminSubprojectList;
                            Integer parentProject = projectID;
                            boolean adminAncestorFound = false;
                            while (parentProject != null) {
                                TProjectBean projectBean = LookupContainer.getProjectBean(parentProject);
                                boolean parentIsAdmin = PersonBL.isProjectAdmin(personID, parentProject);
                                if (parentIsAdmin) {
                                    adminAncestorFound = true;
                                    break;
                                } else {
                                    parentProject = projectBean.getParent();
                                }
                            }
                            if (adminAncestorFound) {
                                //implicit project admin
                                adminSubprojectList = readableSubprojectList;
                            } else {
                                //explicit project admin
                                adminSubprojectList = ProjectBL.loadNotClosedAdminSubprojects(personID, projectID);
                            }
                            Set<Integer> adminProjectIDs = GeneralUtils
                                    .createIntegerSetFromBeanList(adminSubprojectList);
                            childNodes = getProjectNodes(readableSubprojectList, adminProjectIDs, projectStatusMap,
                                    categoryType, repository, tree, locale);
                        }
                    }
                    boolean modifiable = isModifiable(categoryFacade, categoryType, repository, type, objectID,
                            false, personBean);
                    childNodes.addAll(getRootCategoriesAndLeafs(categoryType, repository, projectID, personID,
                            modifiable, tree, fromIssueNavigator, locale));
                    return childNodes;
                } else {
                    boolean modifiable = isModifiable(categoryFacade, categoryType, repository, type, objectID,
                            false, personBean);
                    //children of this category: subcategories and either filters or reports
                    if (Integer.valueOf(TYPE.CATEGORY).equals(type)) {
                        return getChildrenByCategory(categoryType, repository, objectID, personID, modifiable, tree,
                                fromIssueNavigator, locale);
                    } else {
                        if (CATEGORY_TYPE.REPORT_CATEGORY.equals(categoryType)) {
                            return getLeaf(categoryType, repository, objectID, personID, modifiable, tree,
                                    fromIssueNavigator, locale);
                        }
                    }
                }
            }
        }
        return new LinkedList<CategoryTO>();
    }

    /**
     * Get the report repositories 
     * @param personBean
     * @param locale
     * @param categoryType
     * @return
     */
    private static List<CategoryTO> getFirstLevelNodes(TPersonBean personBean, Locale locale, String categoryType,
            Integer projectID, boolean excludePrivate) {
        List<CategoryTO> rootNodes = new LinkedList<CategoryTO>();
        if (projectID == null) {
            //not in project settings
            //by automail settings in project configuration do not show the private notification filters
            if (!excludePrivate) {
                CategoryNodeTO privateRepositoryNode = new CategoryNodeTO(
                        CategoryTokens.encodeRootNode(categoryType, REPOSITORY_TYPE.PRIVATE), categoryType,
                        LocalizeUtil.getLocalizedTextFromApplicationResources("common.opt.repositoryType.private",
                                locale),
                        true, false, true);
                privateRepositoryNode.setLevel(0);
                rootNodes.add(privateRepositoryNode);
            }
            CategoryNodeTO projectRepositoryNode = new CategoryNodeTO(
                    CategoryTokens.encodeRootNode(categoryType, REPOSITORY_TYPE.PROJECT), categoryType, LocalizeUtil
                            .getLocalizedTextFromApplicationResources("common.opt.repositoryType.project", locale),
                    false, false, true);
            projectRepositoryNode.setLevel(0);
            rootNodes.add(projectRepositoryNode);
            CategoryNodeTO publicRepositoryNode = new CategoryNodeTO(
                    CategoryTokens.encodeRootNode(categoryType, REPOSITORY_TYPE.PUBLIC), categoryType, LocalizeUtil
                            .getLocalizedTextFromApplicationResources("common.opt.repositoryType.public", locale),
                    personBean.isSys(), false, true);
            publicRepositoryNode.setLevel(0);
            rootNodes.add(publicRepositoryNode);
        } else {
            //in project settings
            if (CATEGORY_TYPE.NOTIFICATION_FILTER_CATEGORY.equals(categoryType)) {
                //default automail settings for project: show both project (only for selected project) and public filters
                //(also public because they could also be selected for default automail settings for project)
                CategoryNodeTO projectRepositoryNode = new CategoryNodeTO(
                        CategoryTokens.encodeRootNode(categoryType, REPOSITORY_TYPE.PROJECT), categoryType,
                        LocalizeUtil.getLocalizedTextFromApplicationResources("common.opt.repositoryType.project",
                                locale),
                        false, false, true);
                rootNodes.add(projectRepositoryNode);
                CategoryNodeTO publicRepositoryNode = new CategoryNodeTO(
                        CategoryTokens.encodeRootNode(categoryType, REPOSITORY_TYPE.PUBLIC), categoryType,
                        LocalizeUtil.getLocalizedTextFromApplicationResources("common.opt.repositoryType.public",
                                locale),
                        personBean.isSys(), false, true);
                rootNodes.add(publicRepositoryNode);
            }
        }
        return rootNodes;
    }

    /**
     * Get the nodeID of the (not visible) root node for project specific issueFilters/reports 
     * @param projectID
     * @param categoryType
     * @return
     */
    public static String getProjectBranchNodeID(Integer projectID, String categoryType) {
        CategoryTokens projectFilterCategoryTokens = new CategoryTokens(categoryType, REPOSITORY_TYPE.PROJECT,
                Integer.valueOf(TYPE.PROJECT), projectID);
        return CategoryTokens.encodeNode(projectFilterCategoryTokens);
    }

    /**
     * Gets the project nodes
     * @param readableProjectList
     * @param adminProjectIDs
     * @param categoryType
     * @param repository
     * @param tree
     * @param locale
     * @return
     */
    private static List<CategoryTO> getProjectNodes(List<TProjectBean> readableProjectList,
            Set<Integer> adminProjectIDs, Map<Integer, TSystemStateBean> projectStatusMap, String categoryType,
            Integer repository, boolean tree, Locale locale) {
        List<CategoryTO> projectNodes = new ArrayList<CategoryTO>();
        String typeLabel = null;
        if (!tree) {
            typeLabel = FieldRuntimeBL.getLocalizedDefaultFieldLabel(SystemFields.INTEGER_PROJECT, locale);
        }
        if (readableProjectList != null) {
            for (TProjectBean projectBean : readableProjectList) {
                Integer projectID = projectBean.getObjectID();
                CategoryTokens projectCategoryTokens = new CategoryTokens(categoryType, repository,
                        Integer.valueOf(TYPE.PROJECT), projectID);
                CategoryTO categoryTO = null;
                if (tree) {
                    categoryTO = new CategoryNodeTO(CategoryTokens.encodeNode(projectCategoryTokens), categoryType,
                            projectBean.getLabel(), false, adminProjectIDs.contains(projectID), false, false);
                    ((CategoryNodeTO) categoryTO).setIconCls(ProjectBL.getProjectIcon(
                            ProjectBL.getProjectStateFlag(projectStatusMap, projectBean.getStatus())));
                } else {
                    categoryTO = new CategoryGridRowTO(CategoryTokens.encodeNode(projectCategoryTokens),
                            categoryType, projectBean.getLabel(), typeLabel, false, false);
                    categoryTO.setIconCls(ProjectBL.getProjectIcon(
                            ProjectBL.getProjectStateFlag(projectStatusMap, projectBean.getStatus())));
                }
                //project node is not modifiable
                categoryTO.setReadOnly(true);
                projectNodes.add(categoryTO);
            }
        }
        return projectNodes;
    }

    /**
     * Get the children for root nodes
     * @param categoryType
     * @param repository
     * @param projectID
     * @param personID
     * @param modifiable
     * @param tree
     * @param fromIssueNavigator
     * @param locale
     * @return
     */
    private static List<CategoryTO> getRootCategoriesAndLeafs(String categoryType, Integer repository,
            Integer projectID, Integer personID, boolean modifiable, boolean tree, boolean fromIssueNavigator,
            Locale locale) {
        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
        LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
        List<CategoryTO> nodes = new LinkedList<CategoryTO>();
        Integer filterPerson = null;
        if (repository.intValue() == REPOSITORY_TYPE.PRIVATE) {
            //filter the filters by person only if the repository is private
            filterPerson = personID;
        }
        List<ILabelBean> rootCategories = categoryFacade.getRootObjects(repository, projectID, filterPerson,
                locale);
        //first the categories in alphabetical order
        categoryFacade.addCategories(categoryType, repository, rootCategories, modifiable, tree, locale, nodes);
        //then the filters in alphabetical order
        List<ILabelBean> filtersWithoutCategory = leafFacade.getRootObjects(repository, projectID, filterPerson,
                locale);
        leafFacade.filterFromIssueNavigator(filtersWithoutCategory, fromIssueNavigator);
        //MenuitemQuery is person specific use the logged in person (not the filterPerson)
        leafFacade.addLeafs(repository, filtersWithoutCategory, personID, modifiable, tree, projectID, locale,
                nodes);
        return nodes;
    }

    /**
     * Get the children for a category: subcategories and leafs
     * @param repository
     * @param categoryID
     * @param personID
     * @param modifiable
     * @param tree whether to load CategoryNodeTO or CategoryGridRow objects
     * @param locale
     * @return
     */
    private static List<CategoryTO> getChildrenByCategory(String categoryType, Integer repository,
            Integer categoryID, Integer personID, boolean modifiable, boolean tree, boolean fromIssueNavigator,
            Locale locale) {
        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
        LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
        List<CategoryTO> nodes = new LinkedList<CategoryTO>();
        List<ILabelBean> subcategories = categoryFacade.getByParent(categoryID, locale);
        //first the categories in alphabetical order
        categoryFacade.addCategories(categoryType, repository, subcategories, modifiable, tree, locale, nodes);
        //then the filters in alphabetical order
        List<ILabelBean> leafsForCategory = leafFacade.getByParent(categoryID, locale);
        leafFacade.filterFromIssueNavigator(leafsForCategory, fromIssueNavigator);
        leafFacade.addLeafs(repository, leafsForCategory, personID, modifiable, tree,
                getProjectID(categoryType, repository, TYPE.CATEGORY, categoryID), locale, nodes);
        return nodes;
    }

    private static List<CategoryTO> getLeaf(String categoryType, Integer repository, Integer leafID,
            Integer personID, boolean modifiable, boolean tree, boolean fromIssueNavigator, Locale locale) {
        LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
        List<CategoryTO> nodes = new LinkedList<CategoryTO>();
        ILabelBean leafObject = leafFacade.getByKey(leafID, locale);
        List<ILabelBean> leafsForCategory = new LinkedList<ILabelBean>();
        leafsForCategory.add(leafObject);
        leafFacade.filterFromIssueNavigator(leafsForCategory, fromIssueNavigator);
        leafFacade.addLeafs(repository, leafsForCategory, personID, modifiable, tree,
                getProjectID(categoryType, repository, TYPE.LEAF, leafID), locale, nodes);
        return nodes;
    }

    /**
     * Whether the filter is modifiable
     * @param categoryBaseFacade
     * @param node
     * @param add
     * @param personBean
     * @return
     */
    public static boolean isModifiable(CategoryBaseFacade categoryBaseFacade, String categoryType,
            Integer repository, Integer type, Integer objectID, boolean add, TPersonBean personBean) {
        if (add) {
            return true;
        }
        Integer projectID = CategoryBL.getProjectID(categoryType, repository, type, objectID);
        return categoryBaseFacade.isModifiable(repository, objectID, projectID, personBean);

    }

    /**
     * The main repository nodes and projects are read only independently of any rights  
     * @param categoryBaseFacade
     * @param node
     * @param add
     * @return
     */
    public static boolean isReadOnly(String node, boolean add) {
        if (add) {
            return false;
        }
        CategoryTokens categoryTokens = CategoryTokens.decodeNode(node);
        Integer type = categoryTokens.getType();
        if (type == null || type.intValue() == TYPE.PROJECT) {
            return true;
        }
        return false;
    }

    /**
     * Adds the nodes hierarchically
     * @param categoryType
     * @param repository
     * @param rootCategories
     * @param rootLeafs
     * @param treeNodeMap
     */
    public static Integer hasPredefinedFilter(String categoryType, Integer categoryID) {
        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
        LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
        List<Integer> parentCategoryIDs = new LinkedList<Integer>();
        parentCategoryIDs.add(categoryID);
        while (parentCategoryIDs != null && !parentCategoryIDs.isEmpty()) {
            List<ILabelBean> leafs = (List) leafFacade.getByParents(parentCategoryIDs);
            for (ILabelBean leafBean : leafs) {
                if (FilterBL.filterIsPredefined(leafBean.getObjectID())) {
                    return leafBean.getObjectID();
                }
            }
            List<ILabelBean> parentCategoryBeans = (List) categoryFacade.getByParents(parentCategoryIDs);
            parentCategoryIDs = GeneralUtils.createIntegerListFromBeanList(parentCategoryBeans);
        }
        return null;
    }

    /**
     * Get the projectID for a node by edit
     * @param categoryType
     * @param repository
     * @param objectID
     * @return
     */
    public static Integer getProjectID(String categoryType, Integer repository, Integer type, Integer objectID) {
        //edit: a categoryID or a leafID depending on leaf
        if (repository != null && repository.intValue() == REPOSITORY_TYPE.PROJECT) {
            ILabelBean labelBean = null;
            if (type != null) {
                switch (type.intValue()) {
                case TYPE.PROJECT:
                    return objectID;
                case TYPE.LEAF:
                    LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
                    labelBean = leafFacade.getByKey(objectID);
                    if (labelBean != null) {
                        return leafFacade.getProjectID(labelBean);
                    }
                    break;
                case TYPE.CATEGORY:
                    if (type.intValue() == TYPE.CATEGORY) {
                        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance()
                                .getCategoryFacade(categoryType);
                        labelBean = categoryFacade.getByKey(objectID);
                        if (labelBean != null) {
                            return categoryFacade.getProjectID(labelBean);
                        }
                    }
                    break;
                }
            }
        }
        return null;
    }

    /**
     * Saves a new or existing custom list or system or custom list entry
     * @param nodeID
     * @param label
     * @param add
     * @param personID
     */
    static String save(String nodeID, String label, boolean add, Integer personID) {
        CategoryTokens categoryTokens = CategoryTokens.decodeNode(nodeID);
        String categoryType = categoryTokens.getCategoryType();
        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
        ILabelBean labelBean = categoryFacade.prepareBeanAfterAddEdit(categoryTokens, label, personID, add);
        Integer categoryID = categoryFacade.save(labelBean);
        categoryTokens.setType(CategoryBL.TYPE.CATEGORY);
        categoryTokens.setObjectID(categoryID);
        return CategoryJSON.createSaveResultJSON(true, CategoryTokens.encodeNode(categoryTokens), categoryID);
    }

    /**
     * Copies a filter category with the entire subtree
     * @param nodeFrom
     * @param nodeTo
     * @param personID
      * @param isCopy
     * @param locale
     * @return
     */
    public static String copy(String nodeFrom, String nodeTo, Integer personID, boolean isCopy, Locale locale) {
        CategoryTokens categoryTokenFrom = CategoryTokens.decodeNode(nodeFrom);
        CategoryTokens categoryTokenTo = CategoryTokens.decodeNode(nodeTo);
        String categoryType = categoryTokenFrom.getCategoryType();
        Integer repositoryFrom = categoryTokenFrom.getRepository();
        Integer repositoryTo = categoryTokenTo.getRepository();
        Integer typeFrom = categoryTokenFrom.getType();
        Integer typeTo = categoryTokenTo.getType();
        Integer objectIDFrom = categoryTokenFrom.getObjectID();
        Integer objectIDTo = categoryTokenTo.getObjectID();
        Integer objectID = null;
        try {
            objectID = copy(categoryType, typeFrom, typeTo, repositoryFrom, repositoryTo, objectIDFrom, objectIDTo,
                    personID, true, isCopy, locale, new HashMap<Integer, Integer>());
        } catch (CopyInDescendantException e) {
            LOGGER.debug(ExceptionUtils.getStackTrace(e));
            return CategoryJSON.createNodeResultJSON(false, nodeFrom,
                    LocalizeUtil.getLocalizedTextFromApplicationResources("common.err.copyToDescendent", locale));
        }
        categoryTokenFrom.setObjectID(objectID);
        categoryTokenFrom.setRepository(repositoryTo);
        return CategoryJSON.createSaveResultJSON(true, CategoryTokens.encodeNode(categoryTokenFrom), objectID);
    }

    /**
     * Copies a filter category with the entire subtree
     * @param categoryType
     * @param typeFrom
      * @param typeTo
      * @param repositoryFrom
      * @param repositoryTo
      * @param objectIDFrom
      * @param objectIDTo
     * @param personID
      * @param isCopy
     * @param locale
      * @param oldToNewLeafIDs
     * @return
     */
    public static Integer copy(String categoryType, Integer typeFrom, Integer typeTo, Integer repositoryFrom,
            Integer repositoryTo, Integer objectIDFrom, Integer objectIDTo, Integer personID,
            boolean mightAddCopyFlagToLabel, boolean isCopy, Locale locale, Map<Integer, Integer> oldToNewLeafIDs)
            throws CopyInDescendantException {
        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
        LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
        ILabelBean labelBeanTo = null;
        Integer categoryTo = null;
        Integer objectID = null;
        Integer projectTo = null;
        if (typeTo != null) {
            switch (typeTo.intValue()) {
            case CategoryBL.TYPE.PROJECT:
                projectTo = objectIDTo;
                break;
            case CategoryBL.TYPE.CATEGORY:
                labelBeanTo = categoryFacade.getByKey(objectIDTo);
                projectTo = categoryFacade.getProjectID(labelBeanTo);
                categoryTo = objectIDTo;
                break;
            case CategoryBL.TYPE.LEAF:
                labelBeanTo = leafFacade.getByKey(objectIDTo);
                projectTo = leafFacade.getProjectID(labelBeanTo);
                categoryTo = leafFacade.getParentID(labelBeanTo);
                break;
            }
        }
        if (typeFrom != null) {
            ILabelBean labelBeanFrom = null;
            switch (typeFrom.intValue()) {
            case CategoryBL.TYPE.LEAF:
                labelBeanFrom = leafFacade.getByKey(objectIDFrom);
                objectID = leafFacade.saveBeanAfterCutCopy(labelBeanFrom, repositoryTo, projectTo, categoryTo,
                        personID, locale, true, isCopy);
                oldToNewLeafIDs.put(labelBeanFrom.getObjectID(), objectID);
                break;
            case CategoryBL.TYPE.CATEGORY:
                labelBeanFrom = categoryFacade.getByKey(objectIDFrom);
                //Integer categoryFrom = objectIDFrom;
                if (categoryTo != null) {
                    //if not repository node but category
                    if (categoryFacade.isAncestor(objectIDFrom, categoryTo)) {
                        throw new CopyInDescendantException();
                        /*return CategoryJSON.createNodeResultJSON(false, nodeFrom, 
                              LocalizeUtil.getLocalizedTextFromApplicationResources("common.err.copyToDescendent", locale));*/
                    }
                }
                objectID = categoryFacade.copySubtree(labelBeanFrom, repositoryFrom, repositoryTo, projectTo,
                        categoryTo, personID, locale, mightAddCopyFlagToLabel, isCopy, leafFacade, oldToNewLeafIDs);
                break;
            }
        }
        return objectID;
    }

    /**
     * Deletes a category or leaf
     * @param node
     * @param deleteConfirmed
     * @param personBean
     * @param locale
     * @param servletResponse
     */
    public static void delete(String node, boolean deleteConfirmed, TPersonBean personBean, Locale locale,
            HttpServletResponse servletResponse) {
        CategoryTokens categoryTokens = CategoryTokens.decodeNode(node);
        Integer type = categoryTokens.getType();
        String categoryType = categoryTokens.getCategoryType();
        Integer objectID = categoryTokens.getObjectID();
        CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
        LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
        CategoryBaseFacade categoryBaseFacade = null;
        boolean leafReplaceNeeded = false;
        if (type.equals(Integer.valueOf(TYPE.LEAF))) {
            categoryBaseFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
            leafReplaceNeeded = leafFacade.replaceNeeded(objectID);

            if (FilterBL.filterIsPredefined(objectID)) {
                String localizedLabel = LocalizeUtil.getLocalizedEntity(LocalizationKeyPrefixes.FILTER_LABEL_PREFIX,
                        objectID, locale);
                String errorMessage = LocalizeUtil.getParametrizedString(
                        "admin.customize.queryFilter.deletePredefinedFilter", new Object[] { localizedLabel },
                        locale);
                JSONUtility.encodeJSON(servletResponse, JSONUtility.encodeJSONFailure(errorMessage,
                        JSONUtility.DELETE_ERROR_CODES.NO_RIGHT_TO_DELETE));
                return;
            }
        }
        boolean categoryReplaceNeeded = false;
        boolean categoryHasDependentData = false;
        if (type.equals(Integer.valueOf(TYPE.CATEGORY))) {
            categoryReplaceNeeded = categoryFacade.replaceNeeded(objectID);
            categoryHasDependentData = categoryFacade.hasDependentData(objectID);
            categoryBaseFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
            Integer predefinedFilter = CategoryBL.hasPredefinedFilter(categoryType, objectID);
            if (predefinedFilter != null) {
                String localizedLabel = LocalizeUtil.getLocalizedEntity(LocalizationKeyPrefixes.FILTER_LABEL_PREFIX,
                        predefinedFilter, locale);
                String errorMessage = LocalizeUtil.getParametrizedString(
                        "admin.customize.queryFilter.deleteCategoryWithPredefinedFilter",
                        new Object[] { localizedLabel }, locale);
                JSONUtility.encodeJSON(servletResponse, JSONUtility.encodeJSONFailure(errorMessage,
                        JSONUtility.DELETE_ERROR_CODES.NO_RIGHT_TO_DELETE));
                return;
            }
        }
        //whether the user has delete right on this node
        if (!CategoryBL.isModifiable(categoryBaseFacade, categoryType, categoryTokens.getRepository(), type,
                objectID, false, personBean)) {
            String errorMessage = LocalizeUtil.getParametrizedString("common.lbl.noDeleteRight",
                    new Object[] { getLocaleKeyByCategoryKey(categoryType) }, locale);
            JSONUtility.encodeJSON(servletResponse,
                    JSONUtility.encodeJSONFailure(errorMessage, JSONUtility.DELETE_ERROR_CODES.NO_RIGHT_TO_DELETE));
            return;
        }

        if (deleteConfirmed || (type.equals(Integer.valueOf(TYPE.LEAF)) && !leafReplaceNeeded)
                || (type.equals(Integer.valueOf(TYPE.CATEGORY))
                        && !(categoryReplaceNeeded || categoryHasDependentData))) {
            //delete if it has no dependency
            if (categoryBaseFacade != null) {
                categoryBaseFacade.delete(objectID, categoryType);
            }
            JSONUtility.encodeJSON(servletResponse, JSONUtility.encodeJSONSuccess());
        } else {
            if (type.equals(Integer.valueOf(TYPE.LEAF))) {
                //dependent leaf: prepare replacement tree
                JSONUtility.encodeJSON(servletResponse,
                        JSONUtility.encodeJSONFailure(DELETE_ERROR_CODES.NEED_REPLACE));
            } else {
                if (categoryReplaceNeeded) {
                    //category has dependent leafs: prepare replacement tree
                    JSONUtility.encodeJSON(servletResponse,
                            JSONUtility.encodeJSONFailure(DELETE_ERROR_CODES.NEED_REPLACE));
                } else {
                    //a not empty category but does not has dependent leafs: just ask for confirmation
                    String errorMessage = LocalizeUtil.getParametrizedString(
                            "admin.customize.queryFilter.deleteWarning",
                            new Object[] { LocalizeUtil.getLocalizedTextFromApplicationResources(
                                    "admin.customize.queryFilter.lbl.category", locale) },
                            locale);
                    JSONUtility.encodeJSON(servletResponse,
                            JSONUtility.encodeJSONFailure(errorMessage, DELETE_ERROR_CODES.NOT_EMPTY_WARNING));
                }
            }
        }
    }

    /**
     * Get the next unique name in the specified context
     * @param objectID
     * @param label
     * @param repository
     * @param categoryID
     * @param projectID
     * @param personID
     * @param locale
     * @param baseFacade
     * @return
     */
    public static String getNextUniqueName(Integer objectID, String label, Integer repository, Integer categoryID,
            Integer projectID, Integer personID, Integer subtype, Locale locale, CategoryBaseFacade baseFacade) {
        String labelToReturn = label;
        if (!isUniqueName(objectID, label, repository, categoryID, projectID, personID, subtype, baseFacade)) {
            String copyName = LocalizeUtil.getParametrizedString("common.lbl.copy", new Object[] { label }, locale);
            int i = 0;
            while (!isUniqueName(objectID, copyName, repository, categoryID, projectID, personID, subtype,
                    baseFacade)) {
                copyName = LocalizeUtil.getParametrizedString("common.lbl.copyWithNumber",
                        new Object[] { Integer.valueOf(++i), label }, locale);
            }
            labelToReturn = copyName;
        }
        return labelToReturn;
    }

    /**
     * Whether a name is unique in the specified context
     * @param objectID
     * @param label
     * @param repository
     * @param categoryID
     * @param projectID
     * @param personID
     * @param loaderResourceBundleMessages
     * @param baseFacade
     * @return
     */
    private static boolean isUniqueName(Integer objectID, String label, Integer repository, Integer categoryID,
            Integer projectID, Integer personID, Integer subtype, CategoryBaseFacade baseFacade) {
        boolean retValue = false;
        List<ILabelBean> sameNameList = baseFacade.getListByLabel(repository, categoryID, projectID, personID,
                subtype, label);
        if (sameNameList == null || sameNameList.isEmpty()) {
            retValue = true;
        } else {
            if (sameNameList.size() > 1 || objectID == null
                    || (objectID != null && !sameNameList.get(0).getObjectID().equals(objectID))) {
                retValue = false;
            } else {
                retValue = true;
            }
        }
        return retValue;
    }

    /**
     * Gets the replacement string for delete
     * @param node
     * @param errorMessage
     * @param locale
     * @return
     */
    static String getReplacementString(String node, String errorMessage, TPersonBean personBean, Locale locale) {
        CategoryTokens categoryTokens = CategoryTokens.decodeNode(node);
        String categoryType = categoryTokens.getCategoryType();
        Integer repository = categoryTokens.getRepository();
        boolean excludePrivate = false;
        if (repository != null && repository.intValue() != REPOSITORY_TYPE.PRIVATE) {
            //replacing a non-private only with a non-private: mostly correct although by user specific automail assignments the privat notification filters should not be excluded
            excludePrivate = true;
        }
        Integer type = categoryTokens.getType();
        Integer objectID = categoryTokens.getObjectID();

        String filterName = null;
        String replacementWarningLabel = null;
        String replacementFieldLabel = LocalizeUtil
                .getLocalizedTextFromApplicationResources("admin.customize.automail.filter.lblReplace", locale);
        CategoryBaseFacade categoryBaseFacade;
        if (type.equals(Integer.valueOf(TYPE.LEAF))) {
            categoryBaseFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
            replacementWarningLabel = replacementFieldLabel;
        } else {
            categoryBaseFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
            replacementWarningLabel = LocalizeUtil.getParametrizedString("common.lbl.below",
                    new Object[] { replacementFieldLabel, LocalizeUtil.getLocalizedTextFromApplicationResources(
                            "admin.customize.queryFilter.lbl.category", locale), },
                    locale);
        }
        ILabelBean labelBean = categoryBaseFacade.getByKey(objectID);
        Integer projectID = null;
        if (repository != null && repository.intValue() == REPOSITORY_TYPE.PROJECT) {
            projectID = categoryBaseFacade.getProjectID(labelBean);
        }
        if (labelBean != null) {
            filterName = labelBean.getLabel();
        }
        List<TreeNode> categoryTree = CategoryPickerBL.getPickerNodesEager(personBean, excludePrivate, false, null,
                true, projectID, objectID, locale, categoryType);
        return JSONUtility.createReplacementTreeJSON(true, filterName, replacementWarningLabel,
                replacementFieldLabel, JSONUtility.getTreeHierarchyJSON(categoryTree, false, true), errorMessage,
                locale);
    }

    /**
     * Replaces and deletes a node
     * @param node
     * @param replacementID
     */
    static void replaceAndDelete(String node, Integer replacementID) {
        CategoryTokens categoryTokens = CategoryTokens.decodeNode(node);
        String categoryType = categoryTokens.getCategoryType();
        Integer type = categoryTokens.getType();
        Integer objectID = categoryTokens.getObjectID();
        if (type.equals(Integer.valueOf(TYPE.LEAF))) {
            LeafFacade leafFacade = CategoryFacadeFactory.getInstance().getLeafFacade(categoryType);
            leafFacade.replace(objectID, replacementID);
        } else {
            CategoryFacade categoryFacade = CategoryFacadeFactory.getInstance().getCategoryFacade(categoryType);
            categoryFacade.replace(objectID, replacementID);
        }
    }
}