Coverage Report - net.sf.tacos.services.impl.SiteMapImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
SiteMapImpl
86% 
89% 
3,391
 
 1  
 /*******************************************************************************
 2  
  * Copyright (c) 2005 Gabriel Handford.
 3  
  * All rights reserved.
 4  
  *
 5  
  * Contributors - Gabriel Handford
 6  
  ********************************************************************************/
 7  
 package net.sf.tacos.services.impl;
 8  
 
 9  
 import java.util.ArrayList;
 10  
 import java.util.Arrays;
 11  
 import java.util.HashMap;
 12  
 import java.util.Iterator;
 13  
 import java.util.LinkedList;
 14  
 import java.util.List;
 15  
 import java.util.Map;
 16  
 
 17  
 import net.sf.tacos.services.CategoryInfo;
 18  
 import net.sf.tacos.services.PageInfo;
 19  
 import net.sf.tacos.services.SiteMap;
 20  
 
 21  
 import org.apache.commons.logging.Log;
 22  
 import org.apache.commons.logging.LogFactory;
 23  
 import org.apache.hivemind.Resource;
 24  
 import org.dom4j.Document;
 25  
 import org.dom4j.DocumentException;
 26  
 import org.dom4j.Element;
 27  
 import org.dom4j.Node;
 28  
 import org.dom4j.io.SAXReader;
 29  
 
 30  
 /**
 31  
  * This class reads the sitemap configuration, and provides access to
 32  
  * basic relationship information, such as page categories, bread crumbs, and
 33  
  * other information, that the application may need to ease navigation.
 34  
  *
 35  
  * @author Gabriel Handford
 36  
  */
 37  
 public class SiteMapImpl implements SiteMap {
 38  
 
 39  2
     private static final Log log = LogFactory.getLog(SiteMapImpl.class);
 40  
 
 41  
     private Resource resource;
 42  
     private Document document;
 43  
 
 44  8
     private List cachedCategoryList = null;
 45  8
     private Map cachedPageMap = new HashMap();
 46  8
     private Map cachedCategoryInfo = new HashMap();
 47  
     
 48  
     private boolean compact;
 49  
     
 50  
     
 51  
     /**
 52  
      * Empty constructor.
 53  
      */
 54  16
     public SiteMapImpl() { }
 55  
     
 56  
     /**
 57  
      * Set the sitemap resource (xml).
 58  
      * @param resource The resource (xml) file
 59  
      */
 60  
     public void setResource(Resource resource) {
 61  6
         this.resource = resource;        
 62  6
     }
 63  
     
 64  
     /**
 65  
      * Initialize site map from a url (xml document).
 66  
      * @throws DocumentException on error
 67  
      * @throws IllegalStateException If no resource has been set.
 68  
      */
 69  
     public void initialize() throws DocumentException {
 70  7
         if (resource == null)
 71  1
             throw new IllegalStateException(
 72  
                     "No resource set, call setResource(..) before initialize()");
 73  6
         if (resource.getResourceURL() == null)
 74  0
             throw new IllegalStateException(
 75  
                     "No valid resource URL could be resolved:" + resource);
 76  6
         log.debug("Sitemap resource: " + resource.getResourceURL());
 77  6
         SAXReader reader = new SAXReader();
 78  6
         document = reader.read(resource.getResourceURL());
 79  
         
 80  6
         Element main = (Element)document.selectSingleNode("/sitemap");
 81  
         
 82  6
         String val = main.attributeValue("compact");
 83  6
         if (val != null && !val.trim().equals("")) 
 84  2
             compact = Boolean.valueOf(val).booleanValue();
 85  6
     }
 86  
 
 87  
     /**
 88  
      * Check if page name is contained in the page element tree.
 89  
      * @param parentName The page to look for.
 90  
      * @param pageName The page to start at (moves bottom-up).
 91  
      * @return True if the parent contains the page in its tree.
 92  
      */
 93  
     public boolean contains(String parentName, String pageName) {
 94  3
         PageInfo pageInfo = getPageInfo(pageName);
 95  3
         return containsImpl(parentName, pageInfo);
 96  
     }
 97  
 
 98  
     /**
 99  
      * Check if page name is contained in the page element tree.
 100  
      * @param parentName The page to look for.
 101  
      * @param pageInfo The page to start at (moves bottom-up).
 102  
      * @return True if the parent contains the page in its tree.
 103  
      */
 104  
     private boolean containsImpl(String parentName, PageInfo pageInfo) {
 105  5
         if (pageInfo == null) return false;
 106  5
         PageInfo parent = pageInfo.getParent();
 107  5
         if (parent == null) return false;
 108  3
         else if (parent.getName().equals(parentName)) return true;
 109  2
         else return containsImpl(parentName, parent);
 110  
     }
 111  
     
 112  
     /**
 113  
      * Get page information.
 114  
      * @param name The page name
 115  
      * @return The page info
 116  
      */
 117  
     public PageInfo getPageInfo(String name) {
 118  25
         return getPageInfoCacheImpl(name, null);
 119  
     }
 120  
 
 121  
     /**
 122  
      * Get page information (from cache).
 123  
      * @param name The page name.
 124  
      * @param source The page name source (for cycle detetion, first time is null).
 125  
      * @return The page.
 126  
      */
 127  
     private PageInfo getPageInfoCacheImpl(String name, String source) {
 128  25
         PageInfo pi = (PageInfo)cachedPageMap.get(name);
 129  25
         if (pi == null) {
 130  13
             pi = getPageInfoImpl(name, source);
 131  12
             if (pi != null) cachedPageMap.put(name, pi);
 132  
         } else {
 133  
             //log.debug("Returning cached page info: " + name);
 134  
         }
 135  24
         return pi;
 136  
     }
 137  
 
 138  
     /**
 139  
      * Get page information.
 140  
      * @param name The page name
 141  
      * @param source Original page name (for cycle detection)
 142  
      * @return The page info
 143  
      */
 144  
     private PageInfo getPageInfoImpl(String name, String source) {
 145  13
         if (document == null) throw new IllegalStateException("No document set, call setResource and initialize.");
 146  
         
 147  
         // Cycle check
 148  12
         if (source != null && name.equals(source))
 149  0
             throw new IllegalStateException("Found a cycle, " + name + "->" + source);
 150  12
         else if (source == null)
 151  12
             source = name;
 152  
         
 153  12
         Element node = findPageNode(name);
 154  12
         if (node != null) {
 155  
             
 156  11
             PageInfo page = parsePageNode(node);
 157  
             
 158  11
             Node pageCategoryNode = document.selectSingleNode("/sitemap/category//page[@name='" + name + "']");
 159  11
             if (pageCategoryNode != null) {
 160  11
                 parseNodeTree(page, pageCategoryNode);
 161  
                 //Because it will have been re-parsed
 162  11
                 return (PageInfo)cachedPageMap.get(page.getName());
 163  
             }
 164  
             
 165  0
             return page;
 166  
         }
 167  
         
 168  1
         return null;
 169  
     }
 170  
     
 171  
     /**
 172  
      * Finds the specified page node in the sitemap, first trying to find
 173  
      * it by name and then path.
 174  
      * @param page
 175  
      * @return The page, or null if not found.
 176  
      */
 177  
     protected Element findPageNode(String page)
 178  
     {
 179  33
         Element element = (Element)document.selectSingleNode("/sitemap/page[@name='" + page + "']");
 180  33
         if (element == null && compact)
 181  
         {
 182  8
             element = (Element)document.selectSingleNode("/sitemap/category//page[@name='" + page + "']");
 183  
         }
 184  33
         return element;       
 185  
     }
 186  
     
 187  
     /**
 188  
      * Parses a particular page node, doesn't parse
 189  
      * children or parent of {@link PageInfo}.
 190  
      * 
 191  
      * @param node Node to parse.
 192  
      * @return
 193  
      */
 194  
     protected PageInfo parsePageNode(Element node)
 195  
     {
 196  32
         if (node == null || !node.getName().equals("page"))
 197  0
             throw new IllegalArgumentException("Node was null or not a page node.");
 198  
         
 199  32
         String name = node.attributeValue("name");
 200  32
         String desc = node.attributeValue("desc");
 201  32
         String perm = node.attributeValue("permission");
 202  32
         String listItem = node.attributeValue("navigable");
 203  
         //Whether or not to list this page if a child.
 204  32
         boolean list = true;
 205  32
         if (listItem != null && !listItem.trim().equals("")) 
 206  0
             list = Boolean.valueOf(listItem).booleanValue();
 207  
         
 208  32
         PageInfo page = new PageInfoImpl(name, desc, perm, new ArrayList(), list);
 209  32
         return page;
 210  
     }
 211  
     
 212  
     /**
 213  
      * Iterates through the category tree represented 
 214  
      * by this page node and parses all children and parents.
 215  
      * 
 216  
      * @param page The page that caused the tree parse
 217  
      * @param pageNode
 218  
      */
 219  
     protected void parseNodeTree(PageInfo page, Node pageNode)
 220  
     {
 221  
         //First find top-level parent
 222  11
         Node parent = pageNode;
 223  
         while (parent.getParent() != null 
 224  15
                 && parent.getParent().getName().equals("page")) {
 225  4
             parent = parent.getParent();
 226  
         }
 227  
         
 228  11
         log.debug("Parsing tree for page <" + page.getName() + ">");
 229  
         //We can now parse all children of parent
 230  11
         parsePageNodes(parent, page);
 231  11
     }
 232  
     
 233  
     /**
 234  
      * Parses all children of specified node and adds them
 235  
      * to the page cache.
 236  
      * 
 237  
      * @param parent
 238  
      * @param source Page that caused the original parse.
 239  
      * @return The parent node that was parsed.
 240  
      */
 241  
     protected PageInfo parsePageNodes(Node parent, PageInfo source)
 242  
     {
 243  21
         PageInfo parentInfo = parsePageNode(findPageNode(((Element)parent).attributeValue("name")));
 244  21
         cachedPageMap.put(parentInfo.getName(), parentInfo);
 245  
         
 246  21
         log.debug("Parent page name <" + parent.valueOf("@name") + ">");
 247  21
         Iterator it = parent.selectNodes("*").iterator();
 248  31
         while (it.hasNext()) {
 249  10
             Node child = (Node)it.next();
 250  10
             PageInfo childInfo = parsePageNodes(child, source);
 251  
             
 252  10
             childInfo.setParent(parentInfo);
 253  10
             parentInfo.addChild(childInfo);
 254  10
         }
 255  
         
 256  21
         return parentInfo;
 257  
     }
 258  
     
 259  
     /**
 260  
      * Get category listing.
 261  
      * @return The category list
 262  
      */
 263  
     public synchronized List getCategories() {
 264  5
         if (document == null) throw new IllegalStateException("No document set, call setResource and initialize.");
 265  4
         if (cachedCategoryList == null) {
 266  2
             List nodes = document.selectNodes("/sitemap/category[@name]");
 267  2
             cachedCategoryList = new ArrayList(nodes.size());
 268  6
             for(int i = 0, size = nodes.size(); i < size; i++) {
 269  4
                 String category = ((Node)nodes.get(i)).valueOf("@name");
 270  4
                 cachedCategoryList.add(category);
 271  
             }
 272  
         }
 273  4
         return cachedCategoryList;
 274  
     }
 275  
 
 276  
     /**
 277  
      * Get category info for named category.
 278  
      * @param name Category name
 279  
      * @return The category info
 280  
      * @see SiteMap#getCategoryInfo(java.lang.String)
 281  
      */
 282  
     public CategoryInfo getCategoryInfo(String name) {
 283  24
         CategoryInfo ci = (CategoryInfo)cachedCategoryInfo.get(name);
 284  24
         if (ci != null) {
 285  15
             return ci;
 286  
         }
 287  
         
 288  9
         if (document == null) throw new IllegalStateException("No document set, call setResource and initialize.");
 289  
 
 290  8
         String imgName = null;
 291  8
         String inactiveImgName = null;
 292  
         
 293  8
         Element catnode = (Element)document.selectSingleNode("/sitemap/category[@name='" + name + "']");
 294  8
         if (catnode != null) {
 295  8
             imgName = catnode.attributeValue("image-active");
 296  8
             inactiveImgName = catnode.attributeValue("image-inactive");
 297  
         }
 298  
         
 299  
         //Node node = document.selectSingleNode("/sitemap/category[@name='" + name + "']");
 300  8
         List nodes = document.selectNodes("/sitemap/category[@name='" + name + "']/page");
 301  8
         if (nodes != null) {
 302  8
             List pageNames = new ArrayList(nodes.size());
 303  21
             for(int i = 0; i < nodes.size(); i++) {
 304  13
                 Node node = (Node)nodes.get(i);
 305  13
                 String pageName = node.valueOf("@name");
 306  13
                 pageNames.add(pageName);
 307  
             }
 308  8
             ci = new CategoryInfoImpl(name, pageNames, imgName, inactiveImgName);
 309  8
             cachedCategoryInfo.put(name, ci);
 310  8
             return ci;
 311  
         }
 312  0
         ci = new CategoryInfoImpl(name, null);
 313  0
         cachedCategoryInfo.put(name, ci);
 314  0
         return ci;
 315  
     }
 316  
 
 317  
     /**
 318  
      * Traverse up the tree to find the super parent.
 319  
      * @param pageInfo The page.
 320  
      * @return The topmost page.
 321  
      */
 322  
     private PageInfo getOrigin(PageInfo pageInfo) {
 323  11
         if (pageInfo != null && pageInfo.getParent() != null) return getOrigin(pageInfo.getParent());
 324  8
         return pageInfo;
 325  
     }
 326  
 
 327  
     /**
 328  
      * Get the category info for the page name.
 329  
      * @param pageName The page name.
 330  
      * @return The first category associated with this page.
 331  
      */
 332  
     public CategoryInfo getCategoryFromPage(String pageName) {
 333  9
         PageInfo pageInfo = getPageInfo(pageName);
 334  9
         if (pageInfo != null) {
 335  8
             PageInfo topMostPage = getOrigin(pageInfo);
 336  8
             String topPageName = topMostPage.getName();
 337  
 
 338  8
             Node node = document.selectSingleNode("/sitemap/category/page[@name='" + topPageName + "']");
 339  8
             if (node != null) {
 340  8
                 String categoryName = node.getParent().valueOf("@name");
 341  8
                 return getCategoryInfo(categoryName);
 342  
             }
 343  
         }
 344  1
         return null;
 345  
     }
 346  
     
 347  
     /**
 348  
      * Check if page name is in the specified category.
 349  
      * @param pageName Page name
 350  
      * @param category Category
 351  
      * @return True if in the specified category
 352  
      */
 353  
     public boolean inCategory(String pageName, String category) {                
 354  0
         CategoryInfo ci = getCategoryFromPage(pageName);
 355  0
         return (ci != null && ci.getName().equals(category));
 356  
     }
 357  
     
 358  
     /**
 359  
      * Get the default page.
 360  
      * @param category Category
 361  
      * @return Default page
 362  
      */
 363  
     public PageInfo getDefaultPage(String category) {
 364  0
         CategoryInfo ci = getCategoryInfo(category);
 365  0
         if (ci == null) return null;
 366  0
         return getPageInfo(ci.getDefaultPage());
 367  
     }
 368  
     
 369  
     /**
 370  
      * Get pages for a specific page names category.
 371  
      * @param pageName Page name
 372  
      * @return Pages
 373  
      */
 374  
     public List getCategoryPages(String pageName) {
 375  0
         CategoryInfo ci = getCategoryFromPage(pageName);
 376  0
         if (ci == null) return Arrays.asList(new Object[0]);
 377  0
         return ci.getPageNames();        
 378  
     }
 379  
     
 380  
     /**
 381  
      * Get the default page description.
 382  
      * @param category Category
 383  
      * @return Category default page description
 384  
      */
 385  
     public String getDefaultPageDesc(String category) {
 386  0
         PageInfo pi = getDefaultPage(category);
 387  0
         if (pi == null) return null;
 388  0
         return pi.getDesc();
 389  
     }
 390  
 
 391  
     /**
 392  
      * Get bread crumbs.
 393  
      * @param pageName The page name.
 394  
      * @return The bread crumbs (page name list).
 395  
      */
 396  
     public List getBreadCrumbs(String pageName) {
 397  2
         List list = new LinkedList();
 398  2
         PageInfo pageInfo = getPageInfo(pageName);
 399  2
         if (pageInfo == null) return Arrays.asList(new Object[0]);
 400  2
         loadBreadCrumbs(pageInfo, list);
 401  2
         return list;
 402  
     }
 403  
     
 404  
     /**
 405  
      * Load bread crumbs.
 406  
      * @param pageInfo The page.
 407  
      * @param breadCrumbs The list.
 408  
      */
 409  
     private void loadBreadCrumbs(PageInfo pageInfo, List breadCrumbs) {
 410  6
         PageInfo parent = pageInfo.getParent();
 411  6
         if (parent == null || parent.getName().equals(""))
 412  2
             return;
 413  
 
 414  4
         breadCrumbs.add(0, parent.getName());
 415  4
         loadBreadCrumbs(parent, breadCrumbs);
 416  4
     }
 417  
     
 418  
     public boolean getCompact()
 419  
     {
 420  0
         return compact;
 421  
     }
 422  
 
 423  
 }