com.apporiented.hermesftp.PluginManager.java Source code

Java tutorial

Introduction

Here is the source code for com.apporiented.hermesftp.PluginManager.java

Source

/*
 * ------------------------------------------------------------------------------
 * Hermes FTP Server
 * Copyright (c) 2005-2014 Lars Behnke
 * ------------------------------------------------------------------------------
 * 
 * This file is part of Hermes FTP Server.
 * 
 * Hermes FTP Server is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Hermes FTP Server 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 Hermes FTP Server; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * ------------------------------------------------------------------------------
 */

package com.apporiented.hermesftp;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import com.apporiented.hermesftp.common.FtpConstants;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Utility class that manages the plug-in classpath and class loading.
 * 
 * @author Lars Behnke.
 */
public final class PluginManager {

    private static final String PATH_SEPARATOR = "path.separator";

    private static final String JAVA_CLASS_PATH = "java.class.path";

    private static final String JAR_EXT = ".jar";

    private static Log log = LogFactory.getLog(PluginManager.class);

    private static DynClassLoader classLoader;

    static {
        List<URL> jars = new ArrayList<URL>();

        StringBuffer cpExtension = new StringBuffer();
        File[] jarFiles = collectJars(new String[] { getPluginDir().getPath() });

        for (File jarFile : jarFiles) {
            try {
                File file = jarFile;
                jars.add(file.toURI().toURL());
                cpExtension.append(System.getProperty(PATH_SEPARATOR));
                cpExtension.append(file.toString());
            } catch (MalformedURLException e) {
                log.error(e);
            }
        }
        String cp = System.getProperty(JAVA_CLASS_PATH) + cpExtension.toString();
        System.setProperty(JAVA_CLASS_PATH, cp);
        if (log.isDebugEnabled()) {
            log.debug("Classpath: " + cp);
        }
        classLoader = new DynClassLoader(jars.toArray(new URL[jars.size()]));

        /*
         * Necessary for Spring to find plugin classes. Don't do this in servlet/ejb container!!
         */
        Thread.currentThread().setContextClassLoader(classLoader);
    }

    /**
     * Hidden constructor.
     */
    private PluginManager() {
    }

    private static File[] collectJars(String[] paths) {
        Set<File> jarList = new HashSet<File>();
        for (String path : paths) {
            File dir = new File(path);
            if (log.isWarnEnabled() && !dir.exists()) {
                log.warn("JAR folder not found: " + dir);
            }
            if (dir.exists() && dir.isDirectory()) {
                File[] files = dir.listFiles(new JarFileFilter());
                Collections.addAll(jarList, files);
            }
        }
        return jarList.toArray(new File[jarList.size()]);
    }

    /**
     * Adds a resource to the classpath. Note that the resource is not available before the update
     * method of the classloader is called.
     * 
     * @param jarOrPath The resource to add.
     */
    public static void addResource(File jarOrPath) {
        try {
            classLoader.addURL(jarOrPath.toURI().toURL());
        } catch (MalformedURLException e) {
            log.error(e);
        }
    }

    /**
     * Get the directory where plugins are installed.
     * 
     * @return the directory where plugins are installed.
     */
    public static File getPluginDir() {
        String dir = System.getProperty(FtpConstants.HERMES_HOME);
        if (StringUtils.isEmpty(dir)) {
            dir = System.getProperty("user.dir");
        }
        File file = new File(dir, "plugins");
        try {
            file = file.getCanonicalFile();
        } catch (IOException e) {
            log.debug("No canonical path: " + file);
        }
        return file;
    }

    /**
     * Need to be invoked from the application's main in order to utilize the dynamic class loader.
     * 
     * @param mainClassName Main class.
     * @param startMethod Start method that accepts the main arguments.
     * @param args Array of optional arguments
     */
    public static void startApplication(String mainClassName, String startMethod, String[] args) {
        try {
            classLoader.update();
            Class<?> clazz = classLoader.loadClass(mainClassName);
            Object instance = clazz.newInstance();
            Method startup = clazz.getMethod(startMethod, new Class[] { (new String[0]).getClass() });
            startup.invoke(instance, new Object[] { args });
        } catch (Exception e) {
            log.error(e, e);
        }
    }

    /**
     * Filter for JAR files.
     * 
     * @author developer
     */
    private static final class JarFileFilter implements FilenameFilter {
        public boolean accept(File f, String name) {
            return name.endsWith(JAR_EXT);
        }
    }

    /**
     * Class loader that is capable of adding new resources on the fly. In contrast to the default
     * class loader the addURL method is public. The approach was inspired by the Apache JMeter
     * project.
     * 
     * @author developer
     */
    private static class DynClassLoader extends URLClassLoader {

        private static final int EXTENSION_LENGTH = 6;

        public DynClassLoader(URL[] urls) {
            super(urls);
        }

        /**
         * @see java.net.URLClassLoader#addURL(java.net.URL)
         */
        public void addURL(URL url) {
            super.addURL(url);
        }

        /**
         * Loads all class files known to the class loader.
         */
        public void update() {
            try {
                loadClasses(classLoader.getURLs());
            } catch (IOException e) {
                log.error("Loading classes failed: " + e);
            }
        }

        private void loadClasses(URL[] urls) throws IOException {
            for (URL url : urls) {
                JarInputStream jis = new JarInputStream(urls[0].openStream());
                JarEntry entry = jis.getNextJarEntry();
                int loadedCount = 0;
                int totalCount = 0;

                while ((entry = jis.getNextJarEntry()) != null) {
                    String name = entry.getName();
                    if (name.endsWith(".class")) {
                        totalCount++;
                        name = name.substring(0, name.length() - EXTENSION_LENGTH);
                        name = name.replace('/', '.');

                        try {
                            classLoader.loadClass(name);
                            log.debug("Plugin class " + name + "\t- loaded");
                            loadedCount++;
                        } catch (Throwable e) {
                            log.debug("Plugin class " + name + "\t- not loaded - " + e);
                        }

                    }

                }
                log.debug("Classes loaded: " + loadedCount);
            }
        }
    }

}