org.jivesoftware.site.WildfireVersionChecker.java Source code

Java tutorial

Introduction

Here is the source code for org.jivesoftware.site.WildfireVersionChecker.java

Source

/**
 * $Revision$
 * $Date$
 *
 * Copyright (C) 1999-2006 Jive Software. All rights reserved.
 * This software is the proprietary information of Jive Software. Use is subject to license terms.
 */

package org.jivesoftware.site;

import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;

/**
 * WildfireVersionChecker provides two services 1) check for updates and 2) get list of
 * available plugins. The "check for updates" service checks if the installed version of
 * a Wildfire server or any of its plugins are old. The "Get list of available plugins"
 * service offers to Wildfire servers the list of plugins that have not been installed.
 *
 * @author Gaston Dombiak
 */
public class WildfireVersionChecker {

    protected static DocumentFactory docFactory = DocumentFactory.getInstance();
    //private static String WILDFIRE_PATH = "http://www.igniterealtime.org/downloads/download-landing.jsp?file=builds/wildfire/";
    private static String WILDFIRE_PATH = "http://www.igniterealtime.org/downloads/index.jsp";
    private static String WILDFIRE_LOG = "http://www.igniterealtime.org/builds/wildfire/docs/latest/changelog.html";
    private static String PLUGINS_PATH = "http://www.igniterealtime.org/projects/wildfire/plugins/";
    /**
     * Map that keeps the information specified in plugin.xml for each available plugin.
     * Key = filename, value = content of plugin.xml
     */
    private static Map<String, Document> pluginsInfo;
    /**
     * Keep track of the date of the last updated plugin.
     */
    private static long lastPluginDate;

    /**
     * Verifies if there is a newer version of the Wildfire server. The request is specified
     * in XML format and contains the installed server version information.  The answer is
     * also specified in XML format. If a new server version is available then the answer
     * will include information about the available update. Otherwise, an empty "version"
     * element is returned.
     *
     * @param request the XML sent by the remote wildfire installation that contains
     *        current installed server version.
     * @return an answer in XML format containing the items for which a new version is available.
     */
    public static String checkVersions(String request) {
        try {
            Element xmlRequest = new SAXReader().read(new StringReader(request)).getRootElement();
            Element xmlReply = docFactory.createDocument().addElement("version");
            // Check if there is a newer version of the wildfire server
            compareWildfireVersion(xmlRequest, xmlReply);
            return xmlReply.asXML();
        } catch (Exception e) {
            e.printStackTrace();
            // Return a dummy result that says that everything is up to date
            return "<version/>";
        }
    }

    /**
     * Returns the list of available (i.e. not installed) plugins. The answer will
     * include free and commercial plugins.
     *
     * @param pluginsPath the path where the .jar files of the plugins are located.
     * @param request the XML sent by the remote wildfire installation that contains
     *        current installed versions.
     * @return  the list of available (i.e. not installed) plugins.
     */
    public static String getAvailablePlugins(String pluginsPath, String request) {
        try {
            Element xmlRequest = null;
            if (request != null) {
                xmlRequest = new SAXReader().read(new StringReader(request)).getRootElement();
            }
            Element xmlReply = docFactory.createDocument().addElement("available");
            // Check if new plugins are available
            availablePlugins(pluginsPath, xmlRequest, xmlReply);
            return xmlReply.asXML();
        } catch (Exception e) {
            e.printStackTrace();
            // Return a dummy result that says that no more plugins are available
            return "<available/>";
        }
    }

    /**
     * Compares installed wildfire version against the latest release. If the installed version
     * is older than the latest release then include the url from where the latest release
     * can be downloaded and the version of the latest release.
     *
     * @param xmlRequest original XML request sent by the remote wildfire server.
     * @param xmlReply XML reply that may contain information about the latest wildfire release.
     */
    private static void compareWildfireVersion(Element xmlRequest, Element xmlReply) {
        Element currentWildfire = xmlRequest.element("wildfire");
        if (currentWildfire != null) {
            // Compare latest version with installed one
            String latest = Versions.getVersion("wildfire");
            String installed = currentWildfire.attributeValue("current");
            if (installed != null && installed.compareTo(latest) < 0) {
                String extension = currentWildfire.attributeValue("type");
                Element latestWildfire = xmlReply.addElement("wildfire");
                latestWildfire.addAttribute("latest", latest);
                latestWildfire.addAttribute("changelog", WILDFIRE_LOG);
                /*String version = latest.replaceAll(".", "_");
                String fileName = WILDFIRE_PATH + "wildfire_" + version + "." + extension;
                latestWildfire.addAttribute("url", fileName);*/
                latestWildfire.addAttribute("url", WILDFIRE_PATH);
            }
        }
    }

    /**
     * Returns the list of available plugins that are not installed in the Wildfire server.
     * The request contains the plugins that are installed.
     *
     * @param pluginsPath the path where the .jar files of the plugins are located.
     * @param xmlRequest original XML request sent by the remote wildfire server.
     * @param xmlReply XML reply that contains information about available plugins.
     */
    private static void availablePlugins(String pluginsPath, Element xmlRequest, Element xmlReply)
            throws Exception {
        // Get the locale of the requester
        Locale requesterLocale = Locale.ENGLISH;
        if (xmlRequest != null) {
            Element localElement = xmlRequest.element("locale");
            if (localElement != null) {
                // Set the locale of the requester
                requesterLocale = localeCodeToLocale(localElement.getTextTrim());
            }
        }
        // Get jar files of plugins
        File pluginDir = new File(pluginsPath);
        File[] plugins = pluginDir.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar") || name.endsWith(".war");
            }
        });
        // Refresh (if required) information about available plugins
        Collection<String> availablePlugins = getPluginsInfo(plugins).keySet();
        // Build answer based on available plugins
        for (String pluginFilename : availablePlugins) {
            // Get the jar file of the plugin
            File pluginFile = null;
            for (File file : plugins) {
                if (pluginFilename.equals(file.getName())) {
                    pluginFile = file;
                    break;
                }
            }
            if (pluginFile == null) {
                // Should never happen but if jar file is no longer available then skip this plugin
                continue;
            }
            // Create answer
            Element latestPlugin = xmlReply.addElement("plugin");
            // Get the information specified in plugin.xml
            Document doc = pluginsInfo.get(pluginFilename);

            // Get i18n'ed version of the plugin name and description
            Element n1 = (Element) doc.selectSingleNode("/plugin/name");
            String name = (n1 == null ? pluginFilename
                    : geti18nText(pluginFile, n1.getTextTrim(), requesterLocale));
            latestPlugin.addAttribute("name", name);
            n1 = (Element) doc.selectSingleNode("/plugin/description");
            String desc = (n1 == null ? "" : geti18nText(pluginFile, n1.getTextTrim(), requesterLocale));
            latestPlugin.addAttribute("description", desc);

            latestPlugin.addAttribute("url", PLUGINS_PATH + pluginFilename);
            latestPlugin.addAttribute("icon", getIconURL(pluginsPath, pluginFilename));
            latestPlugin.addAttribute("readme", getReadmeURL(pluginsPath, pluginFilename));
            latestPlugin.addAttribute("changelog", getChangelogURL(pluginsPath, pluginFilename));

            Element version = (Element) doc.selectSingleNode("/plugin/version");
            Element licenseType = (Element) doc.selectSingleNode("/plugin/licenseType");
            Element author = (Element) doc.selectSingleNode("/plugin/author");
            Element minVersion = (Element) doc.selectSingleNode("/plugin/minServerVersion");

            latestPlugin.addAttribute("latest", version != null ? version.getTextTrim() : null);
            latestPlugin.addAttribute("licenseType", licenseType != null ? licenseType.getTextTrim() : null);
            latestPlugin.addAttribute("author", author != null ? author.getTextTrim() : null);
            latestPlugin.addAttribute("minServerVersion", minVersion != null ? minVersion.getTextTrim() : null);
            // Include size of plugin
            latestPlugin.addAttribute("fileSize", Long.toString(pluginFile.length()));
        }
    }

    private static String getChangelogURL(String pluginsPath, String pluginFilename) {
        // Remove .jar or .war extension
        String folder = pluginFilename.replaceFirst(".jar", "").toLowerCase();
        folder = folder.replaceFirst(".war", "").toLowerCase();
        String realPath = pluginsPath + File.separator + folder + File.separator + "changelog.html";
        if (new File(realPath).exists()) {
            return PLUGINS_PATH + folder + "/changelog.html";
        } else {
            return null;
        }
    }

    private static String getReadmeURL(String pluginsPath, String pluginFilename) {
        // Remove .jar or .war extension
        String folder = pluginFilename.replaceFirst(".jar", "").toLowerCase();
        folder = folder.replaceFirst(".war", "").toLowerCase();
        String realPath = pluginsPath + File.separator + folder + File.separator + "readme.html";
        if (new File(realPath).exists()) {
            return PLUGINS_PATH + folder + "/readme.html";
        } else {
            return null;
        }
    }

    private static String getIconURL(String pluginsPath, String pluginFilename) {
        // Replacing .jar or .war extension with .gif
        String file = pluginFilename.replaceFirst(".jar", ".gif").toLowerCase();
        file = file.replaceFirst(".war", ".gif").toLowerCase();
        String realPath = pluginsPath + File.separator + "cache" + File.separator + file;
        if (new File(realPath).exists()) {
            return PLUGINS_PATH + "cache/" + file;
        } else {
            return null;
        }
    }

    private static Map<String, Document> getPluginsInfo(File[] plugins) throws Exception {
        long latestPlugin = 0;
        // Get the date of the latest update plugin
        for (File file : plugins) {
            latestPlugin = latestPlugin < file.lastModified() ? file.lastModified() : latestPlugin;
        }
        // Update cache of plugins versions (if first time or a plugin has been updated)
        if (pluginsInfo == null || latestPlugin > lastPluginDate) {
            pluginsInfo = new ConcurrentHashMap<String, Document>();
            lastPluginDate = latestPlugin;
            for (File file : plugins) {
                String x1 = new String(getPluginFile(file, "plugin.xml"));
                Document doc1 = (new SAXReader()).read(new ByteArrayInputStream(x1.getBytes()));
                Element name = (Element) doc1.selectSingleNode("/plugin/name");
                if (name != null) {
                    pluginsInfo.put(file.getName(), doc1);
                }
            }
        }
        return pluginsInfo;
    }

    private static byte[] getPluginFile(File jarFile, String name) throws IOException {
        ZipFile zipFile = new JarFile(jarFile);
        for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
            JarEntry entry = (JarEntry) e.nextElement();
            if (name.equals(entry.getName().toLowerCase())) {
                InputStream in = new BufferedInputStream(zipFile.getInputStream(entry));
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] b = new byte[512];
                int len;
                while ((len = in.read(b)) != -1) {
                    out.write(b, 0, len);
                }
                out.flush();
                out.close();
                return out.toByteArray();
            }
        }
        return null;
    }

    private static String geti18nText(File jarFile, String key, Locale locale) {
        if (key == null) {
            return null;
        }
        // Look for the key symbol:
        if (key.indexOf("${") == 0 && key.indexOf("}") == key.length() - 1) {
            ResourceBundle bundle = getResourceBundle(jarFile, locale);
            if (bundle != null) {
                return bundle.getString(key.substring(2, key.length() - 1));
            }
        }
        return key;
    }

    private static ResourceBundle getResourceBundle(File jarFile, Locale locale) {
        try {
            // Search for the index if the extension
            int extensionIndex = jarFile.getName().lastIndexOf(".jar");
            if (extensionIndex == -1) {
                extensionIndex = jarFile.getName().lastIndexOf(".war");
            }
            String pluginName = jarFile.getName().substring(0, extensionIndex);
            URLClassLoader classLoader = new URLClassLoader(new URL[] { jarFile.toURL() });
            return ResourceBundle.getBundle("i18n/" + pluginName + "_i18n", locale, classLoader);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Converts a locale string like "en", "en_US" or "en_US_win" to a Java
     * locale object. If the conversion fails, null is returned.
     *
     * @param localeCode the locale code for a Java locale. See the {@link java.util.Locale}
     *                   class for more details.
     */
    private static Locale localeCodeToLocale(String localeCode) {
        Locale locale = null;
        if (localeCode != null) {
            String language = null;
            String country = null;
            String variant = null;
            StringTokenizer tokenizer = new StringTokenizer(localeCode, "_");
            if (tokenizer.hasMoreTokens()) {
                language = tokenizer.nextToken();
                if (tokenizer.hasMoreTokens()) {
                    country = tokenizer.nextToken();
                    if (tokenizer.hasMoreTokens()) {
                        variant = tokenizer.nextToken();
                    }
                }
            }
            locale = new Locale(language, ((country != null) ? country : ""), ((variant != null) ? variant : ""));
        }
        return locale;
    }
}