/** * $RCSfile$ * $Revision$ * $Date$ * * Copyright (C) 2004 Jive Software. All rights reserved. * * This software is published under the terms of the GNU Public License (GPL), * a copy of which is included in this distribution. */ package org.jivesoftware.admin; import org.jivesoftware.util.ClassUtils; import org.jivesoftware.util.Log; import org.jivesoftware.util.XMLProperties; import java.util.*; import java.io.InputStream; import java.net.URL; /** * <p>A model for admin tab and sidebar info. This class loads in xml definitions of the data and * produces an in-memory model. There is an internal class, {@link Item} which is the main part * of the model. Items hold info like name, id, url and description as well as an arbritrary number * of sub items. Based on this we can make a tree model of the data.</p> * * <p>This class loads its data from the <tt>admin-sidebar.xml</tt> file which is assumed to be in * the main application jar file. In addition, it will load files from * <tt>META-INF/admin-sidebar.xml</tt> if they're found. This allows developers to extend the * functionality of the admin console to provide more options. See the main * <tt>admin-sidebar.xml</tt> file for documentation of its format.</p> * * <p>Note: IDs in the XML file must be unique because an internal mapping is kept of IDs to * nodes.</p> */ public class AdminConsole { private static Collection items; private static Map idMap; // map of item ids -> item objs private static String appName; private static String logoImage; static { items = new ArrayList(); idMap = new HashMap(); load(); } /** Not instantiatable */ private AdminConsole() { } /** * Adds XML stream to the tabs/sidebar model. * * @param in the XML input stream. * @throws Exception if an error occurs when parsing the XML or adding it to the model. */ public static void addXMLSource(InputStream in) throws Exception { addToModel(in); } /** * Returns the name of the application. */ public static String getAppName() { return appName; } /** * Returns the URL (relative or absolute) of the main logo image for the admin console. * @return */ public static String getLogoImage() { return logoImage; } /** * Returns all items starting from the root. Getting the iterator from this collection returns * all root items (should be used as tabs in the admin tool). * * @return a collection of all items - the root items are returned by calling the * <tt>iterator()</tt> method. */ public static Collection getItems() { return items; } /** * Returns an item given its ID or <tt>null</tt> if it can't be found. * * @param id the ID of the item. * @return an item given its ID or <tt>null</tt> if it can't be found. */ public static Item getItem(String id) { return (Item)idMap.get(id); } /** * Returns the root item given a child item. In other words, a lookup is done on the ID for * the corresponding item - that item is assumed to be a leaf and this method returns the * root ancestor of it. * * @param id the ID of the child item. * @return the root ancestor of the specified child item. */ public static Item getRootByChildID(String id) { if (id == null) { return null; } Item child = getItem(id); Item root = null; if (child != null) { Item parent = child.getParent(); root = parent; while (parent != null) { parent = parent.getParent(); if (parent != null) { root = parent; } } } return root; } /** * Returns <tt>true</tt> if the given item is a sub-menu item. * * @param item the item to test. * @return <tt>true</tt> if the given item is a sub-menu item, <tt>false</tt> otherwise. */ public static boolean isSubMenItem(Item item) { int parentCount = 0; Item parent = item.getParent(); while (parent != null) { parentCount++; parent = parent.getParent(); } return parentCount >= 3; } /** * Returns the ID of the page ID associated with this sub page ID. * @param subPageID the subPageID to use to look up the page ID. * @return the associated pageID or <tt>null</tt> if it can't be found. */ public static String lookupPageID(String subPageID) { String pageID = null; Item item = getItem(subPageID); if (item != null) { Item parent = item.getParent(); while (parent.getId() == null) { parent = parent.getParent(); } pageID = parent.getId(); } return pageID; } /** * A simple class to model an item. Each item has attributes used by the admin console to * display it like ID, name, URL and description. Also, from each item you can get its parent * (because an Item goes in a tree structure) and any children items it has. */ public static class Item { private String id; private String name; private String description; private String url; private boolean active; private Collection items; private Item parent; /** * Creates a new item given its main attributes. */ public Item(String id, String name, String description, String url) { this.id = id; this.name = name; this.description = description; this.url = url; init(); } /** * Creates a new item given its main attributes and the parent item (this helps set up * the tree structure). */ public Item(String id, String name, String description, String url, Item parent) { this.id = id; this.name = name; this.description = description; this.url = url; this.parent = parent; init(); } private void init() { items = Collections.synchronizedList(new ArrayList()); // items = Collections.synchronizedList(new ArrayList(){ // public boolean add(Object obj) { // Item item = (Item)obj; // if (contains(item)) { // Item i = (Item)get(indexOf(item)); // i.setName(item.getName()); // i.setDescription(item.getDescription()); // i.setUrl(item.getUrl()); // return true; // } // else { // super.add(item); // return true; // } // }; // }); if (id != null && !"".equals(id.trim())) { idMap.put(id, this); } } /** * Returns the ID of the item. */ public String getId() { return id; } /** * Returns the name of the item - this is the display name. */ public String getName() { return name; } /** * Sets the name. */ void setName(String name) { this.name = name; } /** * Returns the description of the item. */ public String getDescription() { return description; } /** * Sets the description. */ void setDescription(String description) { this.description = description; } /** * Returns the URL for this item. */ public String getUrl() { return url; } /** * Sets the URL for this item. */ public void setUrl(String url) { this.url = url; } /** * Returns true if this items is active - in the admin console this would mean it's selected. */ public boolean isActive() { return active; } /** * Sets the item as active - in the admin console this would mean it's selected. */ public void setActive(boolean active) { this.active = active; } /** * Returns the parent item or <tt>null</tt> if this is a root item. */ public Item getParent() { return parent; } /** * Sets the parent item. */ public void setParent(Item parent) { this.parent = parent; } /** * Returns the items as a collection. Use the Collection API to get/set/remove items. */ public Collection getItems() { return items; } public boolean equals(Object o) { if (this == o) { return true; } if (o == null) { return false; } if (!(o instanceof Item)) { return false; } Item i = (Item) o; if (!id.equals(i.id)) { return false; } return true; } /** * Returns the ID of the item. */ public String toString() { return id; } } private static void load() { // Load the admin-sidebar.xml file from the jiveforums.jar file: InputStream in = ClassUtils.getResourceAsStream("/admin-sidebar.xml"); if (in == null) { Log.error("Failed to load admin-sidebar.xml file from Jive Messenger classes - admin " + "console will not work correctly."); return; } try { addToModel(in); } catch (Exception e) { Log.error("Failure when parsing main admin-sidebar.xml file", e); } try { in.close(); } catch (Exception ignored) {} // Load other admin-sidebar.xml files from the classpath ClassLoader[] classLoaders = getClassLoaders(); for (int i=0; i<classLoaders.length; i++) { URL url = null; try { if (classLoaders[i] != null) { Enumeration e = classLoaders[i].getResources("/META-INF/admin-sidebar.xml"); while (e.hasMoreElements()) { url = (URL)e.nextElement(); in = url.openStream(); addToModel(in); try { in.close(); } catch (Exception ignored) {} } } } catch (Exception e) { String msg = "Failed to load admin-sidebar.xml"; if (url != null) { msg += " from resource: " + url.toString(); } Log.warn(msg, e); } } } private static void addToModel(InputStream in) throws Exception { // Build an XMLPropertiesTest object from the input stream: XMLProperties xml = new XMLProperties(in); // Set any global properties if (xml.getProperty("global.appname") != null) { appName = xml.getProperty("global.appname"); } if (xml.getProperty("global.logo-image") != null) { logoImage = xml.getProperty("global.logo-image"); } // Get all children of the 'tabs' element - should be 'tab' items: String[] tabs = xml.getChildrenProperties("tabs"); for (int i=0; i<tabs.length; i++) { String propName = "tabs." + tabs[i]; // Create a new top level item with data from the xml file: String id = xml.getProperty(propName + ".id"); String name = xml.getProperty(propName + ".name"); String description = xml.getProperty(propName + ".description"); Item item = new Item(id, name, description, null); // Add that item to the item collection getItems().add(item); // Delve down into this item's sidebars - build up a model of these then add into // the item above. String[] sidebars = xml.getChildrenProperties(propName + ".sidebars"); for (int j=0; j<sidebars.length; j++) { String sidebarName = propName + ".sidebars." + sidebars[j]; name = xml.getProperty(sidebarName + ".name"); // Create a new item, set its name Item subItem = new Item(null, name, null, null); // Now iterate down another level, get the items for this item - this will be the // specific links on the sidebar String[] subitems = xml.getChildrenProperties(sidebarName + ".items"); for (int k=0; k<subitems.length; k++) { String subitemName = sidebarName + ".items." + subitems[k]; // Get the id, name, descr and url attributes: String subID = xml.getProperty(subitemName + ".id"); String subName = xml.getProperty(subitemName + ".name"); String subDescr = xml.getProperty(subitemName + ".description"); String subURL = xml.getProperty(subitemName + ".url"); // Build an item with this, add it to the subItem we made above Item kItem = new Item(subID, subName, subDescr, subURL, subItem); subItem.getItems().add(kItem); // Build any sub-sub menus: subAddtoModel(subitemName, xml, kItem); // If this is the first item, set the root menu item's URL as this URL: if (j==0 && k == 0) { item.setUrl(subURL); } } // Add the subItem to the item created above subItem.setParent(item); item.getItems().add(subItem); } } } private static void subAddtoModel(String path, XMLProperties props, Item parent) { String propName = path + ".subsidebars"; String[] subsidebars = props.getChildrenProperties(propName); for (int i=0; i<subsidebars.length; i++) { String child = propName + "." + subsidebars[i]; String[] subsidebarchildren = props.getChildrenProperties(child); for (int j=0; j<subsidebarchildren.length; j++) { String root = propName + ".subsidebar" + i + "." + subsidebarchildren[j]; String name = props.getProperty(root + ".name"); Item header = new Item(null, name, null, null, parent); // Get the children of this String subPath = root + ".items"; String[] allitems = props.getChildrenProperties(subPath); for (int k=0; k<allitems.length; k++) { String subName = subPath + "." + allitems[k]; String itemID = props.getProperty(subName + ".id"); String itemName = props.getProperty(subName + ".name"); String itemDescr = props.getProperty(subName + ".description"); String itemURL = props.getProperty(subName + ".url"); Item si = new Item(itemID, itemName, itemDescr, itemURL, header); header.getItems().add(si); } parent.getItems().add(header); } } } /** * Returns an array of class loaders to load resources from. */ private static ClassLoader[] getClassLoaders() { ClassLoader[] classLoaders = new ClassLoader[3]; classLoaders[0] = AdminConsole.class.getClass().getClassLoader(); classLoaders[1] = Thread.currentThread().getContextClassLoader(); classLoaders[2] = ClassLoader.getSystemClassLoader(); return classLoaders; } private static void clear() { items = new ArrayList(); idMap = new HashMap(); load(); } }