org.jivesoftware.admin.AdminConsole Maven / Gradle / Ivy
/*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.admin;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A model for admin tab and sidebar info. This class loads in XML definitions of the
* data and produces an in-memory model.
*
* This class loads its data from the admin-sidebar.xml file which is assumed
* to be in the main application jar file. In addition, it will load files from
* META-INF/admin-sidebar.xml if they're found. This allows developers to
* extend the functionality of the admin console to provide more options. See the main
* admin-sidebar.xml file for documentation of its format.
*/
public class AdminConsole {
private static final Logger Log = LoggerFactory.getLogger(AdminConsole.class);
private static Element coreModel;
private static Map overrideModels;
private static Element generatedModel;
static {
overrideModels = new LinkedHashMap<>();
load();
}
/** Not instantiatable */
private AdminConsole() {
}
/**
* Adds XML stream to the tabs/sidebar model.
*
* @param name the name.
* @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 addModel(String name, InputStream in) throws Exception {
SAXReader saxReader = new SAXReader();
Document doc = saxReader.read(in);
addModel(name, (Element)doc.selectSingleNode("/adminconsole"));
}
/**
* Adds an <adminconsole> Element to the tabs/sidebar model.
*
* @param name the name.
* @param element the Element
* @throws Exception if an error occurs.
*/
public static synchronized void addModel(String name, Element element) throws Exception {
overrideModels.put(name, element);
rebuildModel();
}
/**
* Removes an <adminconsole> Element from the tabs/sidebar model.
*
* @param name the name.
*/
public static synchronized void removeModel(String name) {
overrideModels.remove(name);
rebuildModel();
}
/**
* Returns the name of the application.
*
* @return the name of the application.
*/
public static synchronized String getAppName() {
Element appName = (Element)generatedModel.selectSingleNode("//adminconsole/global/appname");
if (appName != null) {
String pluginName = appName.attributeValue("plugin");
return getAdminText(appName.getText(), pluginName);
}
else {
return null;
}
}
/**
* Returns the URL of the main logo image for the admin console.
*
* @return the logo image.
*/
public static synchronized String getLogoImage() {
Element globalLogoImage = (Element)generatedModel.selectSingleNode(
"//adminconsole/global/logo-image");
if (globalLogoImage != null) {
String pluginName = globalLogoImage.attributeValue("plugin");
return getAdminText(globalLogoImage.getText(), pluginName);
}
else {
return null;
}
}
/**
* Returns the URL of the login image for the admin console.
*
* @return the login image.
*/
public static synchronized String getLoginLogoImage() {
Element globalLoginLogoImage = (Element)generatedModel.selectSingleNode(
"//adminconsole/global/login-image");
if (globalLoginLogoImage != null) {
String pluginName = globalLoginLogoImage.attributeValue("plugin");
return getAdminText(globalLoginLogoImage.getText(), pluginName);
}
else {
return null;
}
}
/**
* Returns the version string displayed in the admin console.
*
* @return the version string.
*/
public static synchronized String getVersionString() {
Element globalVersion = (Element)generatedModel.selectSingleNode(
"//adminconsole/global/version");
if (globalVersion != null) {
String pluginName = globalVersion.attributeValue("plugin");
return getAdminText(globalVersion.getText(), pluginName);
}
else {
// Default to the Openfire version if none has been provided via XML.
XMPPServer xmppServer = XMPPServer.getInstance();
return xmppServer.getServerInfo().getVersion().getVersionString();
}
}
/**
* Returns the model. The model should be considered read-only.
*
* @return the model.
*/
public static synchronized Element getModel() {
return generatedModel;
}
/**
* Convenience method to select an element from the model by its ID. If an
* element with a matching ID is not found, null will be returned.
*
* @param id the ID.
* @return the element.
*/
public static synchronized Element getElemnetByID(String id) {
return (Element)generatedModel.selectSingleNode("//*[@id='" + id + "']");
}
/**
* Returns a text element for the admin console, applying the appropriate locale.
* Internationalization logic will only be applied if the String is specially encoded
* in the format "${key.name}". If it is, the String is pulled from the resource bundle.
* If the pluginName is not null, the plugin's resource bundle will be used
* to look up the key.
*
* @param string the String.
* @param pluginName the name of the plugin that the i18n String can be found in,
* or null if the standard Openfire resource bundle should be used.
* @return the string, or if the string is encoded as an i18n key, the value from
* the appropriate resource bundle.
*/
public static String getAdminText(String string, String pluginName) {
if (string == null) {
return null;
}
// Look for the key symbol:
if (string.indexOf("${") == 0 && string.indexOf("}") == string.length()-1) {
return LocaleUtils.getLocalizedString(string.substring(2, string.length()-1), pluginName);
}
return string;
}
private static void load() {
// Load the core model as the admin-sidebar.xml file from the classpath.
InputStream in = ClassUtils.getResourceAsStream("/admin-sidebar.xml");
if (in == null) {
Log.error("Failed to load admin-sidebar.xml file from Openfire classes - admin "
+ "console will not work correctly.");
return;
}
try {
SAXReader saxReader = new SAXReader();
Document doc = saxReader.read(in);
coreModel = (Element)doc.selectSingleNode("/adminconsole");
}
catch (Exception e) {
Log.error("Failure when parsing main admin-sidebar.xml file", e);
}
try {
in.close();
}
catch (Exception ignored) {
// Ignore.
}
// Load other admin-sidebar.xml files from the classpath
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
URL url = null;
try {
if (classLoader != null) {
Enumeration e = classLoader.getResources("/META-INF/admin-sidebar.xml");
while (e.hasMoreElements()) {
url = (URL) e.nextElement();
try {
in = url.openStream();
addModel("admin", in);
}
finally {
try {
if (in != null) {
in.close();
}
}
catch (Exception ignored) {
// Ignore.
}
}
}
}
}
catch (Exception e) {
String msg = "Failed to load admin-sidebar.xml";
if (url != null) {
msg += " from resource: " + url.toString();
}
Log.warn(msg, e);
}
}
rebuildModel();
}
/**
* Rebuilds the generated model.
*/
private static synchronized void rebuildModel() {
Document doc = DocumentFactory.getInstance().createDocument();
generatedModel = coreModel.createCopy();
doc.add(generatedModel);
// Add in all overrides.
for (Element element : overrideModels.values()) {
// See if global settings are overriden.
Element appName = (Element)element.selectSingleNode("//adminconsole/global/appname");
if (appName != null) {
Element existingAppName = (Element)generatedModel.selectSingleNode(
"//adminconsole/global/appname");
existingAppName.setText(appName.getText());
if (appName.attributeValue("plugin") != null) {
existingAppName.addAttribute("plugin", appName.attributeValue("plugin"));
}
}
Element appLogoImage = (Element)element.selectSingleNode("//adminconsole/global/logo-image");
if (appLogoImage != null) {
Element existingLogoImage = (Element)generatedModel.selectSingleNode(
"//adminconsole/global/logo-image");
existingLogoImage.setText(appLogoImage.getText());
if (appLogoImage.attributeValue("plugin") != null) {
existingLogoImage.addAttribute("plugin", appLogoImage.attributeValue("plugin"));
}
}
Element appLoginImage = (Element)element.selectSingleNode("//adminconsole/global/login-image");
if (appLoginImage != null) {
Element existingLoginImage = (Element)generatedModel.selectSingleNode(
"//adminconsole/global/login-image");
existingLoginImage.setText(appLoginImage.getText());
if (appLoginImage.attributeValue("plugin") != null) {
existingLoginImage.addAttribute("plugin", appLoginImage.attributeValue("plugin"));
}
}
Element appVersion = (Element)element.selectSingleNode("//adminconsole/global/version");
if (appVersion != null) {
Element existingVersion = (Element)generatedModel.selectSingleNode(
"//adminconsole/global/version");
if (existingVersion != null) {
existingVersion.setText(appVersion.getText());
if (appVersion.attributeValue("plugin") != null) {
existingVersion.addAttribute("plugin", appVersion.attributeValue("plugin"));
}
}
else {
((Element)generatedModel.selectSingleNode(
"//adminconsole/global")).add(appVersion.createCopy());
}
}
// Tabs
for (Object o : element.selectNodes("//tab")) {
Element tab = (Element) o;
String id = tab.attributeValue("id");
Element existingTab = getElemnetByID(id);
// Simple case, there is no existing tab with the same id.
if (existingTab == null) {
// Make sure that the URL on the tab is set. If not, default to the
// url of the first item.
if (tab.attributeValue("url") == null) {
Element firstItem = (Element) tab.selectSingleNode(
"//item[@url]");
if (firstItem != null) {
tab.addAttribute("url", firstItem.attributeValue("url"));
}
}
generatedModel.add(tab.createCopy());
}
// More complex case -- a tab with the same id already exists.
// In this case, we have to overrite only the difference between
// the two elements.
else {
overrideTab(existingTab, tab);
}
}
}
}
private static void overrideTab(Element tab, Element overrideTab) {
// Override name, url, description.
if (overrideTab.attributeValue("name") != null) {
tab.addAttribute("name", overrideTab.attributeValue("name"));
}
if (overrideTab.attributeValue("url") != null) {
tab.addAttribute("url", overrideTab.attributeValue("url"));
}
if (overrideTab.attributeValue("description") != null) {
tab.addAttribute("description", overrideTab.attributeValue("description"));
}
if (overrideTab.attributeValue("plugin") != null) {
tab.addAttribute("plugin", overrideTab.attributeValue("plugin"));
}
// Override sidebar items.
for (Iterator i=overrideTab.elementIterator(); i.hasNext(); ) {
Element sidebar = (Element)i.next();
String id = sidebar.attributeValue("id");
Element existingSidebar = getElemnetByID(id);
// Simple case, there is no existing sidebar with the same id.
if (existingSidebar == null) {
tab.add(sidebar.createCopy());
}
// More complex case -- a sidebar with the same id already exists.
// In this case, we have to overrite only the difference between
// the two elements.
else {
overrideSidebar(existingSidebar, sidebar);
}
}
}
private static void overrideSidebar(Element sidebar, Element overrideSidebar) {
// Override name.
if (overrideSidebar.attributeValue("name") != null) {
sidebar.addAttribute("name", overrideSidebar.attributeValue("name"));
}
if (overrideSidebar.attributeValue("plugin") != null) {
sidebar.addAttribute("plugin", overrideSidebar.attributeValue("plugin"));
}
// Override entries.
for (Iterator i=overrideSidebar.elementIterator(); i.hasNext(); ) {
Element entry = (Element)i.next();
String id = entry.attributeValue("id");
Element existingEntry = getElemnetByID(id);
// Simple case, there is no existing sidebar with the same id.
if (existingEntry == null) {
sidebar.add(entry.createCopy());
}
// More complex case -- an entry with the same id already exists.
// In this case, we have to overrite only the difference between
// the two elements.
else {
overrideEntry(existingEntry, entry);
}
}
}
private static void overrideEntry(Element entry, Element overrideEntry) {
// Override name.
if (overrideEntry.attributeValue("name") != null) {
entry.addAttribute("name", overrideEntry.attributeValue("name"));
}
if (overrideEntry.attributeValue("url") != null) {
entry.addAttribute("url", overrideEntry.attributeValue("url"));
}
if (overrideEntry.attributeValue("description") != null) {
entry.addAttribute("description", overrideEntry.attributeValue("description"));
}
if (overrideEntry.attributeValue("plugin") != null) {
entry.addAttribute("plugin", overrideEntry.attributeValue("plugin"));
}
// Override any sidebars contained in the entry.
for (Iterator i=overrideEntry.elementIterator(); i.hasNext(); ) {
Element sidebar = (Element)i.next();
String id = sidebar.attributeValue("id");
Element existingSidebar = getElemnetByID(id);
// Simple case, there is no existing sidebar with the same id.
if (existingSidebar == null) {
entry.add(sidebar.createCopy());
}
// More complex case -- a sidebar with the same id already exists.
// In this case, we have to overrite only the difference between
// the two elements.
else {
overrideSidebar(existingSidebar, sidebar);
}
}
}
/**
* Returns an array of class loaders to load resources from.
*
* @return 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;
}
}