001: /**
002: * $RCSfile$
003: * $Revision: 7742 $
004: * $Date: 2007-03-27 17:44:27 -0700 (Tue, 27 Mar 2007) $
005: *
006: * Copyright (C) 2004 Jive Software. All rights reserved.
007: *
008: * This software is published under the terms of the GNU Public License (GPL),
009: * a copy of which is included in this distribution.
010: */package org.jivesoftware.admin;
011:
012: import org.jivesoftware.util.*;
013: import org.jivesoftware.openfire.XMPPServer;
014: import org.jivesoftware.openfire.container.PluginManager;
015: import org.dom4j.Document;
016: import org.dom4j.Element;
017: import org.dom4j.DocumentFactory;
018: import org.dom4j.io.SAXReader;
019:
020: import java.util.*;
021: import java.io.InputStream;
022: import java.net.URL;
023:
024: /**
025: * A model for admin tab and sidebar info. This class loads in XML definitions of the
026: * data and produces an in-memory model.<p>
027: *
028: * This class loads its data from the <tt>admin-sidebar.xml</tt> file which is assumed
029: * to be in the main application jar file. In addition, it will load files from
030: * <tt>META-INF/admin-sidebar.xml</tt> if they're found. This allows developers to
031: * extend the functionality of the admin console to provide more options. See the main
032: * <tt>admin-sidebar.xml</tt> file for documentation of its format.
033: */
034: public class AdminConsole {
035:
036: private static Element coreModel;
037: private static Map<String, Element> overrideModels;
038: private static Element generatedModel;
039:
040: static {
041: overrideModels = new LinkedHashMap<String, Element>();
042: load();
043:
044: // The admin console model has special logic to include an informational
045: // Enterprise tab when the Enterprise plugin is not installed. A property
046: // controls whether to show that tab. Listen for the property value changing
047: // and rebuild the model when that happens.
048: PropertyEventDispatcher
049: .addListener(new PropertyEventListener() {
050:
051: public void propertySet(String property, Map params) {
052: if ("enterpriseInfoEnabled".equals(property)) {
053: rebuildModel();
054: }
055: }
056:
057: public void propertyDeleted(String property,
058: Map params) {
059: if ("enterpriseInfoEnabled".equals(property)) {
060: rebuildModel();
061: }
062: }
063:
064: public void xmlPropertySet(String property,
065: Map params) {
066: // Do nothing
067: }
068:
069: public void xmlPropertyDeleted(String property,
070: Map params) {
071: // Do nothing
072: }
073: });
074: }
075:
076: /** Not instantiatable */
077: private AdminConsole() {
078: }
079:
080: /**
081: * Adds XML stream to the tabs/sidebar model.
082: *
083: * @param name the name.
084: * @param in the XML input stream.
085: * @throws Exception if an error occurs when parsing the XML or adding it to the model.
086: */
087: public static void addModel(String name, InputStream in)
088: throws Exception {
089: SAXReader saxReader = new SAXReader();
090: Document doc = saxReader.read(in);
091: addModel(name, (Element) doc.selectSingleNode("/adminconsole"));
092: }
093:
094: /**
095: * Adds an <adminconsole> Element to the tabs/sidebar model.
096: *
097: * @param name the name.
098: * @param element the Element
099: * @throws Exception if an error occurs.
100: */
101: public static void addModel(String name, Element element)
102: throws Exception {
103: overrideModels.put(name, element);
104: rebuildModel();
105: }
106:
107: /**
108: * Removes an <adminconsole> Element from the tabs/sidebar model.
109: *
110: * @param name the name.
111: */
112: public static void removeModel(String name) {
113: overrideModels.remove(name);
114: rebuildModel();
115: }
116:
117: /**
118: * Returns the name of the application.
119: *
120: * @return the name of the application.
121: */
122: public static synchronized String getAppName() {
123: Element appName = (Element) generatedModel
124: .selectSingleNode("//adminconsole/global/appname");
125: if (appName != null) {
126: String pluginName = appName.attributeValue("plugin");
127: return getAdminText(appName.getText(), pluginName);
128: } else {
129: return null;
130: }
131: }
132:
133: /**
134: * Returns the URL of the main logo image for the admin console.
135: *
136: * @return the logo image.
137: */
138: public static synchronized String getLogoImage() {
139: Element globalLogoImage = (Element) generatedModel
140: .selectSingleNode("//adminconsole/global/logo-image");
141: if (globalLogoImage != null) {
142: String pluginName = globalLogoImage
143: .attributeValue("plugin");
144: return getAdminText(globalLogoImage.getText(), pluginName);
145: } else {
146: return null;
147: }
148: }
149:
150: /**
151: * Returns the URL of the login image for the admin console.
152: *
153: * @return the login image.
154: */
155: public static synchronized String getLoginLogoImage() {
156: Element globalLoginLogoImage = (Element) generatedModel
157: .selectSingleNode("//adminconsole/global/login-image");
158: if (globalLoginLogoImage != null) {
159: String pluginName = globalLoginLogoImage
160: .attributeValue("plugin");
161: return getAdminText(globalLoginLogoImage.getText(),
162: pluginName);
163: } else {
164: return null;
165: }
166: }
167:
168: /**
169: * Returns the version string displayed in the admin console.
170: *
171: * @return the version string.
172: */
173: public static synchronized String getVersionString() {
174: Element globalVersion = (Element) generatedModel
175: .selectSingleNode("//adminconsole/global/version");
176: if (globalVersion != null) {
177: String pluginName = globalVersion.attributeValue("plugin");
178: return getAdminText(globalVersion.getText(), pluginName);
179: } else {
180: // Default to the Openfire version if none has been provided via XML.
181: XMPPServer xmppServer = XMPPServer.getInstance();
182: return xmppServer.getServerInfo().getVersion()
183: .getVersionString();
184: }
185: }
186:
187: /**
188: * Returns the model. The model should be considered read-only.
189: *
190: * @return the model.
191: */
192: public static synchronized Element getModel() {
193: return generatedModel;
194: }
195:
196: /**
197: * Convenience method to select an element from the model by its ID. If an
198: * element with a matching ID is not found, <tt>null</tt> will be returned.
199: *
200: * @param id the ID.
201: * @return the element.
202: */
203: public static synchronized Element getElemnetByID(String id) {
204: return (Element) generatedModel.selectSingleNode("//*[@id='"
205: + id + "']");
206: }
207:
208: /**
209: * Returns a text element for the admin console, applying the appropriate locale.
210: * Internationalization logic will only be applied if the String is specially encoded
211: * in the format "${key.name}". If it is, the String is pulled from the resource bundle.
212: * If the pluginName is not <tt>null</tt>, the plugin's resource bundle will be used
213: * to look up the key.
214: *
215: * @param string the String.
216: * @param pluginName the name of the plugin that the i18n String can be found in,
217: * or <tt>null</tt> if the standard Openfire resource bundle should be used.
218: * @return the string, or if the string is encoded as an i18n key, the value from
219: * the appropriate resource bundle.
220: */
221: public static String getAdminText(String string, String pluginName) {
222: if (string == null) {
223: return null;
224: }
225: // Look for the key symbol:
226: if (string.indexOf("${") == 0
227: && string.indexOf("}") == string.length() - 1) {
228: return LocaleUtils.getLocalizedString(string.substring(2,
229: string.length() - 1), pluginName);
230: }
231: return string;
232: }
233:
234: private static void load() {
235: // Load the core model as the admin-sidebar.xml file from the classpath.
236: InputStream in = ClassUtils
237: .getResourceAsStream("/admin-sidebar.xml");
238: if (in == null) {
239: Log
240: .error("Failed to load admin-sidebar.xml file from Openfire classes - admin "
241: + "console will not work correctly.");
242: return;
243: }
244: try {
245: SAXReader saxReader = new SAXReader();
246: Document doc = saxReader.read(in);
247: coreModel = (Element) doc.selectSingleNode("/adminconsole");
248: } catch (Exception e) {
249: Log.error(
250: "Failure when parsing main admin-sidebar.xml file",
251: e);
252: }
253: try {
254: in.close();
255: } catch (Exception ignored) {
256: // Ignore.
257: }
258:
259: // Load other admin-sidebar.xml files from the classpath
260: ClassLoader[] classLoaders = getClassLoaders();
261: for (int i = 0; i < classLoaders.length; i++) {
262: URL url = null;
263: try {
264: if (classLoaders[i] != null) {
265: Enumeration e = classLoaders[i]
266: .getResources("/META-INF/admin-sidebar.xml");
267: while (e.hasMoreElements()) {
268: url = (URL) e.nextElement();
269: try {
270: in = url.openStream();
271: addModel("admin", in);
272: } finally {
273: try {
274: if (in != null) {
275: in.close();
276: }
277: } catch (Exception ignored) {
278: // Ignore.
279: }
280: }
281: }
282: }
283: } catch (Exception e) {
284: String msg = "Failed to load admin-sidebar.xml";
285: if (url != null) {
286: msg += " from resource: " + url.toString();
287: }
288: Log.warn(msg, e);
289: }
290: }
291: rebuildModel();
292: }
293:
294: /**
295: * Rebuilds the generated model.
296: */
297: private static synchronized void rebuildModel() {
298: Document doc = DocumentFactory.getInstance().createDocument();
299: generatedModel = coreModel.createCopy();
300: doc.add(generatedModel);
301:
302: // Add in all overrides.
303: for (Element element : overrideModels.values()) {
304: // See if global settings are overriden.
305: Element appName = (Element) element
306: .selectSingleNode("//adminconsole/global/appname");
307: if (appName != null) {
308: Element existingAppName = (Element) generatedModel
309: .selectSingleNode("//adminconsole/global/appname");
310: existingAppName.setText(appName.getText());
311: if (appName.attributeValue("plugin") != null) {
312: existingAppName.addAttribute("plugin", appName
313: .attributeValue("plugin"));
314: }
315: }
316: Element appLogoImage = (Element) element
317: .selectSingleNode("//adminconsole/global/logo-image");
318: if (appLogoImage != null) {
319: Element existingLogoImage = (Element) generatedModel
320: .selectSingleNode("//adminconsole/global/logo-image");
321: existingLogoImage.setText(appLogoImage.getText());
322: if (appLogoImage.attributeValue("plugin") != null) {
323: existingLogoImage.addAttribute("plugin",
324: appLogoImage.attributeValue("plugin"));
325: }
326: }
327: Element appLoginImage = (Element) element
328: .selectSingleNode("//adminconsole/global/login-image");
329: if (appLoginImage != null) {
330: Element existingLoginImage = (Element) generatedModel
331: .selectSingleNode("//adminconsole/global/login-image");
332: existingLoginImage.setText(appLoginImage.getText());
333: if (appLoginImage.attributeValue("plugin") != null) {
334: existingLoginImage.addAttribute("plugin",
335: appLoginImage.attributeValue("plugin"));
336: }
337: }
338: Element appVersion = (Element) element
339: .selectSingleNode("//adminconsole/global/version");
340: if (appVersion != null) {
341: Element existingVersion = (Element) generatedModel
342: .selectSingleNode("//adminconsole/global/version");
343: if (existingVersion != null) {
344: existingVersion.setText(appVersion.getText());
345: if (appVersion.attributeValue("plugin") != null) {
346: existingVersion.addAttribute("plugin",
347: appVersion.attributeValue("plugin"));
348: }
349: } else {
350: ((Element) generatedModel
351: .selectSingleNode("//adminconsole/global"))
352: .add(appVersion.createCopy());
353: }
354: }
355: // Tabs
356: for (Iterator i = element.selectNodes("//tab").iterator(); i
357: .hasNext();) {
358: Element tab = (Element) i.next();
359: String id = tab.attributeValue("id");
360: Element existingTab = getElemnetByID(id);
361: // Simple case, there is no existing tab with the same id.
362: if (existingTab == null) {
363: // Make sure that the URL on the tab is set. If not, default to the
364: // url of the first item.
365: if (tab.attributeValue("url") == null) {
366: Element firstItem = (Element) tab
367: .selectSingleNode("//item[@url]");
368: if (firstItem != null) {
369: tab.addAttribute("url", firstItem
370: .attributeValue("url"));
371: }
372: }
373: generatedModel.add(tab.createCopy());
374: }
375: // More complex case -- a tab with the same id already exists.
376: // In this case, we have to overrite only the difference between
377: // the two elements.
378: else {
379: overrideTab(existingTab, tab);
380: }
381: }
382: }
383:
384: // Special case: show an informational tab about Openfire Enterprise if Enterprise
385: // is not installed and if the user has not chosen to hide tab.
386: PluginManager pluginManager = XMPPServer.getInstance()
387: .getPluginManager();
388: boolean pluginExists = pluginManager != null
389: && pluginManager.isPluginDownloaded("enterprise.jar");
390: if (!pluginExists
391: && JiveGlobals.getBooleanProperty(
392: "enterpriseInfoEnabled", true)) {
393: Element enterprise = generatedModel.addElement("tab");
394: enterprise.addAttribute("id", "tab-enterprise");
395: enterprise.addAttribute("name", "Enterprise");
396: enterprise.addAttribute("url", "enterprise-info.jsp");
397: enterprise.addAttribute("description",
398: "Click for Enterprise information.");
399: Element sidebar = enterprise.addElement("sidebar");
400: sidebar.addAttribute("id", "sidebar-enterprise-info");
401: sidebar.addAttribute("name", "Openfire Enterprise");
402: Element item = sidebar.addElement("item");
403: item.addAttribute("id", "enterprise-info");
404: item.addAttribute("name", "Try Enterprise");
405: item.addAttribute("url", "enterprise-info.jsp");
406: item.addAttribute("description",
407: "Openfire Enterprise overview inforation");
408: }
409: }
410:
411: private static void overrideTab(Element tab, Element overrideTab) {
412: // Override name, url, description.
413: if (overrideTab.attributeValue("name") != null) {
414: tab
415: .addAttribute("name", overrideTab
416: .attributeValue("name"));
417: }
418: if (overrideTab.attributeValue("url") != null) {
419: tab.addAttribute("url", overrideTab.attributeValue("url"));
420: }
421: if (overrideTab.attributeValue("description") != null) {
422: tab.addAttribute("description", overrideTab
423: .attributeValue("description"));
424: }
425: if (overrideTab.attributeValue("plugin") != null) {
426: tab.addAttribute("plugin", overrideTab
427: .attributeValue("plugin"));
428: }
429: // Override sidebar items.
430: for (Iterator i = overrideTab.elementIterator(); i.hasNext();) {
431: Element sidebar = (Element) i.next();
432: String id = sidebar.attributeValue("id");
433: Element existingSidebar = getElemnetByID(id);
434: // Simple case, there is no existing sidebar with the same id.
435: if (existingSidebar == null) {
436: tab.add(sidebar.createCopy());
437: }
438: // More complex case -- a sidebar with the same id already exists.
439: // In this case, we have to overrite only the difference between
440: // the two elements.
441: else {
442: overrideSidebar(existingSidebar, sidebar);
443: }
444: }
445: }
446:
447: private static void overrideSidebar(Element sidebar,
448: Element overrideSidebar) {
449: // Override name.
450: if (overrideSidebar.attributeValue("name") != null) {
451: sidebar.addAttribute("name", overrideSidebar
452: .attributeValue("name"));
453: }
454: if (overrideSidebar.attributeValue("plugin") != null) {
455: sidebar.addAttribute("plugin", overrideSidebar
456: .attributeValue("plugin"));
457: }
458: // Override entries.
459: for (Iterator i = overrideSidebar.elementIterator(); i
460: .hasNext();) {
461: Element entry = (Element) i.next();
462: String id = entry.attributeValue("id");
463: Element existingEntry = getElemnetByID(id);
464: // Simple case, there is no existing sidebar with the same id.
465: if (existingEntry == null) {
466: sidebar.add(entry.createCopy());
467: }
468: // More complex case -- an entry with the same id already exists.
469: // In this case, we have to overrite only the difference between
470: // the two elements.
471: else {
472: overrideEntry(existingEntry, entry);
473: }
474: }
475: }
476:
477: private static void overrideEntry(Element entry,
478: Element overrideEntry) {
479: // Override name.
480: if (overrideEntry.attributeValue("name") != null) {
481: entry.addAttribute("name", overrideEntry
482: .attributeValue("name"));
483: }
484: if (overrideEntry.attributeValue("url") != null) {
485: entry.addAttribute("url", overrideEntry
486: .attributeValue("url"));
487: }
488: if (overrideEntry.attributeValue("description") != null) {
489: entry.addAttribute("description", overrideEntry
490: .attributeValue("description"));
491: }
492: if (overrideEntry.attributeValue("plugin") != null) {
493: entry.addAttribute("plugin", overrideEntry
494: .attributeValue("plugin"));
495: }
496: // Override any sidebars contained in the entry.
497: for (Iterator i = overrideEntry.elementIterator(); i.hasNext();) {
498: Element sidebar = (Element) i.next();
499: String id = sidebar.attributeValue("id");
500: Element existingSidebar = getElemnetByID(id);
501: // Simple case, there is no existing sidebar with the same id.
502: if (existingSidebar == null) {
503: entry.add(sidebar.createCopy());
504: }
505: // More complex case -- a sidebar with the same id already exists.
506: // In this case, we have to overrite only the difference between
507: // the two elements.
508: else {
509: overrideSidebar(existingSidebar, sidebar);
510: }
511: }
512: }
513:
514: /**
515: * Returns an array of class loaders to load resources from.
516: *
517: * @return an array of class loaders to load resources from.
518: */
519: private static ClassLoader[] getClassLoaders() {
520: ClassLoader[] classLoaders = new ClassLoader[3];
521: classLoaders[0] = AdminConsole.class.getClass()
522: .getClassLoader();
523: classLoaders[1] = Thread.currentThread()
524: .getContextClassLoader();
525: classLoaders[2] = ClassLoader.getSystemClassLoader();
526: return classLoaders;
527: }
528: }
|