/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.menu.classic.application; import java.util.LinkedList; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.globant.katari.core.security.MenuAccessFilterer; import com.globant.katari.core.web.MenuBar; import com.globant.katari.core.web.MenuNode; /** Utility class to assist in the selection of menu entries to display for a * user. */ public class MenuDisplayHelper { /** The class logger. */ private static Logger log = LoggerFactory.getLogger(MenuDisplayHelper.class); /** The menu bar to obtain the list of menu nodes. * * It is never null. */ private MenuBar menu; /** The current menu entry, expressed as a path of menu names, like * /root/Administration/Users, never null. */ private String currentMenuEntry; /** The Menu Access Filterer. * * It's used to filter the menu nodes according to the user permissions. * This is never null. */ private MenuAccessFilterer filterer; /** Builds a menu display helper. * * @param menuBar The menu bar to obtain the list of menu nodes. It cannot be * null. * * @param current The current menu entry, expressed as a path of menu names, * like /root/Administration/Users. It cannot be null. * * @param theFilterer The Menu Access Filterer used to filter the menu nodes * according to the user permissions. It cannot be null. */ public MenuDisplayHelper(final MenuBar menuBar, final String current, final MenuAccessFilterer theFilterer) { Validate.notNull(menuBar, "The menu bar cannot be null"); Validate.notNull(current, "The current menu entry cannot be null"); Validate.notNull(menuBar, "The menu access filterer cannot be null"); menu = menuBar; currentMenuEntry = current; filterer = theFilterer; } /** Obtains a list of MenuNodes with its display attributes according to the * currently logged on user permissions, the menu level and the current * selection. * * @param level The level required. The list of menu nodes is obtained from * this level. It must be greater than 1. A level 0 represent the root level, * never returned by this method. * * @return a list of menu nodes with the display attributes according to the * currently logged on user permissions. */ public List<MenuNodeDisplay> getMenuNodesForLevel(final int level) { Validate.isTrue(level > 0, "The level must be greater than 0"); return getMenuNodesForLevel(menu, level - 1); } /** Obtains a list of MenuNodes with its display attributes according to the * currently logged on user permissions and the current selection. * * It returns all the menus that are children of the specified path. * * @return a list of menu nodes with the display attributes. */ public List<MenuNodeDisplay> getMenuNodesForCurrentPath() { int level = StringUtils.countMatches(currentMenuEntry, "/"); return getMenuNodesForLevel(level); } /** Obtains a list of MenuNodes with its display attributes according to the * currently logged on user permissions, the menu level and the current * selection. * * It starts looking from the provided node. * * @param node The node to start looking. It cannot be null. * * @param level The level required. The list of menu nodes is obtained from * this level. * * @return a list of menu nodes with the display attributes according to the * currently logged on user permissions. If node if a leaf, it returns the * empty list. Never returns null. */ private List<MenuNodeDisplay> getMenuNodesForLevel(final MenuNode node, final int level) { Validate.notNull(node, "The menu node cannot be null"); if (log.isTraceEnabled()) { log.trace("Entering getMenuNodesForLevel('" + node.getPath() + "', '" + currentMenuEntry + "', " + level + ")"); } List<MenuNodeDisplay> nodes = new LinkedList<MenuNodeDisplay>(); if (node.isLeaf()) { return nodes; } List<MenuNode> filteredMenuNodes; filteredMenuNodes = filterer.filterMenuNodes(node.getChildNodes()); for (MenuNode child : filteredMenuNodes) { log.debug("Checking if '" + child.getPath() + "' is selected"); boolean selected; selected = (currentMenuEntry + "/").startsWith(child.getPath() + "/"); if (level == 0) { // We reached the desired level, add the child node to the list. log.debug("Adding '" + child.getPath() + "' to the list"); String linkPath; String path; if (child.isLeaf()) { linkPath = child.getLinkPath(); path = child.getPath(); } else { // It is not a leaf, we must find the first non-leaf descendant node // that the user has access to and use its link for the current node // link. if (child.getHome() == null) { // The children of the node come from merging two (or more) menu // entries. linkPath = "/module/classic-menu/menu.do"; path = child.getPath(); } else { MenuNode descendant = findFirstDescendentLeaf(child); /// TODO Is this null check necessary? Aren't we masking an error? if (descendant != null) { linkPath = descendant.getLinkPath(); path = descendant.getPath(); } else { linkPath = "/module/classic-menu/menu.do"; path = child.getPath(); } } } nodes.add(new MenuNodeDisplay(child, path, linkPath, true, selected)); } else if (selected) { // Not in the desired level yet, but the current node is the selected // one, go one level deeper. nodes = getMenuNodesForLevel(child, level - 1); break; } } log.trace("Leaving getMenuNodesForLevel"); return nodes; } /** Returns the current menu entry. * * @return the current menu entry, never null. */ public String getCurrentMenuEntry() { return currentMenuEntry; } /** Finds the first descendant leaf of the provided node that the user has * access to. * * @param node The node to start looking. It must be a non-leaf node. It * cannot be null. * * @return Returns the first accesible descendant, or null if the node has no * accessible descendants. */ MenuNode findFirstDescendentLeaf(final MenuNode node) { Validate.notNull(node, "The menu node cannot be null"); Validate.isTrue(!node.isLeaf(), "The menu node cannot be a leaf"); List<MenuNode> filteredMenuNodes; filteredMenuNodes = filterer.filterMenuNodes(node.getChildNodes()); if (filteredMenuNodes.size() > 0) { MenuNode child = filteredMenuNodes.get(0); if (child.isLeaf()) { return child; } else { return findFirstDescendentLeaf(child); } } return null; } }