org.tolven.command.TolvenApplication.java Source code

Java tutorial

Introduction

Here is the source code for org.tolven.command.TolvenApplication.java

Source

/*
 * Copyright (C) 2009 Tolven Inc
    
 * This library is free software; you can redistribute it and/or modify it under the terms of 
 * the GNU Lesser General Public License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;  
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU Lesser General Public License for more details.
 *
 * Contact: info@tolvenhealth.com 
 *
 * @author Joseph Isaac
 * @version $Id$
 */
package org.tolven.command;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.java.plugin.Plugin;
import org.java.plugin.PluginLifecycleException;
import org.java.plugin.PluginManager;
import org.java.plugin.boot.Application;
import org.java.plugin.boot.ApplicationPlugin;
import org.java.plugin.registry.ExtensionPoint;
import org.java.plugin.registry.ExtensionPoint.ParameterDefinition;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.registry.PluginPrerequisite;
import org.java.plugin.registry.PluginRegistry;
import org.java.plugin.util.ExtendedProperties;
import org.tolven.plugin.boot.TPFBoot;
import org.tolven.plugin.repository.RepositoryMetadata;
import org.tolven.security.hash.TolvenMessageDigest;

/**
 * This plugin is an ApplicationPlugin called by the JPF framework on startup.
 * 
 * @author Joseph Isaac
 *
 */
public class TolvenApplication extends ApplicationPlugin implements Application {

    public static final String CMD_PLUGIN_OPTION = "plugin";
    public static final String CMD_IF_RUNTIME_EXISTS_OPTION = "ifRuntimeExists";
    public static final String CMD_NOOP_OPTION = "noop";
    public static final String CMD_FORMAT_OPTION = "formatPluginsFile";

    public static final String EXTENSION_POINT_RUNTIME_LIB = "runtimeLib";
    public static final String EXTENSION_POINT_DOWNLOAD_WEB = "downLoadWeb";
    public static final String COMMAND_PLUGIN_ID = "org.tolven.command";
    public static final String MESSAGE_DIGEST_ALGORITHM = "md5";

    private static String[] initArgs;
    private static Method method;

    private static Logger logger = Logger.getLogger(TolvenApplication.class);

    @Override
    protected Application initApplication(ExtendedProperties extendedProperties, String[] initArgs)
            throws Exception {
        TolvenApplication.initArgs = initArgs;
        return this;
    }

    @Override
    public void startApplication() throws Exception {
        CommandLine commandLine = getCommandLine(initArgs);
        String[] pluginArgs = commandLine.getArgs();
        /*
         * The initArgs are used to determine which plugins to execute, while pluginArgs are passed to those plugins
         */
        if (commandLine.hasOption(CMD_NOOP_OPTION)) {
            //Which is the current default anyway i.e. do nothing
        } else if (commandLine.hasOption(CMD_FORMAT_OPTION)) {
            String[] optionValues = commandLine.getOptionValues(CMD_FORMAT_OPTION);
            String inputFilname = optionValues[0];
            File pluginsXMLFile = new File(inputFilname);
            if (!pluginsXMLFile.exists()) {
                throw new RuntimeException("Could not find plugins.xml file: " + pluginsXMLFile.getPath());
            }
            if (optionValues.length < 2) {
                throw new RuntimeException(
                        "A second argument is required for the destination of the formatted plugins.xml");
            }
            String outputFilename = optionValues[1];
            File outputFile = new File(outputFilename);
            if (outputFile.getParentFile() == null) {
                //A relative path
                String userDir = System.getProperty("user.dir");
                outputFile = new File(userDir, outputFile.getPath());
            }
            outputFile.getParentFile().mkdirs();
            String outputPluginsXML = RepositoryMetadata.format(pluginsXMLFile.toURI().toURL());
            logger.info("Output to: " + outputFile.getPath());
            FileUtils.writeStringToFile(outputFile, outputPluginsXML);
        } else {
            String pluginsString = commandLine.getOptionValue(CMD_PLUGIN_OPTION);
            if (pluginsString.length() == 0) {
                throw new RuntimeException("No plugins were supplied at the command line");
            }
            List<String> plugins = getPlugins(pluginsString);
            boolean skip = false;
            if (commandLine.hasOption(CMD_IF_RUNTIME_EXISTS_OPTION)) {
                String pluginId = plugins.get(0);
                boolean exists = getManager().getRegistry().isPluginDescriptorAvailable(pluginId);
                skip = !exists;
            }
            if (skip) {
                logger.info("Runtime plugin does not exist so skipped: " + pluginsString);
            } else {
                loadLibraries(pluginsString);
                String adminPluginId = getDescriptor().getAttribute("adminPluginId").getValue();
                execute(adminPluginId, getManager(), pluginArgs);
                plugins.remove(adminPluginId);
                startPlugins(plugins);
                String[] pluginIdsWithoutAdminId = new String[plugins.size()];
                for (int i = 0; i < plugins.size(); i++) {
                    pluginIdsWithoutAdminId[i] = plugins.get(i);
                }
                for (String pluginId : plugins) {
                    execute(pluginId, getManager(), pluginArgs);
                }
                logger.info("Execution of: " + pluginsString + " completed");
            }
        }
    }

    private CommandLine getCommandLine(String[] args) {
        Options cmdLineOptions = new Options();
        Option pluginOption = new Option(CMD_PLUGIN_OPTION, CMD_PLUGIN_OPTION, true, "\"plugin id\"");
        pluginOption.setRequired(true);
        cmdLineOptions.addOption(pluginOption);
        OptionGroup optionGroup = new OptionGroup();
        Option runtimePluginExistsOption = new Option(CMD_IF_RUNTIME_EXISTS_OPTION, CMD_IF_RUNTIME_EXISTS_OPTION,
                false, "\"execute if runtime plugin exists\"");
        optionGroup.addOption(runtimePluginExistsOption);
        Option noopOption = new Option(CMD_NOOP_OPTION, CMD_NOOP_OPTION, false,
                "no op plugin starts the plugin framework as a test, but does nothing");
        optionGroup.addOption(noopOption);
        Option formatOption = new Option(CMD_FORMAT_OPTION, CMD_FORMAT_OPTION, false,
                "formatPluginsFile inputPlugins.xml outputPlugins.xml");
        formatOption.setArgs(2);
        optionGroup.addOption(formatOption);
        cmdLineOptions.addOptionGroup(optionGroup);
        try {
            GnuParser parser = new GnuParser();
            CommandLine commandLine = parser.parse(cmdLineOptions, args, true);
            return commandLine;
        } catch (ParseException ex) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp(getClass().getName(), cmdLineOptions);
            throw new RuntimeException("Could not parse command line for: " + getClass().getName(), ex);
        }
    }

    private void loadLibraries(String pluginsString) throws Exception {
        List<PluginDescriptor> pluginDescriptors = new ArrayList<PluginDescriptor>();
        pluginDescriptors.add(getDescriptor());
        String adminPluginId = getDescriptor().getAttribute("adminPluginId").getValue();
        PluginDescriptor adminPluginDescriptor = getManager().getRegistry().getPluginDescriptor(adminPluginId);
        pluginDescriptors.add(adminPluginDescriptor);
        PluginRegistry registry = getManager().getRegistry();
        for (String pluginId : pluginsString.split(",")) {
            PluginDescriptor pluginDescriptor = registry.getPluginDescriptor(pluginId);
            pluginDescriptors.add(pluginDescriptor);
        }
        Set<PluginDescriptor> processedPluginDescriptors = new HashSet<PluginDescriptor>();
        List<URL> libraryPaths = new ArrayList<URL>();
        for (PluginDescriptor pluginDescriptor : pluginDescriptors) {
            loadLibraries(pluginDescriptor, getManager(), processedPluginDescriptors, libraryPaths);
        }
        StringBuffer buff = new StringBuffer();
        String pathSeparator = System.getProperty("path.separator");
        for (URL url : libraryPaths) {
            buff.append(url.getFile());
            buff.append(pathSeparator);
        }
        logger.debug("Plugin library path=" + buff.toString());
    }

    public static void execute(String pluginId, PluginManager pluginManager, String[] args) {
        PluginDescriptor pluginDescriptor = pluginManager.getRegistry().getPluginDescriptor(pluginId);
        execute(pluginDescriptor, pluginManager, args);
    }

    public static void execute(PluginDescriptor pluginDescriptor, PluginManager pluginManager, String[] args) {
        try {
            Plugin plugin = getPlugin(pluginDescriptor, pluginManager);
            pluginManager.activatePlugin(plugin.getDescriptor().getId());
            Method method = null;
            try {
                method = plugin.getClass().getDeclaredMethod("execute", new Class[] { String[].class });
                method.invoke(plugin, new Object[] { args });
            } catch (NoSuchMethodException ex) {
                // does not support execute() method, and that is fine, it was activated
            }
        } catch (Exception ex) {
            throw new RuntimeException("Could not execute: " + pluginDescriptor.getId(), ex);
        }
    }

    public static Plugin getPlugin(PluginDescriptor pluginDescriptor, PluginManager pluginManager) {
        loadLibraries(pluginDescriptor, pluginManager);
        try {
            Plugin plugin = pluginManager.getPlugin(pluginDescriptor.getId());
            return plugin;
        } catch (PluginLifecycleException ex) {
            throw new RuntimeException("Could not get plugin: " + pluginDescriptor.getId(), ex);
        }
    }

    private static List<URL> loadLibraries(PluginDescriptor pluginDescriptor, PluginManager pluginManager) {
        Set<PluginDescriptor> processedPluginDescriptors = new HashSet<PluginDescriptor>();
        List<URL> libraryPaths = new ArrayList<URL>();
        loadLibraries(pluginDescriptor, pluginManager, processedPluginDescriptors, libraryPaths);
        return libraryPaths;
    }

    private static List<URL> loadLibraries(PluginDescriptor pluginDescriptor, PluginManager pluginManager,
            Set<PluginDescriptor> processedPluginDescriptors, List<URL> libraryPaths) {
        Set<PluginDescriptor> unprocessedPluginDescriptors = new HashSet<PluginDescriptor>();
        if (!processedPluginDescriptors.contains(pluginDescriptor)) {
            unprocessedPluginDescriptors.add(pluginDescriptor);
        }
        for (PluginPrerequisite prereq : pluginDescriptor.getPrerequisites()) {
            PluginDescriptor prereqPluginDescriptor = pluginManager.getRegistry()
                    .getPluginDescriptor(prereq.getPluginId());
            if (!processedPluginDescriptors.contains(prereqPluginDescriptor)) {
                unprocessedPluginDescriptors.add(prereqPluginDescriptor);
            }
        }
        if (!unprocessedPluginDescriptors.isEmpty()) {
            for (PluginDescriptor unprocessedPluginDescriptor : unprocessedPluginDescriptors) {
                loadLibraries(unprocessedPluginDescriptor, pluginManager, libraryPaths);
                processedPluginDescriptors.add(unprocessedPluginDescriptor);
                loadLibraries(unprocessedPluginDescriptor, pluginManager, processedPluginDescriptors, libraryPaths);
            }
        }
        return libraryPaths;
    }

    private static void loadLibraries(PluginDescriptor descr, PluginManager pluginManager, List<URL> libraryPaths) {
        try {
            URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
            for (ExtensionPoint extensionPoint : descr.getExtensionPoints()) {
                if (EXTENSION_POINT_RUNTIME_LIB.equals(extensionPoint.getParentExtensionPointId())) {
                    String parentExtensionPointId = extensionPoint.getParentExtensionPointId();
                    String parentPluginId = extensionPoint.getParentPluginId();
                    if (parentPluginId == null || parentExtensionPointId == null) {
                        throw new RuntimeException(
                                "Cannot locate parent for extension point: " + extensionPoint.getUniqueId());
                    }
                    ExtensionPoint libraryExtensionPoint = descr.getRegistry().getExtensionPoint(parentPluginId,
                            parentExtensionPointId);
                    PluginDescriptor libraryPluginDescriptor = libraryExtensionPoint.getDeclaringPluginDescriptor();
                    for (ParameterDefinition parameterDefinition : extensionPoint.getParameterDefinitions()) {
                        String parameterId = parameterDefinition.getId();
                        ParameterDefinition parentParamterDefinition = libraryExtensionPoint
                                .getParameterDefinition(parameterId);
                        if (parentParamterDefinition == null) {
                            throw new RuntimeException(libraryExtensionPoint.getUniqueId()
                                    + " does not have the parameter definition: " + parameterId);
                        }
                        if (parentParamterDefinition.getDefaultValue() == null) {
                            continue;
                        }
                        String library = TPFBoot.pluginsWrapper.evaluate(parentParamterDefinition.getDefaultValue(),
                                libraryPluginDescriptor.getId());
                        ParameterDefinition remoteDefinition = null;
                        try {
                            remoteDefinition = parentParamterDefinition.getSubDefinition("remote");
                        } catch (Exception ex) {
                            // The underlying JPF did not follow its own pattern for allowing a call to handle whether a parameter definition exists or not
                        }
                        URL url = null;
                        if (remoteDefinition == null) {
                            url = loadLocalLibrary(library, libraryPluginDescriptor, pluginManager, urlClassLoader);
                        } else {
                            try {
                                url = loadRemoteLibrary(library, pluginManager, urlClassLoader);
                            } catch (Exception ex) {
                                logger.warn(
                                        "Remote library unavailable: " + library + " because " + ex.getMessage());
                            }
                        }
                        if (url != null) {
                            libraryPaths.add(url);
                        }
                    }
                }
            }
        } catch (Exception ex) {
            throw new RuntimeException("Could not load runtime libraries for plugin: " + descr.getId(), ex);
        }
    }

    private static URL loadLocalLibrary(String library, PluginDescriptor pluginDescriptor,
            PluginManager pluginManager, URLClassLoader urlClassLoader) throws Exception {
        File libraryFile = null;
        if (new File(library).getPath().equals(new File(library).getAbsolutePath())) {
            libraryFile = new File(library);
        } else {
            URL url = new URL(pluginManager.getPathResolver().resolvePath(pluginDescriptor, "") + library);
            libraryFile = new File(url.getFile());
        }
        if (!libraryFile.exists()) {
            throw new RuntimeException(pluginDescriptor.getUniqueId() + " could not find runtime library using: "
                    + library + " which leads to: " + libraryFile.getPath());
        }
        URL url = libraryFile.toURI().toURL();
        updateLibraryPath(url, urlClassLoader);
        return url;
    }

    private static URL loadRemoteLibrary(String library, PluginManager pluginManager,
            URLClassLoader urlClassLoader) {
        ExtensionPoint extensionPoint = pluginManager.getRegistry().getExtensionPoint(COMMAND_PLUGIN_ID,
                EXTENSION_POINT_DOWNLOAD_WEB);
        String hostname = TPFBoot.pluginsWrapper.evaluate(
                extensionPoint.getParameterDefinition("download.web.hostname").getDefaultValue(),
                COMMAND_PLUGIN_ID);
        if (hostname == null) {
            throw new RuntimeException(
                    "The property download.web.hostname for " + COMMAND_PLUGIN_ID + " evaluated to null");
        }
        String port = TPFBoot.pluginsWrapper.evaluate(
                extensionPoint.getParameterDefinition("download.web.http.port").getDefaultValue(),
                COMMAND_PLUGIN_ID);
        if (port == null) {
            throw new RuntimeException(
                    "The property download.web.http.port for " + COMMAND_PLUGIN_ID + " evaluated to null");
        }
        String downloadURLString = "http://" + hostname + ":" + port + "/Tolven/download";
        String installDirname = TPFBoot.pluginsWrapper.evaluate("#{globalProperty['installation.dir']}");
        try {
            File installRemoteLibDir = new File(installDirname, "remoteLib");
            if (!installRemoteLibDir.exists()) {
                installRemoteLibDir.mkdirs();
            }
            File destFile = new File(installRemoteLibDir, library);
            URL localURL = destFile.toURI().toURL();
            if (Arrays.asList(urlClassLoader.getURLs()).contains(localURL)) {
                return null;
            }
            String urlString = downloadURLString + "/" + library;
            URL url = new URL(urlString);
            if (destFile.exists()) {
                String md5sum = getRemoteChecksum(urlString + ".md5");
                if (md5sum.equals(TolvenMessageDigest.checksum(localURL, MESSAGE_DIGEST_ALGORITHM))) {
                    updateLibraryPath(localURL, urlClassLoader);
                    return localURL;
                }
            }
            logger.debug("Downloading remote library from: " + downloadURLString + " to " + installDirname);
            downloadFile(url, destFile);
            updateLibraryPath(localURL, urlClassLoader);
            return localURL;
        } catch (Exception ex) {
            throw new RuntimeException("Could not retrieve library: " + library, ex);
        }
    }

    private static String getRemoteChecksum(String urlString) {
        try {
            String md5sum = null;
            File tmpFile = null;
            try {
                tmpFile = File.createTempFile("remoteMD5_", null);
                URL url = new URL(urlString);
                downloadFile(url, tmpFile);
                md5sum = FileUtils.readFileToString(tmpFile);
            } finally {
                if (tmpFile != null) {
                    tmpFile.delete();
                }
            }
            return md5sum;
        } catch (Exception ex) {
            throw new RuntimeException("Could not download remote MD5 URL: " + urlString, ex);
        }
    }

    private static void downloadFile(URL url, File destFile) throws Exception {
        try {
            URLConnection urlConnection = url.openConnection();
            File tmpFile = null;
            try {
                tmpFile = File.createTempFile("remoteLib_", null);
                InputStream in = null;
                FileOutputStream out = null;
                try {
                    in = urlConnection.getInputStream();
                    out = new FileOutputStream(tmpFile);
                    IOUtils.copy(in, out);
                } finally {
                    if (out != null) {
                        out.close();
                    }
                    if (in != null) {
                        in.close();
                    }
                }
                FileUtils.copyFile(tmpFile, destFile);
            } finally {
                if (tmpFile != null) {
                    tmpFile.delete();
                }
            }
        } catch (Exception ex) {
            throw new RuntimeException("Could not download URL: " + url, ex);
        }
    }

    private static Method getMethod() throws SecurityException, NoSuchMethodException {
        if (method == null) {
            Class<?> sysclass = URLClassLoader.class;
            method = sysclass.getDeclaredMethod("addURL", new Class[] { URL.class });
            method.setAccessible(true);
        }
        return method;
    }

    private static void updateLibraryPath(URL url, URLClassLoader urlClassLoader) throws Exception {
        getMethod().invoke(urlClassLoader, new Object[] { url });
    }

    private List<String> getPlugins(String pluginsString) {
        List<String> plugins = new ArrayList<String>();
        for (String plugin : pluginsString.split(",")) {
            plugins.add(plugin);
        }
        return plugins;
    }

    private List<Plugin> startPlugins(List<String> plugins) throws PluginLifecycleException {
        // Activate (doStart) all plugins
        List<Plugin> startedPlugins = new ArrayList<Plugin>();
        for (String pluginId : plugins) {
            getManager().activatePlugin(pluginId);
            Plugin plugin = getManager().getPlugin(pluginId);
            startedPlugins.add(plugin);
        }
        return startedPlugins;
    }

    @Override
    protected void doStart() throws Exception {
    }

    @Override
    protected void doStop() throws Exception {
    }

}