View Javadoc

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      private static final Log log = LogFactory.getLog(SiteMapImpl.class);
40  
41      private Resource resource;
42      private Document document;
43  
44      private List cachedCategoryList = null;
45      private Map cachedPageMap = new HashMap();
46      private Map cachedCategoryInfo = new HashMap();
47      
48      private boolean compact;
49      
50      
51      /**
52       * Empty constructor.
53       */
54      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          this.resource = resource;        
62      }
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          if (resource == null)
71              throw new IllegalStateException(
72                      "No resource set, call setResource(..) before initialize()");
73          if (resource.getResourceURL() == null)
74              throw new IllegalStateException(
75                      "No valid resource URL could be resolved:" + resource);
76          log.debug("Sitemap resource: " + resource.getResourceURL());
77          SAXReader reader = new SAXReader();
78          document = reader.read(resource.getResourceURL());
79          
80          Element main = (Element)document.selectSingleNode("/sitemap");
81          
82          String val = main.attributeValue("compact");
83          if (val != null && !val.trim().equals("")) 
84              compact = Boolean.valueOf(val).booleanValue();
85      }
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          PageInfo pageInfo = getPageInfo(pageName);
95          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         if (pageInfo == null) return false;
106         PageInfo parent = pageInfo.getParent();
107         if (parent == null) return false;
108         else if (parent.getName().equals(parentName)) return true;
109         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         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         PageInfo pi = (PageInfo)cachedPageMap.get(name);
129         if (pi == null) {
130             pi = getPageInfoImpl(name, source);
131             if (pi != null) cachedPageMap.put(name, pi);
132         } else {
133             //log.debug("Returning cached page info: " + name);
134         }
135         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         if (document == null) throw new IllegalStateException("No document set, call setResource and initialize.");
146         
147         // Cycle check
148         if (source != null && name.equals(source))
149             throw new IllegalStateException("Found a cycle, " + name + "->" + source);
150         else if (source == null)
151             source = name;
152         
153         Element node = findPageNode(name);
154         if (node != null) {
155             
156             PageInfo page = parsePageNode(node);
157             
158             Node pageCategoryNode = document.selectSingleNode("/sitemap/category//page[@name='" + name + "']");
159             if (pageCategoryNode != null) {
160                 parseNodeTree(page, pageCategoryNode);
161                 //Because it will have been re-parsed
162                 return (PageInfo)cachedPageMap.get(page.getName());
163             }
164             
165             return page;
166         }
167         
168         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         Element element = (Element)document.selectSingleNode("/sitemap/page[@name='" + page + "']");
180         if (element == null && compact)
181         {
182             element = (Element)document.selectSingleNode("/sitemap/category//page[@name='" + page + "']");
183         }
184         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         if (node == null || !node.getName().equals("page"))
197             throw new IllegalArgumentException("Node was null or not a page node.");
198         
199         String name = node.attributeValue("name");
200         String desc = node.attributeValue("desc");
201         String perm = node.attributeValue("permission");
202         String listItem = node.attributeValue("navigable");
203         //Whether or not to list this page if a child.
204         boolean list = true;
205         if (listItem != null && !listItem.trim().equals("")) 
206             list = Boolean.valueOf(listItem).booleanValue();
207         
208         PageInfo page = new PageInfoImpl(name, desc, perm, new ArrayList(), list);
209         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         Node parent = pageNode;
223         while (parent.getParent() != null 
224                 && parent.getParent().getName().equals("page")) {
225             parent = parent.getParent();
226         }
227         
228         log.debug("Parsing tree for page <" + page.getName() + ">");
229         //We can now parse all children of parent
230         parsePageNodes(parent, page);
231     }
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         PageInfo parentInfo = parsePageNode(findPageNode(((Element)parent).attributeValue("name")));
244         cachedPageMap.put(parentInfo.getName(), parentInfo);
245         
246         log.debug("Parent page name <" + parent.valueOf("@name") + ">");
247         Iterator it = parent.selectNodes("*").iterator();
248         while (it.hasNext()) {
249             Node child = (Node)it.next();
250             PageInfo childInfo = parsePageNodes(child, source);
251             
252             childInfo.setParent(parentInfo);
253             parentInfo.addChild(childInfo);
254         }
255         
256         return parentInfo;
257     }
258     
259     /**
260      * Get category listing.
261      * @return The category list
262      */
263     public synchronized List getCategories() {
264         if (document == null) throw new IllegalStateException("No document set, call setResource and initialize.");
265         if (cachedCategoryList == null) {
266             List nodes = document.selectNodes("/sitemap/category[@name]");
267             cachedCategoryList = new ArrayList(nodes.size());
268             for(int i = 0, size = nodes.size(); i < size; i++) {
269                 String category = ((Node)nodes.get(i)).valueOf("@name");
270                 cachedCategoryList.add(category);
271             }
272         }
273         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         CategoryInfo ci = (CategoryInfo)cachedCategoryInfo.get(name);
284         if (ci != null) {
285             return ci;
286         }
287         
288         if (document == null) throw new IllegalStateException("No document set, call setResource and initialize.");
289 
290         String imgName = null;
291         String inactiveImgName = null;
292         
293         Element catnode = (Element)document.selectSingleNode("/sitemap/category[@name='" + name + "']");
294         if (catnode != null) {
295             imgName = catnode.attributeValue("image-active");
296             inactiveImgName = catnode.attributeValue("image-inactive");
297         }
298         
299         //Node node = document.selectSingleNode("/sitemap/category[@name='" + name + "']");
300         List nodes = document.selectNodes("/sitemap/category[@name='" + name + "']/page");
301         if (nodes != null) {
302             List pageNames = new ArrayList(nodes.size());
303             for(int i = 0; i < nodes.size(); i++) {
304                 Node node = (Node)nodes.get(i);
305                 String pageName = node.valueOf("@name");
306                 pageNames.add(pageName);
307             }
308             ci = new CategoryInfoImpl(name, pageNames, imgName, inactiveImgName);
309             cachedCategoryInfo.put(name, ci);
310             return ci;
311         }
312         ci = new CategoryInfoImpl(name, null);
313         cachedCategoryInfo.put(name, ci);
314         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         if (pageInfo != null && pageInfo.getParent() != null) return getOrigin(pageInfo.getParent());
324         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         PageInfo pageInfo = getPageInfo(pageName);
334         if (pageInfo != null) {
335             PageInfo topMostPage = getOrigin(pageInfo);
336             String topPageName = topMostPage.getName();
337 
338             Node node = document.selectSingleNode("/sitemap/category/page[@name='" + topPageName + "']");
339             if (node != null) {
340                 String categoryName = node.getParent().valueOf("@name");
341                 return getCategoryInfo(categoryName);
342             }
343         }
344         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         CategoryInfo ci = getCategoryFromPage(pageName);
355         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         CategoryInfo ci = getCategoryInfo(category);
365         if (ci == null) return null;
366         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         CategoryInfo ci = getCategoryFromPage(pageName);
376         if (ci == null) return Arrays.asList(new Object[0]);
377         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         PageInfo pi = getDefaultPage(category);
387         if (pi == null) return null;
388         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         List list = new LinkedList();
398         PageInfo pageInfo = getPageInfo(pageName);
399         if (pageInfo == null) return Arrays.asList(new Object[0]);
400         loadBreadCrumbs(pageInfo, list);
401         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         PageInfo parent = pageInfo.getParent();
411         if (parent == null || parent.getName().equals(""))
412             return;
413 
414         breadCrumbs.add(0, parent.getName());
415         loadBreadCrumbs(parent, breadCrumbs);
416     }
417     
418     public boolean getCompact()
419     {
420         return compact;
421     }
422 
423 }