org.hyperic.hq.product.ProductPluginManager.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.product.ProductPluginManager.java

Source

/**
 * NOTE: This copyright does *not* cover user programs that use Hyperic
 * program services by normal system calls through the application
 * program interfaces provided as part of the Hyperic Plug-in Development
 * Kit or the Hyperic Client Development Kit - this is merely considered
 * normal use of the program, and does *not* fall under the heading of
 *  "derived work".
 *
 * Copyright (C) [2004-2010], VMware, Inc.
 * This file is part of Hyperic.
 *
 *  Hyperic is free software; you can redistribute it and/or modify
 *  it under the terms version 2 of the GNU General Public License as
 *  published by the Free Software Foundation. This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 *  USA.
 *
 */

package org.hyperic.hq.product;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.agent.AgentConfig;
import org.hyperic.hq.common.shared.ProductProperties;
import org.hyperic.hq.product.pluginxml.PluginData;
import org.hyperic.util.PluginLoader;
import org.hyperic.util.PluginLoaderException;
import org.hyperic.util.StringUtil;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.config.ConfigSchema;
import org.hyperic.util.security.MD5;

/**
 * This class is a manager for ProductPlugin implementations and is also a
 * manager of plugin managers.
 */
public class ProductPluginManager extends PluginManager {
    public static final String PROP_PDK_DIR = AgentConfig.PDK_DIR_KEY;

    private static final String PROP_PDK_PLUGINS_DIR = AgentConfig.PDK_PLUGIN_DIR_KEY;

    private static final String PROP_PDK_WORK_DIR = AgentConfig.PDK_WORK_DIR_KEY;

    // this is really verbose and not very helpful
    static final boolean DEBUG_LIFECYCLE = false;

    private static final String PLUGIN_STUB_NAME = "org.hyperic.hq.product.ProductPluginXML";

    private static final String PLUGIN_STUB = "org/hyperic/hq/product/ProductPluginXML.stub";

    // absolute must-have
    private static final String SYSTEM_PLUGIN = "system";

    private static final String[] BASE_PLUGINS = { "netservices", // many
            // plugins
            // depend on
            // this
            "sqlquery", // for sql:
                        // metrics
    };

    // support plugins loaded before product plugins
    private static final String[] PLUGIN_SUPPORT_DIRS = { "scripting" };

    private boolean registerTypes = false;
    private boolean client;
    private HashMap<String, PluginManager> managers = new HashMap<String, PluginManager>();
    private Map<String, Map<String, TypeInfo>> types = new HashMap<String, Map<String, TypeInfo>>();
    private HashMap includePlugins = null;
    private HashMap excludePlugins = null;
    private Log log = LogFactory.getLog(this.getClass().getName());
    private byte[] pluginStub = null;
    private int pluginStubLength = 0;

    private static final File HQ_DIR = new File(System.getProperty("user.home"), ".hq");

    public static final File PLUGIN_PROPERTIES_FILE = new File(HQ_DIR, "plugin.properties");

    public static final String PROPERTY_PREFIX = "hq.plugins.";

    private static final Map<String, String> JAVA_VERSIONS = new HashMap<String, String>();

    // java.class.version.major -> java.version
    static {
        JAVA_VERSIONS.put("48", "1.4");
        JAVA_VERSIONS.put("49", "1.5");
        JAVA_VERSIONS.put("50", "1.6");
        JAVA_VERSIONS.put("51", "1.7");
    }

    private MeasurementPluginManager mpm;
    private ControlPluginManager cpm;
    private AutoinventoryPluginManager apm;
    private RtPluginManager rpm;
    private LogTrackPluginManager ltpm;
    private ConfigTrackPluginManager ctpm;
    private LiveDataPluginManager ldpm;

    public ProductPluginManager() {
        this(System.getProperties());
    }

    /**
     * If true creates a mapping of the ProductPlugin TypeInfos, only needed on
     * the server side.
     */
    public void setRegisterTypes(boolean registerTypes) {
        this.registerTypes = registerTypes;
    }

    public boolean getRegisterTypes() {
        return this.registerTypes;
    }

    public static String getPropertyKey(String plugin, String key) {
        return PROPERTY_PREFIX + plugin + "." + key;
    }

    // ignore failures. if you want to check for errors, do it yourself.
    private static Properties getFileProperties(File file) {
        Properties props = new Properties();
        FileInputStream is = null;

        try {
            is = new FileInputStream(file);
            props.load(is);
        } catch (IOException e) {
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException ie) {
                }
            }
        }

        return props;
    }

    public ProductPluginManager(File file) {
        this(getFileProperties(file));
    }

    public ProductPluginManager(Properties props) {
        super(props);
    }

    public String getName() {
        return ProductPlugin.TYPE_PRODUCT;
    }

    /**
     * Derive plugin name from file name-plugin.ext
     */
    public static String getNameFromFile(String file) {
        String name = new File(file).getName();
        int ix = name.indexOf("-plugin.");
        if (ix != -1) {
            return name.substring(0, ix);
        } else {
            return null;
        }
    }

    // assumes type names are unique across plugins,
    // which they should be. if needed we could index on
    // product name too.
    /**
     * Find TypeInfo for the given platform and type name.
     * @param platform The platform name, e.g. "Linux"
     * @param name The type name, e.g. "Apache 2.0"
     */
    public TypeInfo getTypeInfo(String platform, String name) {
        Map<String, TypeInfo> platforms = this.types.get(platform);

        if (platforms == null) {
            return null;
        }

        return platforms.get(name);
    }

    /**
     * Find TypeInfo's accross all platforms for the given type name.
     * @param name The type name, e.g. "Apache 2.0"
     * @return  map<platformName, typeInfo> the type per platform
     */
    public Map<String, TypeInfo> getTypeInfo(String typeName) {
        Map<String, TypeInfo> typeInfos = Collections.emptyMap();

        if ((null != typeName) && (typeName.length() > 0)) {
            typeInfos = new HashMap<String, TypeInfo>(10);
            final Map<String, Map<String, TypeInfo>> types = getTypes();
            for (final Entry<String, Map<String, TypeInfo>> platform : types.entrySet()) {
                final TypeInfo typeInfo = platform.getValue().get(typeName);
                if (null != typeInfo)
                    typeInfos.put(platform.getKey(), typeInfo);
            }
        }
        return typeInfos;
    }

    public Map<String, Map<String, TypeInfo>> getTypes() {
        return Collections.unmodifiableMap(types);
    }

    protected void setTypeInfo(String platform, String name, TypeInfo info) {

        Map<String, TypeInfo> platforms = this.types.get(platform);

        if (platforms == null) {
            platforms = new HashMap<String, TypeInfo>();
            this.types.put(platform, platforms);
        }

        platforms.put(name, info);
    }

    // XXX we could just cache lookups in getTypeInfo
    // instead of mapping everything.
    /**
     * Create a mapping of product plugin TypeInfos.
     * @see #getTypeInfo
     * @see #registerPluginJar(String jarName)
     */
    private void registerTypeInfo(TypeInfo[] types) {
        if (types == null) {
            return;
        }

        for (int i = 0; i < types.length; i++) {
            TypeInfo type = types[i];
            String[] platforms = type.getPlatformTypes();

            for (int j = 0; j < platforms.length; j++) {
                setTypeInfo(platforms[j], type.getName(), type);
            }
        }
    }

    private String[] getPluginNames(String plugins) {
        if (plugins == null) {
            return null;
        }

        List<String> names = StringUtil.explode(plugins, ",");
        return names.toArray(new String[0]);
    }

    private void initPluginFilters() {
        HashMap<String, Integer> basePlugins = new HashMap<String, Integer>();
        basePlugins.put(SYSTEM_PLUGIN, // must-have
                new Integer(TypeInfo.TYPE_PLATFORM));
        String[] defaultPlugins;
        String base = getProperty("plugins.base");
        if (base == null) {
            defaultPlugins = BASE_PLUGINS;
        } else {
            defaultPlugins = getPluginNames(base);
        }

        for (int i = 0; i < defaultPlugins.length; i++) {
            basePlugins.put(defaultPlugins[i], new Integer(TypeInfo.TYPE_SERVER));
        }

        String include = getProperty("plugins.include");
        String exclude = getProperty("plugins.exclude");
        if ((include != null) && (exclude != null)) {
            log.warn("plugins.{include,exclude} are both defined" + ", use one or the other.");
        }

        if (include != null) {
            this.includePlugins = new HashMap();
            String[] plugins = getPluginNames(include);
            for (int i = 0; i < plugins.length; i++) {
                this.includePlugins.put(plugins[i], Boolean.TRUE);
            }
            // must-haves
            this.includePlugins.putAll(basePlugins);
        }

        if (exclude != null) {
            this.excludePlugins = new HashMap();
            String[] plugins = getPluginNames(exclude);
            for (int i = 0; i < plugins.length; i++) {
                String name = plugins[i];
                if (basePlugins.get(name) == null) {
                    this.excludePlugins.put(name, Boolean.TRUE);
                } else {
                    this.log.warn("Cannot exclude " + name + " plugin, ignoring.");
                }
            }
        }
    }

    private void setSystemProperties() {
        String pdk = getProperty(PROP_PDK_DIR);
        String workDir = getProperty(PROP_PDK_WORK_DIR, pdk + "/" + AgentConfig.WORK_DIR);
        String pluginsDir = getProperty(PROP_PDK_PLUGINS_DIR, pdk + "/plugins");

        if (pdk != null) {
            setPdkDir(pdk);
            setPdkWorkDir(workDir);
            setPdkPluginsDir(pluginsDir);
            log.info(PROP_PDK_DIR + "=" + getPdkDir());
        } else {
            String tmp = System.getProperty("java.io.tmpdir");
            File work = new File(tmp + "/pdk" + workDir);
            setPdkWorkDir(work.getPath());
            if (!work.exists()) {
                work.mkdirs();
            }
        }

        log.info(PROP_PDK_PLUGINS_DIR + "=" + getPdkPluginsDir());
        log.info(PROP_PDK_WORK_DIR + "=" + getPdkWorkDir());
    }

    public static String getPdkDir() {
        return System.getProperty(PROP_PDK_DIR);
    }

    public static void setPdkDir(String dir) {
        System.setProperty(PROP_PDK_DIR, dir);
    }

    public static String getPdkPluginsDir() {
        return System.getProperty(PROP_PDK_PLUGINS_DIR);
    }

    public static void setPdkPluginsDir(String dir) {
        System.setProperty(PROP_PDK_PLUGINS_DIR, dir);
    }

    public static String getPdkWorkDir() {
        return System.getProperty(PROP_PDK_WORK_DIR);
    }

    public static void setPdkWorkDir(String dir) {
        System.setProperty(PROP_PDK_WORK_DIR, dir);
    }

    public void init() throws PluginException {

        loadProductPluginStub();

        super.init(null); // null == we dont have a parent manager

        Properties props = getProperties();
        props.putAll(ProductProperties.getProperties());
        setSystemProperties();
        // not the same as platform.fqdn
        String name = props.getProperty(ProductPlugin.PROP_PLATFORM_NAME);
        if (name != null) {
            GenericPlugin.setPlatformName(name);
        }

        initPluginFilters();

        String pdk = getPdkDir();
        if (pdk != null) {
            this.client = new File(pdk, "lib").exists();
        }
        if (this.client) {
            log.debug("Initializing in client mode " + "(pdk=" + pdk + ")");
        } else {
            log.debug("Initializing in server mode");
        }
        initPluginManagers(props);
    }

    protected void initPluginManagers(Properties props) throws PluginException {
        this.mpm = new MeasurementPluginManager(props);
        this.cpm = new ControlPluginManager(props);
        this.apm = new AutoinventoryPluginManager(props);
        this.rpm = new RtPluginManager(props);
        this.ltpm = new LogTrackPluginManager(props);
        this.ctpm = new ConfigTrackPluginManager(props);
        this.ldpm = new LiveDataPluginManager(props);

        PluginManager[] mgrs = { this.mpm, this.cpm, this.apm, this.rpm, this.ltpm, this.ctpm, this.ldpm, this // note
                                                                                                               // to
                                                                                                               // self
        };

        for (int i = 0; i < mgrs.length; i++) {
            PluginManager mgr = mgrs[i];
            mgr.init(this);
            this.managers.put(mgr.getName(), mgr);
            if (!this.client || DEBUG_LIFECYCLE) {
                log.debug(mgr.getName() + " plugins enabled=" + isPluginTypeEnabled(mgr.getName()));
            }
        }

        // XXX by-passing server hot-deploy
        String plugins = getPdkPluginsDir();
        for (int i = 0; i < PLUGIN_SUPPORT_DIRS.length; i++) {
            File dir = new File(plugins, PLUGIN_SUPPORT_DIRS[i]);
            if (!dir.exists()) {
                continue;
            }
            registerPlugins(dir.getPath(), null);
        }
    }

    public Map<String, String> getPluginVersions(String pluginDir) {
        Map<String, String> rtn = new HashMap<String, String>();
        for (int i = 0; i < PLUGIN_SUPPORT_DIRS.length; i++) {
            File dir = new File(pluginDir, PLUGIN_SUPPORT_DIRS[i]);
            if (!dir.exists()) {
                continue;
            }
            File[] pluginFiles = listPlugins(dir);
            for (final File file : pluginFiles) {
                rtn.put(file.toString(), MD5.getMD5Checksum(file));
            }
        }
        return rtn;
    }

    public void shutdown() throws PluginException {

        synchronized (managers) {
            managers.remove(getName());
            for (Map.Entry<String, PluginManager> entry : managers.entrySet()) {
                PluginManager manager = entry.getValue();
                try {
                    manager.shutdown();
                } catch (PluginException e) {
                    log.error(manager.getName() + ".shutdown() failed", e);
                }
            }
            this.types.clear();
            this.managers.clear();
        }

        // shutdown() all registered ProductPlugins
        super.shutdown();
    }

    public MeasurementPluginManager getMeasurementPluginManager() {
        return this.mpm;
    }

    public ControlPluginManager getControlPluginManager() {
        return this.cpm;
    }

    public AutoinventoryPluginManager getAutoinventoryPluginManager() {
        return this.apm;
    }

    public RtPluginManager getRtPluginManager() {
        return this.rpm;
    }

    public LogTrackPluginManager getLogTrackPluginManager() {
        return this.ltpm;
    }

    public ConfigTrackPluginManager getConfigTrackPluginManager() {
        return this.ctpm;
    }

    public LiveDataPluginManager getLiveDataPluginManager() {
        return this.ldpm;
    }

    public MeasurementPlugin getMeasurementPlugin(String name) {
        try {
            return (MeasurementPlugin) this.mpm.getPlugin(name);
        } catch (PluginNotFoundException e) {
            log.debug("No MeasurementPlugin found for: " + name);
            return null;
        }
    }

    public ControlPlugin getControlPlugin(String name) {
        try {
            return (ControlPlugin) this.cpm.getPlugin(name);
        } catch (PluginNotFoundException e) {
            log.debug("No ControlPlugin found for: " + name);
            return null;
        }
    }

    public ServerDetector getAutoinventoryPlugin(String name) {
        try {
            return (ServerDetector) this.apm.getPlugin(name);
        } catch (PluginNotFoundException e) {
            log.debug("No AutoinventoryPlugin found for: " + name);
            return null;
        }
    }

    public RtPlugin getRtPlugin(String name) {
        try {
            return (RtPlugin) this.rpm.getPlugin(name);
        } catch (PluginNotFoundException e) {
            log.debug("No RtPlugin found for: " + name);
            return null;
        }
    }

    public ProductPlugin getProductPlugin(String name) {
        try {
            return (ProductPlugin) getPlugin(name);
        } catch (PluginNotFoundException e) {
            log.debug("No ProductPlugin found for: " + name);
            return null;
        }
    }

    /**
     * Register a plugin with the given GenericPluginManger, one instance
     * per-platform of server/service types.
     * @param pm The plugin manager
     * @param info Plugin info from the product plugin where this plugin is
     *        implemented.
     * @param plugin The plugin instance to register.
     * @param type The resource type info for this plugin.
     * @param registerTypes If true registers a plugin instance for all platform
     *        types (server-side), otherwise the current platform only
     *        (agent-side).
     * @throws PluginExistsException If an instance already exists with the same
     *         name in the given plugin manager.
     */
    void registerTypePlugin(PluginManager pm, PluginInfo info, GenericPlugin plugin, TypeInfo type)
            throws PluginExistsException {

        boolean register = this.registerTypes;
        boolean hasServer = false;
        ServiceTypeInfo service;
        ServerTypeInfo server = null;
        String[] platforms = null;
        String thisPlatform = HypericOperatingSystem.getInstance().getName();
        String name = plugin.getName();
        String skipMsg = null;

        if (DEBUG_LIFECYCLE) {
            skipMsg = "Skipping registration of '" + name + "' " + pm.getName() + " plugin on this platform";
        }

        switch (type.getType()) {
        case TypeInfo.TYPE_SERVER:
            server = (ServerTypeInfo) type;
            hasServer = true;
            break;
        case TypeInfo.TYPE_SERVICE:
            service = (ServiceTypeInfo) type;
            server = service.getServerTypeInfo();
            hasServer = true;
            break;
        case TypeInfo.TYPE_PLATFORM:
            if (((PlatformTypeInfo) type).isDevice()) {
                // always register devices, so they can be serviced by
                // an agent on another platform
                register = true;
            }
            if (!register && !thisPlatform.equals(name)) {
                if (DEBUG_LIFECYCLE) {
                    log.trace(skipMsg);
                }
                return;
            }
        default:
            break;
        }

        if (hasServer) {
            if (server.isPlatformDevice()) {
                // always register server types on platform devices
                register = true;
            }

            String[] validPlatforms = server.getValidPlatformTypes();

            if (register) {
                platforms = validPlatforms;
            } else {
                if (Arrays.asList(validPlatforms).contains(thisPlatform)) {
                    platforms = new String[] { thisPlatform };
                } else {
                    if (DEBUG_LIFECYCLE) {
                        log.trace(skipMsg + ", validPlatforms=" + Arrays.asList(validPlatforms));
                    }
                    return;
                }
            }
        }

        try {
            // XXX in the case of server/service type plugins
            // if there are to TypeInfos defined with the same
            // name but different platforms, first one wins
            // here. this should just be for temporary compat,
            // until subsystems include platform name when
            // looking up a server/service type plugin.

            PluginInfo gInfo = new PluginInfo(name, info);
            pm.setPluginInfo(name, gInfo);

            pm.registerPlugin(plugin, null);
        } catch (PluginExistsException e) {
            if (!hasServer) {
                throw e;
            }
        } catch (PluginException e) {
            this.log.error("registerPlugin=" + plugin.getName(), e);
        }

        if (!hasServer) {
            return;
        }

        // for server/service types we register an instance of
        // the plugin per-platform
        for (int i = 0; i < platforms.length; i++) {
            String pName = TypeBuilder.composePlatformTypeName(name, platforms[i]);

            try {
                pm.createPlugin(pName, plugin, null);
            } catch (PluginException e) {
                this.log.error("createPlugin=" + plugin.getName(), e);
            }
        }
    }

    private boolean isPluginTypeEnabled(String type) {
        String typeProp = getPropertyKey(type, "disable");
        return !"true".equals(getProperty(typeProp));
    }

    public boolean isLoadablePluginName(String name) {
        if (!(name.endsWith("-plugin.jar") || name.endsWith("-plugin.xml"))) {
            if (DEBUG_LIFECYCLE) {
                log.debug(name + " not a loadable plugin");
            }
            return false;
        }

        name = name.substring(0, name.length() - 11);
        if (this.includePlugins != null) {
            if (this.includePlugins.get(name) == null) {
                if (DEBUG_LIFECYCLE) {
                    log.debug("Skipping " + name + " (not in plugins.include)");
                }
                return false;
            }
        }
        if (isExcluded(name)) {
            return false;
        }

        return true;
    }

    private boolean isExcluded(String name) {
        if (name.endsWith("-plugin.jar") || name.endsWith("-plugin.xml")) {
            name = name.substring(0, name.length() - 11);
        }
        if (this.excludePlugins != null) {
            if (this.excludePlugins.get(name) != null) {
                if (DEBUG_LIFECYCLE) {
                    log.debug("Skipping " + name + " (in plugins.exclude)");
                }
                return true;
            }
        }
        return false;
    }

    /**
     * registerPluginJar() without mapping types.
     * @param jarName The name of the jar file on disk.
     * @see #registerPluginJar(String jarName,ClassLoader resourceLoader)
     */
    public PluginInfo registerPluginJar(String jarName) throws PluginException, PluginExistsException {
        return registerPluginJar(jarName, null);
    }

    private void loadProductPluginStub() throws PluginException {
        this.pluginStub = new byte[1024];
        ClassLoader loader = this.getClass().getClassLoader();

        InputStream is = null;
        try {
            is = loader.getResourceAsStream(PLUGIN_STUB);
            if (is == null) {
                throw new PluginException("Unable to find: " + PLUGIN_STUB);
            }
            this.pluginStubLength = is.read(this.pluginStub);
        } catch (IOException e) {
            throw new PluginException("Unable to read: " + PLUGIN_STUB + ": " + e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private File[] listPlugins(File dir) {
        File[] plugins = dir.listFiles();
        return plugins;
    }

    private String unsupportedClassVersionMessage(String msg) {
        final String ex = "Unsupported major.minor version ";
        int ix;
        if ((ix = msg.indexOf(ex)) > -1) {
            ix += ex.length();
            String major = msg.substring(ix, ix + 2);
            String jre = (String) JAVA_VERSIONS.get(major);
            if (jre == null) {
                return msg;
            }
            return "requires JRE " + jre + " or higher";
        } else {
            return msg;
        }
    }

    public Collection<PluginInfo> getAllPluginInfoDirectFromFileSystem(String path) {
        final Collection<PluginInfo> rtn = new ArrayList<PluginInfo>();
        final List<String> dirs = StringUtil.explode(path, File.pathSeparator);
        for (final String d : dirs) {
            final File dir = new File(d);
            if (!dir.exists() || !dir.isDirectory()) {
                continue;
            }
            File[] plugins = dir.listFiles();
            for (File plugin : plugins) {
                String name = plugin.getName();
                if (name.endsWith("-plugin.jar") || name.endsWith("-plugin.xml")) {
                    rtn.add(new PluginInfo(plugin, "NONE"));
                }
            }
        }
        return rtn;
    }

    public Collection<PluginInfo> registerPlugins(String path, Collection<PluginInfo> excludes) {
        Collection<PluginInfo> rtn = new ArrayList<PluginInfo>();
        List<String> dirs = StringUtil.explode(path, File.pathSeparator);
        for (int i = 0; i < dirs.size(); i++) {
            File dir = new File(dirs.get(i));
            if (!dir.exists()) {
                log.warn("register plugins: " + dir + " does not exist");
                continue;
            }
            if (!dir.isDirectory()) {
                log.warn("register plugins: " + dir + " not a directory");
                continue;
            }
            File[] plugins = listPlugins(dir);
            Collection<PluginInfo> pluginInfo = register(Arrays.asList(plugins), excludes);
            rtn.addAll(pluginInfo);
        }
        return rtn;
    }

    private Collection<PluginInfo> register(Collection<File> plugins, Collection<PluginInfo> excludes) {
        Collection<PluginInfo> rtn = new ArrayList<PluginInfo>();
        for (File plugin : plugins) {
            String name = plugin.getName();
            if (!isLoadablePluginName(name)) {
                if (isExcluded(name) && excludes != null && plugin.exists() && !plugin.isDirectory()) {
                    PluginInfo info = new PluginInfo(plugin, "EXCLUDED");
                    excludes.add(info);
                }
                continue;
            }
            log.info("Loading plugin: " + name + " (" + plugin.getParent() + ")");
            try {
                PluginInfo info = null;
                if ((info = registerPluginJar(plugin.getAbsolutePath())) != null) {
                    rtn.add(info);
                }
            } catch (UnsupportedClassVersionError e) {
                log.info("Cannot load " + name + ": " + unsupportedClassVersionMessage(e.getMessage()));
            } catch (PluginExistsException e) {
                log.debug("Plugin " + name + " already exists.");
            } catch (PluginException e) {
                // ...we're unable to register this particular plugin, log it and press on...
                log.error("A problem occured while registering plugin [" + name + "]", e);
            }
        }
        return rtn;
    }

    private void addClassPath(PluginLoader loader, String path) throws PluginException {

        try {
            loader.addURL(path);
        } catch (PluginLoaderException e) {
            throw new PluginException(e.getMessage());
        }
    }

    private void addClassPath(PluginLoader loader, String name, String[] classpath) throws PluginException {

        if (classpath.length == 0) {
            return;
        }

        String pdkDir = getPdkDir();
        String pdkPluginsDir = getPdkPluginsDir();
        for (int i = 0; i < classpath.length; i++) {
            String path = classpath[i];
            if (path.startsWith("pdk/plugins/") && pdkPluginsDir != null) {
                path = pdkPluginsDir + "/" + path.substring(12);
            } else if (path.startsWith("pdk/") && (pdkDir != null)) {
                path = pdkDir + "/" + path.substring(3);
            }
            addClassPath(loader, path);
        }
    }

    private void logPluginManifest(String jarName) {
        if (log.isDebugEnabled()) {
            URL url;
            try {
                url = new URL("jar", "", "file:" + jarName + "!/");
                JarURLConnection jarConn = (JarURLConnection) url.openConnection();
                Map attributeMap = jarConn.getManifest().getMainAttributes();
                if (!attributeMap.isEmpty()) {
                    StringBuilder manifestLog = new StringBuilder(
                            "\n--- Manifest entries for: " + url.toString() + " ---\n");
                    Iterator iter = attributeMap.entrySet().iterator();
                    while (iter.hasNext()) {
                        Map.Entry entry = (Map.Entry) iter.next();
                        manifestLog.append(entry.getKey() + " - " + entry.getValue() + "\n");
                    }
                    log.debug(manifestLog.toString());
                }
            } catch (Exception e) {
                log.debug("Manifest retrieval had an exception (continuing): " + e.getMessage());
            }
        }
    }

    /**
     * Load a product plugin jar. Registers the product plugin, as defined by
     * the Main-Class jar manifest attribute which must be a class which
     * implements the ProductPlugin interface. Registers plugins supported for
     * each plugin type (control, measurement, responsetime) as returned by the
     * ProductPlugin.getPlugin method.
     * @param jarName The name of the jar file on disk.
     * @param resourceLoader ClassLoader used to find jar resources.
     * @return The name of the product plugin as returned by
     *         ProductPlugin.getName.
     * @see org.hyperic.hq.product.ProductPlugin
     */
    public PluginInfo registerPluginJar(String jarName, ClassLoader resourceLoader)
            throws PluginException, PluginExistsException {

        ProductPlugin plugin = null;
        Class<?> pluginClass = null;
        PluginData data;
        String defaultPluginName = getNameFromFile(jarName);

        try {
            PluginLoader loader = PluginLoader.create(jarName, this.getClass().getClassLoader());

            logPluginManifest(jarName);
            PluginLoader.setClassLoader(loader);
            ClassLoader dataLoader;
            if (resourceLoader != null) {
                dataLoader = resourceLoader;
            } else {
                dataLoader = loader;
            }

            data = PluginData.getInstance(this, dataLoader, jarName);

            String[] classpath = ProductPlugin.getDataClassPath(data);
            addClassPath(loader, jarName, classpath);

            if (jarName.endsWith(".jar")) {
                String pdk = getPdkWorkDir();

                ClientPluginDeployer deployer = new ClientPluginDeployer(pdk, defaultPluginName);
                List jars = deployer.unpackJar(jarName);
                //if (this.isClient) {
                loader.addURLs(jars);
                //}
            }

            String implName = data.getPlugin(ProductPlugin.TYPE_PRODUCT, ProductPlugin.TYPE_PRODUCT);

            if (implName == null) {
                pluginClass = loader.loadPlugin(PLUGIN_STUB_NAME, this.pluginStub, this.pluginStubLength);
            } else {
                pluginClass = ProductPlugin.getPluginClass(PluginLoader.getClassLoader(), data, implName, jarName);
            }

            if (pluginClass == null) {
                throw new PluginException("Class [" + implName + "] not found " + "via classloader=["
                        + PluginLoader.getClassLoader());
            }

            plugin = (ProductPlugin) pluginClass.newInstance();
            plugin.data = data;

            // there are 3 ways to set the product name:
            // - legacy ProductPlugin.setName()
            // - <product name="foo"> in hq-plugin.xml
            // - default to name of the plugin file minus "-plugin.{xml,jar}"
            // we try all three and make sure plugin.name and data.name
            // are both set with the same value.
            String pluginName = plugin.getName(); // legacy
            if (pluginName == null) {
                pluginName = data.getName(); // hq-plugin.xml
            }
            if (pluginName == null) {
                pluginName = defaultPluginName;
                if (pluginName == null) {
                    throw new PluginException("Malformed name for: " + jarName);
                }
            }
            if (data.getName() == null) {
                data.setName(pluginName);
            }
            if (plugin.getName() == null) {
                plugin.setName(pluginName);
            }
            if (plugin.getPluginVersion() == null) {
                plugin.setPluginVersion(data.getVersion());
            }

            if (this.client && (implName != null)) {
                // already added the classpath, but the impl may override/adjust
                String[] pluginClasspath = plugin.getClassPath(this);
                addClassPath(loader, plugin.getName(), pluginClasspath);
            }

            PluginInfo info = new PluginInfo(plugin, jarName);
            // e.g. for finding hq-plugin.xml
            // when deployed on server
            // resourceLoader != plugin.getClass().getClassLoader()
            if (resourceLoader == null) {
                resourceLoader = plugin.getClass().getClassLoader();
            }
            info.resourceLoader = resourceLoader;

            setPluginInfo(pluginName, info);

            registerPlugin(plugin, null);
            TypeInfo[] types = plugin.getTypes();

            if (types == null) {
                this.log.error(pluginName + ".getTypes returned null");
                return null;
            }
            addPluginTypes(types, plugin);
            return info;
        } catch (PluginException e) {
            throw e;
        } catch (Exception e) {
            throw new PluginException(e.getMessage(), e);
        } finally {
            if (plugin != null) {
                PluginLoader.resetClassLoader(plugin);
            }
        }
    }

    public void addPluginTypes(TypeInfo[] types, ProductPlugin plugin) throws PluginExistsException {
        PluginInfo info = getPluginInfo(plugin.getName());
        if (this.registerTypes) {
            registerTypeInfo(types);
        }
        for (int i = 0; i < ProductPlugin.TYPES.length; i++) {
            String type = ProductPlugin.TYPES[i];

            if (type.equals(ProductPlugin.TYPE_PRODUCT))
                continue;

            if (!isPluginTypeEnabled(type)) {
                continue;
            }
            PluginManager pm = (PluginManager) managers.get(type);

            for (int j = 0; j < types.length; j++) {
                GenericPlugin gPlugin;
                String typeName = types[j].getName();

                gPlugin = plugin.getPlugin(type, types[j]);
                if (gPlugin == null) {
                    if (DEBUG_LIFECYCLE) {
                        log.debug(plugin.getName() + " does not implement " + type + " for type=" + typeName);
                    }
                    continue;
                }

                gPlugin.data = plugin.data;
                gPlugin.setName(typeName);
                gPlugin.setTypeInfo(types[j]);

                if (DEBUG_LIFECYCLE) {
                    log.debug(plugin.getName() + " implements " + type + " for type=" + typeName);
                }

                registerTypePlugin(pm, info, gPlugin, types[j]);
            }
        }

    }

    public void removePluginTypes(List<TypeInfo> typeInfos) {
        for (int i = 0; i < ProductPlugin.TYPES.length; i++) {
            String type = ProductPlugin.TYPES[i];

            if (type.equals(ProductPlugin.TYPE_PRODUCT))
                continue;

            if (!isPluginTypeEnabled(type)) {
                continue;
            }
            PluginManager pm = (PluginManager) managers.get(type);
            Map<String, GenericPlugin> plugins = pm.getPlugins();
            Set<String> pluginsToDelete = new HashSet<String>();
            for (GenericPlugin gPlugin : plugins.values()) {

                if (typeInfos.contains(gPlugin.getTypeInfo())) {
                    pluginsToDelete.add(gPlugin.getName());
                }
            }
            for (String pluginToDelete : pluginsToDelete) {
                try {
                    pm.removePlugin(pluginToDelete);
                } catch (PluginNotFoundException e) {
                    log.warn("Error attempting to remove a plugin that does not exist");
                } catch (PluginException e) {
                    log.warn("Error removing a plugin while updating type metadata.  "
                            + "This could cause a PluginExistsException later when an attempt is made to re-register the plugin.  Cause: "
                            + e.getMessage());
                }
            }
        }
    }

    private void removeManagerPlugins(PluginManager mgr, String jarName) throws PluginException {

        Map<String, GenericPlugin> mPlugins = mgr.getPlugins();

        // cannot use keySet().iterator() else
        // ConcurrentModificationException is thrown during removePlugin()
        String[] keys = mPlugins.keySet().toArray(new String[0]);

        for (int i = 0; i < keys.length; i++) {
            String name = keys[i];
            PluginInfo info = mgr.getPluginInfo(name);

            if (info == null) {
                String msg = "no plugin info found for " + mgr.getName() + " plugin " + name;
                throw new PluginException(msg);
            }

            // XXX: should prolly check more than jar basename
            // but then again, they live in the same directory
            // so jar basename should be unique.
            if (info.jar.equals(jarName)) {
                try {
                    mgr.removePlugin(name);
                } catch (PluginNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void removePluginJar(String jarName) throws PluginException {

        String fileName = new File(jarName).getName();

        for (Map.Entry<String, PluginManager> entry : this.managers.entrySet()) {

            PluginManager manager = entry.getValue();

            removeManagerPlugins(manager, fileName);
        }
    }

    public void updatePluginJar(String jarName) throws PluginException {

        removePluginJar(jarName);

        try {
            registerPluginJar(jarName);
        } catch (PluginExistsException e) {
            // should not happen if removePluginJar was a success
            throw new PluginException(e);
        }
    }

    public PluginManager getPluginManager(String type) throws PluginException {

        PluginManager mgr = this.managers.get(type);

        if (mgr == null) {
            // XXX PluginManagerNotFoundException
            throw new PluginException();
        }

        return mgr;
    }

    public void setProperty(String key, String value) {
        for (Map.Entry<String, PluginManager> entry : this.managers.entrySet()) {
            PluginManager manager = entry.getValue();
            manager.getProperties().setProperty(key, value);
        }
    }

    public boolean isClient() {
        return this.client;
    }

    /**
     * As opposed to other plugins, product plugins
     * are fetched by plugin name 
     */
    @Override
    public ConfigSchema getConfigSchema(String pluginName, String platformName, String typeName, TypeInfo info,
            ConfigResponse config) throws PluginNotFoundException {

        return getConfigSchema(pluginName, info, config);
    }
}