org.fusesource.meshkeeper.distribution.PluginClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.fusesource.meshkeeper.distribution.PluginClassLoader.java

Source

/**
 *  Copyright (C) 2009 Progress Software, Inc. All rights reserved.
 *  http://fusesource.com
 *
 *  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.fusesource.meshkeeper.distribution;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.fusesource.meshkeeper.classloader.ClassLoaderServer;
import org.fusesource.meshkeeper.control.ControlService;
import org.fusesource.meshkeeper.distribution.event.EventClient;
import org.fusesource.meshkeeper.distribution.registry.RegistryClient;
import org.fusesource.meshkeeper.distribution.remoting.RemotingClient;
import org.fusesource.meshkeeper.distribution.repository.RepositoryClient;
import org.fusesource.meshkeeper.util.internal.FileSupport;
import org.fusesource.meshkeeper.util.internal.IOSupport;

/**
 * PluginClassLoader
 * <p>
 * Description:
 * </p>
 * 
 * @author cmacnaug
 * @version 1.0
 */
public class PluginClassLoader extends URLClassLoader {

    private static final Log LOG = LogFactory.getLog(PluginClassLoader.class);

    private static final HashSet<String> SPI_PACKAGES = new HashSet<String>();
    private static final HashSet<String> PARENT_FIRST = new HashSet<String>();
    private static final String DEFAULT_PLUGIN_VERSION = getDefaultPluginVersion();

    private static final PluginClassLoader DEFAULT_PLUGIN_CLASSLOADER = new PluginClassLoader(
            Thread.currentThread().getContextClassLoader());
    private static final boolean USE_PARENT_FIRST = false;
    private static PluginResolver PLUGIN_RESOLVER;
    private static final HashMap<String, List<File>> RESOLVED_PLUGINS = new HashMap<String, List<File>>();

    static {
        SPI_PACKAGES.add(DefaultDistributor.class.getPackage().getName());
        SPI_PACKAGES.add(RemotingClient.class.getPackage().getName());
        SPI_PACKAGES.add(RepositoryClient.class.getPackage().getName());
        SPI_PACKAGES.add(RegistryClient.class.getPackage().getName());
        SPI_PACKAGES.add(EventClient.class.getPackage().getName());
        SPI_PACKAGES.add(ControlService.class.getPackage().getName());
        SPI_PACKAGES.add(ClassLoaderServer.class.getPackage().getName());

        PARENT_FIRST.add(LogFactory.class.getPackage().getName());
        PARENT_FIRST.add("org.apache.log4j");
        PARENT_FIRST.add("org.apache.maven");
        PARENT_FIRST.add("org.springframework");
        PARENT_FIRST.add("javax.jms");
    }

    //Keep track of files already added to the classpath:
    private final HashSet<String> resolvedFiles = new HashSet<String>();

    /**
     * @return Returns the default plugin classloader.
     */
    public static final PluginClassLoader getDefaultPluginLoader() {
        return DEFAULT_PLUGIN_CLASSLOADER;
    }

    /**
     * If the current context class loader is a plugin class loader it is
     * returned otherwise the default plugin classloader is returned
     * 
     * @return
     */
    public static final PluginClassLoader getContextPluginLoader() {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl instanceof PluginClassLoader) {
            return (PluginClassLoader) cl;
        } else {
            return getDefaultPluginLoader();
        }
    }

    PluginClassLoader(ClassLoader parent) {
        super(new URL[] {}, parent);
        //        delegate = new PluginCL(this.getParent());
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        if (c == null) {

            int pDelim = name.lastIndexOf(".");
            if (USE_PARENT_FIRST || (pDelim > 0 && SPI_PACKAGES.contains(name.substring(0, pDelim)))) {
                c = super.loadClass(name, resolve);
            } else {
                for (String prefix : PARENT_FIRST) {
                    if (name.startsWith(prefix)) {
                        try {
                            c = super.loadClass(name, resolve);
                        } catch (ClassNotFoundException cnfe) {
                        }
                    }
                }

                if (c == null) {
                    try {
                        c = findClass(name);
                    } catch (ClassNotFoundException cnfe) {
                        c = super.loadClass(name, resolve);
                    }
                }
            }
        }

        if (c == null) {
            throw new ClassNotFoundException(name);
        }

        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {

        Class<?> c = super.findClass(name);
        if (LOG.isDebugEnabled()) {
            LOG.debug("PluginCL-" + this.hashCode() + " Found class: " + name);
            //" from: " + c.getProtectionDomain().getCodeSource().getLocation());
        }
        return c;
    }

    @Override
    public URL getResource(String name) {
        URL url = findResource(name);

        // if local search failed, delegate to parent
        if (url == null) {
            url = getParent().getResource(name);
        }
        return url;
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        Enumeration<URL> results = null;
        try {
            results = super.findResources(name);
        } catch (IOException ioe) {
        }

        // if local search failed, delegate to parent
        if (results == null || !results.hasMoreElements()) {
            results = getParent().getResources(name);
        }
        return results;
    }

    /**
     * Finds the resource with the specified name on the URL search path.
     * 
     * @param name
     *            the name of the resource
     * @return a <code>URL</code> for the resource, or <code>null</code> if the
     *         resource could not be found.
     */
    @Override
    public URL findResource(final String name) {
        URL url = null;
        if (name.equals("log4j.properties")) {
            url = getParent().getResource(name);
            if (url != null) {
                return url;
            }
        }
        url = super.findResource(name);
        if (LOG.isDebugEnabled()) {

            if (url == null) {
                LOG.debug("Couldn't find resource " + name + " in path: " + Arrays.toString(super.getURLs()));
            } else {
                LOG.debug("Looking for resource: " + name + " found: " + url);
            }
        }

        return url;
    }

    public void addUrl(URL url) {
        super.addURL(url);
    }

    /**
     * @param key
     */
    public Class<?> loadPlugin(String path, String key) throws IOException, ClassNotFoundException {

        String uri = path + key;

        try {
            //Possible that the plugin properties are already on the classpath
            //try loading there first: 
            Properties properties = loadProperties(this, uri);
            if (properties == null) {
                loadPlugin(key);
                properties = loadProperties(this, uri);
            }

            if (properties == null) {
                throw new IOException("Could not find factory properties: " + uri);
            }

            String className = properties.getProperty("class");
            if (className == null) {
                throw new IOException("Expected property is missing: class");
            }

            // The properties file can point us to the artifact to use...
            String mavenArtifact = properties.getProperty("maven.artifact");
            if (mavenArtifact != null) {
                loadArtifact(mavenArtifact);
            }

            //See if we can load it (either from system or the recently
            //loaded plugin classloader:
            return loadClass(className);

        } catch (IOException ioe) {
            throw ioe;
        } catch (ClassNotFoundException cnfe) {
            throw cnfe;
        } catch (Exception e) {
            IOException ioe = new IOException();
            ioe.initCause(e);
            throw ioe;
        }

    }

    private static Properties loadProperties(ClassLoader cl, String uri) throws IOException {
        InputStream in = cl.getResourceAsStream(uri);
        if (in == null) {
            return null;
        }

        // lets load the file
        BufferedInputStream reader = new BufferedInputStream(in);
        try {
            Properties properties = new Properties();
            properties.load(reader);
            return properties;
        } finally {
            try {
                reader.close();
            } catch (Exception e) {
            }
        }
    }

    public void loadArtifact(String artifactId) throws IOException, Exception {
        List<File> resolved = null;
        synchronized (RESOLVED_PLUGINS) {
            if (!RESOLVED_PLUGINS.containsKey(artifactId)) {
                LOG.info("Resolving plugin: " + artifactId);
                resolved = getPluginResolver().resolvePlugin(artifactId);
                RESOLVED_PLUGINS.put(artifactId, resolved);
                LOG.info("Resolved plugin: " + artifactId);
            } else {
                resolved = RESOLVED_PLUGINS.get(artifactId);
            }
        }

        for (File f : resolved) {
            if (resolvedFiles.add(f.getCanonicalPath())) {
                LOG.debug("Adding plugin dependency: " + f.getCanonicalPath());
                addUrl(f.toURL());
            }
        }

    }

    private void loadPlugin(String key) throws IOException, Exception {
        // The plugin version can be configured via a system prop
        String version = System.getProperty(PluginResolver.KEY_PLUGIN_VERSION_PREFIX + key, DEFAULT_PLUGIN_VERSION);
        loadArtifact(PluginResolver.PROJECT_GROUP_ID + ":meshkeeper-" + key + "-plugin:" + version);
    }

    public static String getDefaultPluginVersion() {
        String rc = System.getProperty(PluginResolver.KEY_DEFAULT_PLUGINS_VERSION);
        if (rc != null) {
            return rc;
        }

        return getModuleVersion();
    }

    static public String getModuleVersion() {
        String pomProps = "META-INF/maven/" + PluginResolver.PROJECT_GROUP_ID + "/"
                + PluginResolver.PROJECT_ARTIFACT_ID + "/pom.properties";

        final String DEFAULT_VERSION = "LATEST";
        try {
            Properties p = loadProperties(PluginClassLoader.class.getClassLoader(), pomProps);

            //Look for this in eclipse (for dev)
            if (p == null) {
                try {
                    URL location = PluginClassLoader.class.getProtectionDomain().getCodeSource().getLocation();
                    if (location.getProtocol().equals("file")) {
                        File f = new File(location.getFile() + File.separator + ".." + File.separator
                                + "maven-archiver" + File.separator + "pom.properties");
                        if (f.exists()) {
                            p = new Properties();
                            p.load(new FileInputStream(f));
                        }
                    }
                } catch (Exception e) {

                }
            }

            if (p != null) {
                return p.getProperty("version", DEFAULT_VERSION);
            }
        } catch (Exception e) {
        }
        LOG.warn("Unable to locate '" + pomProps + "' to determine plugin versions using default: "
                + DEFAULT_VERSION);
        return DEFAULT_VERSION;
    }

    public synchronized PluginResolver getPluginResolver() {
        if (PLUGIN_RESOLVER == null) {
            PluginClassLoader loader = this;
            try {
                // Extract the jar to temp file...
                InputStream is = PluginClassLoader.class.getClassLoader()
                        .getResourceAsStream("meshkeeper-mop-resolver.jar");
                if (is != null) {
                    File tempJar = File.createTempFile("meshkeeper-mop-resolver", ".jar");
                    tempJar.deleteOnExit();
                    try {
                        FileSupport.write(is, tempJar);
                    } finally {
                        IOSupport.close(is);
                    }
                    loader.addUrl(tempJar.toURL());
                }
                PLUGIN_RESOLVER = (PluginResolver) loader
                        .loadClass("org.fusesource.meshkeeper.distribution.MopPluginResolver").newInstance();
            } catch (Throwable thrown) {
                LOG.error("Error loading plugin resolver:" + thrown.getMessage(), thrown);
                throw new RuntimeException(thrown);
            }
            PLUGIN_RESOLVER.setDefaultPluginVersion(getDefaultPluginVersion());
        }
        return PLUGIN_RESOLVER;
    }

}