com.celements.navigation.service.TreeNodeService.java Source code

Java tutorial

Introduction

Here is the source code for com.celements.navigation.service.TreeNodeService.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.celements.navigation.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.annotation.Requirement;
import org.xwiki.context.Execution;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.SpaceReference;

import com.celements.common.classes.IClassCollectionRole;
import com.celements.inheritor.InheritorFactory;
import com.celements.iterator.XObjectIterator;
import com.celements.navigation.Navigation;
import com.celements.navigation.NavigationClasses;
import com.celements.navigation.TreeNode;
import com.celements.navigation.filter.INavFilter;
import com.celements.navigation.filter.InternalRightsFilter;
import com.celements.web.plugin.cmd.PageLayoutCommand;
import com.celements.web.service.IWebUtilsService;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseCollection;
import com.xpn.xwiki.objects.BaseObject;

/**
 * TreeNodeService
 * 
 * access a tree node structure through the method of this service.
 * 
 * @author Fabian Pichler
 *
 */
@Component
public class TreeNodeService implements ITreeNodeService {

    private static Log LOGGER = LogFactory.getFactory().getInstance(TreeNodeService.class);

    public PageLayoutCommand pageLayoutCmd;

    @Requirement
    ITreeNodeCache treeNodeCache;

    @Requirement
    IWebUtilsService webUtilsService;

    private InheritorFactory injectedInheritorFactory;

    @Requirement
    Map<String, ITreeNodeProvider> nodeProviders;

    @Requirement("celements.celNavigationClasses")
    IClassCollectionRole navigationClasses;

    @Requirement
    Execution execution;

    private XWikiContext getContext() {
        return (XWikiContext) execution.getContext().getProperty("xwikicontext");
    }

    private NavigationClasses getNavigationClasses() {
        return (NavigationClasses) navigationClasses;
    }

    private PageLayoutCommand getPageLayoutCmd() {
        if (pageLayoutCmd == null) {
            pageLayoutCmd = new PageLayoutCommand();
        }
        return pageLayoutCmd;
    }

    public int getActiveMenuItemPos(int menuLevel, String menuPart) {
        List<DocumentReference> parents = webUtilsService
                .getDocumentParentsList(getContext().getDoc().getDocumentReference(), true);
        if (parents.size() >= menuLevel) {
            return getMenuItemPos(parents.get(parents.size() - menuLevel), menuPart);
        }
        return -1;
    }

    public int getMenuItemPos(DocumentReference docRef, String menuPart) {
        try {
            EntityReference parent = getParentEntityRef(docRef);
            int pos = -1;
            for (TreeNode menuItem : getSubNodesForParent(parent, menuPart)) {
                pos = pos + 1;
                if (docRef.equals(menuItem.getDocumentReference())) {
                    return pos;
                }
            }
        } catch (XWikiException e) {
            LOGGER.error(e);
        }
        return -1;
    }

    public boolean isTreeNode(DocumentReference docRef) {
        //TODO move to ITreeNodeProvider and integrate over all nodeProviders
        try {
            XWikiDocument document = getContext().getWiki().getDocument(docRef, getContext());
            List<BaseObject> menuItems = document
                    .getXObjects(getNavigationClasses().getMenuItemClassRef(getContext().getDatabase()));
            return ((menuItems != null) && !menuItems.isEmpty());
        } catch (XWikiException exp) {
            LOGGER.error("Failed to get document for reference [" + docRef + "].", exp);
        }
        return false;
    }

    /**
     * 
     * @deprecated since 2.17.0 use getSubNodesForParent(EntityReference, INavFilter)  or 
     * getSubNodesForParent(EntityReference, String) instead
     */
    @Deprecated
    public <T> List<TreeNode> getSubNodesForParent(String parent, String menuSpace, INavFilter<T> filter) {
        if ("".equals(menuSpace)) {
            menuSpace = getContext().getDoc().getDocumentReference().getLastSpaceReference().getName();
        }
        String parentKey = getParentKey(parent, menuSpace);
        return getSubNodesForParent(resolveEntityReference(parentKey), filter);
    }

    String getParentKey(String parent, String menuSpace) {
        String parentKey = "";
        if (parent != null) {
            parentKey = parent;
        }
        if (parentKey.indexOf('.') < 0) {
            parentKey = menuSpace + "." + parentKey;
        }
        if (parentKey.indexOf(':') < 0) {
            parentKey = getContext().getDatabase() + ":" + parentKey;
        }
        return parentKey;
    }

    String getParentKey(EntityReference reference, boolean withDataBase) {
        String parentKey = "";
        if (reference != null) {
            parentKey = webUtilsService.getRefDefaultSerializer().serialize(reference);
            if (!parentKey.contains(":") && !parentKey.endsWith(":")) {
                parentKey += ":";
            } else if (!parentKey.contains(".") && !parentKey.endsWith(".")) {
                parentKey += ".";
            }
            if (!withDataBase) {
                parentKey = parentKey.substring(parentKey.indexOf(":") + 1);
            }
        }
        LOGGER.debug("getParentKey: returning [" + parentKey + "] for entityref [" + reference + "].");
        return parentKey;
    }

    public <T> List<TreeNode> getSubNodesForParent(EntityReference entRef, INavFilter<T> filter) {
        LOGGER.trace("getSubNodesForParent: entRef [" + entRef + "] filter class [" + filter.getClass() + "].");
        ArrayList<TreeNode> menuArray = new ArrayList<TreeNode>();
        for (TreeNode node : fetchNodesForParentKey(entRef)) {
            if ((node != null) && filter.includeTreeNode(node, getContext())) {
                // show only Menuitems of pages accessible to the current user
                menuArray.add(node);
            } else {
                LOGGER.debug("getSubNodesForParent: omit [" + node + "].");
            }
        }
        return menuArray;
    }

    /**
     * 
     * @deprecated since 2.17.0 use getSubNodesForParent(EntityReference, INavFilter)  or 
     * getSubNodesForParent(EntityReference, String) instead
     */
    @Deprecated
    public List<TreeNode> getSubNodesForParent(String parent, String menuSpace, String menuPart) {
        InternalRightsFilter filter = new InternalRightsFilter();
        filter.setMenuPart(menuPart);
        List<TreeNode> subNodesForParent = getSubNodesForParent(parent, menuSpace, filter);
        LOGGER.debug("getSubNodesForParent deprecated use: parent [" + parent + "] menuSpace [" + menuSpace
                + "] menuPart [" + menuPart + "] returning [" + subNodesForParent.size() + "].");
        return subNodesForParent;
    }

    public List<TreeNode> getSubNodesForParent(EntityReference entRef, String menuPart) {
        InternalRightsFilter filter = new InternalRightsFilter();
        filter.setMenuPart(menuPart);
        return getSubNodesForParent(entRef, filter);
    }

    /**
     * fetchNodesForParentKey
     * @param parentKey
     * @param context
     * @return Collection keeps ordering of TreeNodes according to posId
     */
    List<TreeNode> fetchNodesForParentKey(EntityReference parentRef) {
        String parentKey = getParentKey(parentRef, true);
        LOGGER.trace("fetchNodesForParentKey: parentRef [" + parentRef + "] parentKey [" + parentKey + "].");
        long starttotal = System.currentTimeMillis();
        long start = System.currentTimeMillis();
        List<TreeNode> nodes = fetchNodesForParentKey_internal(parentKey, starttotal, start);
        if ((nodeProviders != null) && (nodeProviders.values().size() > 0)) {
            TreeMap<Integer, TreeNode> treeNodesMergedMap = new TreeMap<Integer, TreeNode>();
            for (TreeNode node : nodes) {
                treeNodesMergedMap.put(new Integer(node.getPosition()), node);
            }
            for (ITreeNodeProvider tnProvider : nodeProviders.values()) {
                try {
                    for (TreeNode node : tnProvider.getTreeNodesForParent(parentKey)) {
                        treeNodesMergedMap.put(new Integer(node.getPosition()), node);
                    }
                } catch (Exception exp) {
                    LOGGER.warn("Failed on provider [" + tnProvider.getClass() + "] to get nodes for parentKey ["
                            + parentKey + "].", exp);
                }
            }
            nodes = new ArrayList<TreeNode>(treeNodesMergedMap.values());
            long end = System.currentTimeMillis();
            LOGGER.info("fetchNodesForParentKey: [" + parentKey + "] totaltime for list of [" + nodes.size() + "]: "
                    + (end - starttotal));
        }
        return nodes;
    }

    private List<TreeNode> fetchNodesForParentKey_internal(String parentKey, long starttotal, long start) {
        List<TreeNode> notMappedmenuItems = treeNodeCache.getNotMappedMenuItemsForParentCmd()
                .getTreeNodesForParentKey(parentKey, getContext());
        long end = System.currentTimeMillis();
        LOGGER.debug("fetchNodesForParentKey_internal: time for" + " getNotMappedMenuItemsFromDatabase: "
                + (end - start));
        start = System.currentTimeMillis();
        List<TreeNode> mappedTreeNodes = treeNodeCache.getMappedMenuItemsForParentCmd()
                .getTreeNodesForParentKey(parentKey, getContext());
        end = System.currentTimeMillis();
        LOGGER.debug(
                "fetchNodesForParentKey_internal: time for" + " getMappedMenuItemsForParentCmd: " + (end - start));
        start = System.currentTimeMillis();
        TreeMap<Integer, TreeNode> menuItemsMergedMap = null;
        if ((notMappedmenuItems == null) || (notMappedmenuItems.size() == 0)) {
            end = System.currentTimeMillis();
            LOGGER.info("fetchNodesForParentKey_internal: [" + parentKey + "] totaltime for list of ["
                    + mappedTreeNodes.size() + "]: " + (end - starttotal));
            return mappedTreeNodes;
        } else if (mappedTreeNodes.size() == 0) {
            end = System.currentTimeMillis();
            LOGGER.info("fetchNodesForParentKey_internal: [" + parentKey + "] totaltime for list of ["
                    + notMappedmenuItems.size() + "]: " + (end - starttotal));
            return notMappedmenuItems;
        } else {
            menuItemsMergedMap = new TreeMap<Integer, TreeNode>();
            for (TreeNode node : notMappedmenuItems) {
                menuItemsMergedMap.put(new Integer(node.getPosition()), node);
            }
            for (TreeNode node : mappedTreeNodes) {
                menuItemsMergedMap.put(new Integer(node.getPosition()), node);
            }
            end = System.currentTimeMillis();
            LOGGER.debug("fetchNodesForParentKey_internal: time for merging menu items: " + (end - start));
            ArrayList<TreeNode> menuItems = new ArrayList<TreeNode>(menuItemsMergedMap.values());
            LOGGER.info("fetchNodesForParentKey_internal: [" + parentKey + "] totaltime for list of ["
                    + menuItems.size() + "]: " + (end - starttotal));
            return menuItems;
        }
    }

    /**
     * 
     * @param parentKey
     * @param context
     * @return Collection keeps ordering of menuItems according to posId
     * 
     * @deprecated since 2.14.0  use new fetchNodesForParentKey instead
     */
    @Deprecated
    List<BaseObject> fetchMenuItemsForXWiki(String parentKey) {
        long starttotal = System.currentTimeMillis();
        List<BaseObject> menuItemList = new ArrayList<BaseObject>();
        EntityReference refParent = resolveEntityReference(parentKey);
        for (TreeNode node : fetchNodesForParentKey(refParent)) {
            try {
                XWikiDocument itemdoc = getContext().getWiki().getDocument(node.getFullName(), getContext());
                BaseObject cobj = itemdoc.getObject("Celements2.MenuItem");
                if (cobj != null) {
                    menuItemList.add(cobj);
                }
            } catch (XWikiException exp) {
                LOGGER.error("failed to get doc for menuItem", exp);
            }
        }
        long end = System.currentTimeMillis();
        LOGGER.info("fetchMenuItemsForXWiki: [" + parentKey + "] totaltime for list of [" + menuItemList.size()
                + "]: " + (end - starttotal));
        return menuItemList;
    }

    EntityReference resolveEntityReference(String name) {
        String wikiName = "", spaceName = "", docName = "";

        String[] name_a = name.split("\\.");
        String[] name_b = name_a[0].split(":");

        //wikiName:spaceName
        if (name_b.length > 1) {
            wikiName = name_b[0];
            spaceName = name_b[1];
            //wikiName:
        } else if (name_a[0].endsWith(":")) {
            if (name_b.length > 0) {
                wikiName = name_b[0];
            }
            //spaceName
        } else {
            wikiName = getContext().getDatabase();
            if (name_b.length > 0) {
                spaceName = name_b[0];
            }
        }
        if (name_a.length > 1) {
            docName = name_a[1];
        }

        EntityReference entRef = new EntityReference(wikiName, EntityType.WIKI);
        if (spaceName.length() > 0) {
            entRef = new EntityReference(spaceName, EntityType.SPACE, entRef);
            if (docName.length() > 0) {
                entRef = new EntityReference(docName, EntityType.DOCUMENT, entRef);
            }
        }
        LOGGER.debug("resolveEntityReference: for [" + name + "] returning [" + entRef + "].");
        return entRef;
    }

    /**
     * 
     * @deprecated since 2.14.0 use getSubNodesForParent(EntityReference, INavFilter)  or 
     * getSubNodesForParent(EntityReference, String) instead
     */
    @Deprecated
    public <T> List<T> getSubMenuItemsForParent(String parent, String menuSpace, INavFilter<T> filter) {
        if ("".equals(menuSpace)) {
            menuSpace = getContext().getDoc().getSpace();
        }
        String parentKey = getParentKey(parent, menuSpace);
        ArrayList<T> menuArray = new ArrayList<T>();
        for (BaseObject baseObj : fetchMenuItemsForXWiki(parentKey)) {
            if (filter.includeMenuItem(baseObj, getContext())) {
                // show only Menuitems of pages accessible to the current user
                menuArray.add(filter.convertObject(baseObj, getContext()));
            }
        }
        return menuArray;
    }

    public Integer getMaxConfiguredNavigationLevel() {
        List<BaseObject> navConfigObjects = new Vector<BaseObject>();
        List<BaseObject> navConfigDocsObjs = getNavObjectsOnConfigDocs();
        if (navConfigDocsObjs != null) {
            navConfigObjects.addAll(navConfigDocsObjs);
        }
        List<BaseObject> navConfigLayoutObjs = getNavObjectsFromLayout();
        if (navConfigLayoutObjs != null) {
            navConfigObjects.addAll(navConfigLayoutObjs);
        }
        if ((navConfigObjects != null) && (!navConfigObjects.isEmpty())) {
            int maxLevel = 0;
            for (BaseObject navObj : navConfigObjects) {
                if (navObj != null) {
                    maxLevel = Math.max(maxLevel, navObj.getIntValue(NavigationClasses.TO_HIERARCHY_LEVEL_FIELD));
                }
            }
            return maxLevel;
        }
        return Navigation.DEFAULT_MAX_LEVEL;
    }

    List<BaseObject> getNavObjectsFromLayout() {
        List<BaseObject> navObjects = new Vector<BaseObject>();
        SpaceReference layoutRef = getPageLayoutCmd().getPageLayoutForCurrentDoc();
        if (layoutRef != null) {
            XObjectIterator objectIterator = new XObjectIterator(getContext());
            List<String> layoutCellList = new Vector<String>();
            List<TreeNode> subNodesForParent = getSubNodesForParent(layoutRef, "");
            while (!subNodesForParent.isEmpty()) {
                List<TreeNode> newSubNodes = new Vector<TreeNode>();
                for (TreeNode node : subNodesForParent) {
                    List<TreeNode> subNodes = getSubNodesForParent(node.getDocumentReference(), "");
                    newSubNodes.addAll(subNodes);
                    if (subNodes.isEmpty()) {
                        layoutCellList.add(
                                webUtilsService.getRefDefaultSerializer().serialize(node.getDocumentReference()));
                    }
                }
                subNodesForParent = newSubNodes;
            }
            objectIterator.setDocList(layoutCellList);
            objectIterator.setClassName(NavigationClasses.NAVIGATION_CONFIG_CLASS);
            for (BaseObject navConfigObj : objectIterator) {
                navObjects.add(navConfigObj);
            }
        }
        return navObjects;
    }

    private List<BaseObject> getNavObjectsOnConfigDocs() {
        List<BaseObject> navConfigObjects2 = Collections.emptyList();
        try {
            BaseCollection navConfigObj = getInheritorFactory()
                    .getConfigDocFieldInheritor(NavigationClasses.NAVIGATION_CONFIG_CLASS,
                            getParentKey(getContext().getDoc().getDocumentReference(), false), getContext())
                    .getObject("menu_element_name");
            if (navConfigObj != null) {
                XWikiDocument navConfigDoc = getContext().getWiki().getDocument(navConfigObj.getDocumentReference(),
                        getContext());
                String navConfigDocWikiName = navConfigDoc.getDocumentReference().getLastSpaceReference()
                        .getParent().getName();
                navConfigObjects2 = navConfigDoc
                        .getXObjects(getNavigationClasses().getNavigationConfigClassRef(navConfigDocWikiName));
            } else {
                LOGGER.info("no config object found");
            }
        } catch (XWikiException e) {
            LOGGER.error("unable to get configDoc.", e);
        }
        return navConfigObjects2;
    }

    public TreeNode getPrevMenuItem(DocumentReference docRef) throws XWikiException {
        return getSiblingMenuItem(docRef, true);
    }

    public TreeNode getNextMenuItem(DocumentReference docRef) throws XWikiException {
        return getSiblingMenuItem(docRef, false);
    }

    TreeNode getSiblingMenuItem(DocumentReference docRef, boolean previous) throws XWikiException {
        XWikiDocument doc = getContext().getWiki().getDocument(docRef, getContext());
        BaseObject menuItem = doc.getXObject(getRef("Celements2", "MenuItem"));
        if (menuItem != null) {
            try {
                EntityReference parent = getParentEntityRef(docRef);
                List<TreeNode> subMenuItems = getSubNodesForParent(parent, menuItem.getStringValue("part_name"));
                LOGGER.debug("getPrevMenuItem: " + subMenuItems.size() + " subMenuItems found for parent '" + parent
                        + "'. " + Arrays.deepToString(subMenuItems.toArray()));
                int pos = getMenuItemPos(docRef, menuItem.getStringValue("part_name"));
                if (previous && (pos > 0)) {
                    return subMenuItems.get(pos - 1);
                } else if (!previous && (pos < (subMenuItems.size() - 1))) {
                    return subMenuItems.get(pos + 1);
                }
                LOGGER.info("getPrevMenuItem: no previous MenuItem found for " + getParentKey(docRef, true));
            } catch (XWikiException e) {
                LOGGER.error(e);
            }
        } else {
            LOGGER.debug("getPrevMenuItem: no MenuItem Object found on doc " + getParentKey(docRef, true));
        }
        return null;
    }

    public List<TreeNode> getMenuItemsForHierarchyLevel(int menuLevel, String menuPart) {
        DocumentReference parent = webUtilsService.getParentForLevel(menuLevel);
        if (parent != null) {
            List<TreeNode> submenuItems = getSubNodesForParent(parent, menuPart);
            LOGGER.debug("submenuItems for parent: " + parent + " ; " + submenuItems);
            return submenuItems;
        }
        LOGGER.debug("parent is null");
        return new ArrayList<TreeNode>();
    }

    DocumentReference getRef(String spaceName, String pageName) {
        return new DocumentReference(getContext().getDatabase(), spaceName, pageName);
    }

    private EntityReference getParentEntityRef(DocumentReference docRef) throws XWikiException {
        EntityReference parent = getContext().getWiki().getDocument(docRef, getContext()).getParentReference();
        if ((parent == null) || ("".equals(parent))) {
            parent = docRef.getLastSpaceReference();
        }
        return parent;
    }

    /**
     * FOR TEST PURPOSES ONLY
     */
    public void injectInheritorFactory(InheritorFactory injectedInheritorFactory) {
        this.injectedInheritorFactory = injectedInheritorFactory;
    }

    private InheritorFactory getInheritorFactory() {
        if (injectedInheritorFactory != null) {
            return injectedInheritorFactory;
        }
        return new InheritorFactory();
    }

}