/** * Copyright (c) 2009--2014 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.frontend.nav; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * NavTreeIndex * @version $Rev$ */ public class NavTreeIndex { private static Logger log = Logger.getLogger(NavTreeIndex.class); private Map nodesByLabel; private Map childToParentMap; private Map depthMap; private Map nodeDirMap; //tab dir is key, value is List of NavNodes private Map nodeURLMap; //url is key, value is List of NavNodes private Map primaryURLMap; //url is key, value is NavNode private ArrayList nodeLevels; private Set activeNodes; //The best node and its parents (all highlighted in UI) private NavNode bestNode; //The node best corresponding with the url private NavTree tree; /** * Public constructor * @param treeIn the tree to index */ public NavTreeIndex(NavTree treeIn) { nodesByLabel = new HashMap(); childToParentMap = new HashMap(); depthMap = new HashMap(); nodeDirMap = new HashMap(); nodeURLMap = new HashMap(); primaryURLMap = new HashMap(); nodeLevels = new ArrayList(); activeNodes = new HashSet(); tree = treeIn; indexTree(); } /** * get the tree of that this index indexes * @return NavTree the tree in question */ public NavTree getTree() { return tree; } /** * get the "best" node of that this index indexes. "best" is * defined as the single node most fitting the request as passed * into computeActiveNodes. * @return NavNode the best node */ public NavNode getBestNode() { return bestNode; } private void indexTree() { int depth = 0; List nodesAtCurrentDepth = new ArrayList(tree.getNodes()); nodeLevels.add(depth, nodesAtCurrentDepth); Iterator i = nodesAtCurrentDepth.iterator(); while (i.hasNext()) { NavNode n = (NavNode)i.next(); indexNode(n, depth + 1); } } private void indexNode(NavNode parent, int depth) { depthMap.put(parent, new Integer(depth)); if (log.isDebugEnabled()) { log.debug("adding primaryurl to map [" + parent.getPrimaryURL() + "]"); } primaryURLMap.put(parent.getPrimaryURL(), parent); List nodesAtCurrentDepth = new ArrayList(parent.getNodes()); nodeLevels.add(depth, nodesAtCurrentDepth); addURLMaps(parent); addDirMaps(parent); if (parent.getLabel() != null) { nodesByLabel.put(parent.getLabel(), parent); } Iterator i = nodesAtCurrentDepth.iterator(); while (i.hasNext()) { NavNode child = (NavNode)i.next(); childToParentMap.put(child, parent); indexNode(child, depth + 1); } } private void addURLMaps(NavNode node) { Iterator i = node.getURLs().iterator(); while (i.hasNext()) { String url = (String)i.next(); List currentNodes = (List)nodeURLMap.get(url); if (currentNodes == null) { currentNodes = new ArrayList(); if (log.isDebugEnabled()) { log.debug("adding url map [" + url + "]"); } nodeURLMap.put(url, currentNodes); } currentNodes.add(node); } } private void addDirMaps(NavNode node) { Iterator i = node.getDirs().iterator(); while (i.hasNext()) { String dir = (String)i.next(); List currentNodes = (List)nodeDirMap.get(dir); if (currentNodes == null) { currentNodes = new ArrayList(); if (log.isDebugEnabled()) { log.debug("adding dir map [" + dir + "]"); } nodeDirMap.put(dir, currentNodes); } currentNodes.add(node); } } /** * Splits the given url string at the /. Returns the * parts in an array. For example, given "/network/users/details" * this method will return {"/network", "/users", "/details"} * @param urlIn url string to be split. * @return the parts in an array. */ public static String[] splitUrlPrefixes(String urlIn) { String url = StringUtils.strip(urlIn, "/"); String[] splitPath = StringUtils.split(url, "/"); List pathPrefixes = new ArrayList(splitPath.length + 1); // loop through the path parts of URL, creating a new split // URL for each pass, starting with longest, going to shortest for (int i = splitPath.length - 1; i >= 0; i--) { StringBuilder sb = new StringBuilder("/"); for (int j = 0; j <= i; j++) { sb.append(splitPath[j]); sb.append("/"); } // strip off trailing / from last pass in loop sb.deleteCharAt(sb.length() - 1); pathPrefixes.add(sb.toString()); } pathPrefixes.add("/"); return (String[])pathPrefixes.toArray(new String[] {}); } /** * Given a URL, compute the active nodes for the URL, altering the * state of the class. Pass in the most recently Active URL to be * used as a fallback if necessary. * * @param url string of form /foo/bar/baz * @param lastActive the last computed ActiveNode URL * @return String the URL computed */ public String computeActiveNodes(String url, String lastActive) { String[] prefixes = splitUrlPrefixes(url); // If we have a lastActive URL we // will add it to the end of the list of URLs to // use it as a last resort. if (lastActive != null) { String[] urls = new String[prefixes.length + 1]; // Add the lastActive to the end for (int i = 0; i < prefixes.length; i++) { urls[i] = prefixes[i]; } urls[prefixes.length] = lastActive; prefixes = urls; } return computeActiveNodes(prefixes); } /** * does the real work for computeActiveNodes * @param urls list of URLs, in order of preference, to match * @return String the URL computed */ private String computeActiveNodes(String[] urls) { bestNode = findBestNode(urls); if (bestNode == null) { // can't find an best node. assume topmost leftmost node is best ArrayList depthZero = (ArrayList)nodeLevels.get(0); bestNode = (NavNode)depthZero.get(0); } NavNode walker = bestNode; activeNodes = new HashSet(); while (walker != null) { activeNodes.add(walker); walker = (NavNode)childToParentMap.get(walker); } if (log.isDebugEnabled()) { log.debug("returning [" + bestNode.getPrimaryURL() + "] as the url of the active node"); } return bestNode.getPrimaryURL(); } private NavNode findBestNode(String[] urls) { for (int i = 0; i < urls.length; i++) { if (log.isDebugEnabled()) { log.debug("Url being searched [" + urls[i] + "]"); } // first match by the primary url which is the // first rhn-tab-url definition in the sitenav.xml. if (primaryURLMap.get(urls[i]) != null) { if (log.isDebugEnabled()) { log.debug("Primary node for [" + urls[i] + "] is [" + primaryURLMap.get(urls[i]) + "]"); } // we found a match, now let's make sure it is accessible // we need to do this because sometimes there are multiple // nodes with the same url. At that point they are // distinguishable only by acls. if (canViewUrl((NavNode)primaryURLMap.get(urls[i]), 0)) { return (NavNode)primaryURLMap.get(urls[i]); } } // either we couldn't find a primary url match OR it isn't // accessible. Let's go through the other url mappings (if any) // looking for an accessible url. List nodesByUrl = (List) nodeURLMap.get(urls[i]); if (nodesByUrl != null) { Iterator nodeItr = nodesByUrl.iterator(); while (nodeItr.hasNext()) { NavNode next = (NavNode)nodeItr.next(); if (canViewUrl(next, 1)) { if (log.isDebugEnabled()) { log.debug("Best node for [" + urls[i] + "] is [" + primaryURLMap.get(urls[i]) + "]"); } return next; } } } // finally, we couldn't find a match by primary url, nor by // any of the other mappings. At this point we will attempt // to match by directory if there was an rhn-tab-directory // definition. Otherwise, we're just going to bail and return // null. if (nodeDirMap.get(urls[i]) != null) { List nodes = (List)nodeDirMap.get(urls[i]); // what do we do with a list that contains // more than one. if (log.isDebugEnabled()) { log.debug("Best node for [" + urls[i] + "] is [" + nodes.get(0) + "]"); } return (NavNode)nodes.get(0); } } return null; } private boolean canViewUrl(NavNode node, int depth) { AclGuard guard = tree.getGuard(); // purposefully an or, not an and return (guard == null || guard.canRender(node, depth)); } /** * simple method to ask if a given node is in the active set * @param node to test * @return boolean if the node is active or not */ public boolean isNodeActive(NavNode node) { return activeNodes.contains(node); } }