]> source.dussan.org Git - pf4j.git/commitdiff
Add JarPluginManager, PluginLoader, AbstractPluginManager 128/head
authorDecebal Suiu <decebal.suiu@gmail.com>
Wed, 22 Feb 2017 21:23:16 +0000 (23:23 +0200)
committerDecebal Suiu <decebal.suiu@gmail.com>
Wed, 22 Feb 2017 21:23:16 +0000 (23:23 +0200)
31 files changed:
demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java
demo/app/src/main/resources/log4j.properties
pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/BasePluginRepository.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/CompoundPluginRepository.java
pf4j/src/main/java/ro/fortsoft/pf4j/CyclicDependencyException.java [deleted file]
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginClasspath.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginFactory.java
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginLoader.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginRepository.java
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginStatusProvider.java
pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java
pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java
pf4j/src/main/java/ro/fortsoft/pf4j/JarPluginManager.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptorFinder.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginRepository.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java
pf4j/src/main/java/ro/fortsoft/pf4j/PropertiesPluginDescriptorFinder.java
pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java
pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java
pf4j/src/test/java/ro/fortsoft/pf4j/DefaultPluginRepositoryTest.java
pf4j/src/test/java/ro/fortsoft/pf4j/DefaultPluginStatusProviderTest.java
pf4j/src/test/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinderTest.java
pf4j/src/test/resources/log4j.properties [new file with mode: 0644]

index fa779e27fe8d47c103b94571e8877f1db49703ce..c9738ebb7c41e5afd730d86921773f2feda84ba2 100644 (file)
@@ -21,6 +21,7 @@ import java.util.Set;
 import org.apache.commons.lang.StringUtils;
 
 import ro.fortsoft.pf4j.DefaultPluginManager;
+import ro.fortsoft.pf4j.JarPluginManager;
 import ro.fortsoft.pf4j.PluginManager;
 import ro.fortsoft.pf4j.PluginWrapper;
 import ro.fortsoft.pf4j.demo.api.Greeting;
@@ -38,6 +39,7 @@ public class Boot {
 
         // create the plugin manager
         final PluginManager pluginManager = new DefaultPluginManager();
+//        final PluginManager pluginManager = new JarPluginManager();
 
         // load the plugins
         pluginManager.loadPlugins();
index 0454ba28da8f7905cf9446e03ec21b8f78368846..692e39b8d44bca4e1cb1b0aabf480265915da260 100644 (file)
@@ -5,7 +5,7 @@ log4j.rootLogger=DEBUG, Console
 #
 log4j.logger.ro.fortsoft.pf4j=DEBUG, Console
 # !!! Put the bellow classes on level TRACE when you are in trouble
-log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=WARN, Console
+log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=DEBUG, Console
 log4j.logger.ro.fortsoft.pf4j.AbstractExtensionFinder=DEBUG, Console
 log4j.additivity.ro.fortsoft.pf4j=false
 log4j.additivity.ro.fortsoft.pf4j.PluginClassLoader=false
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java
new file mode 100644 (file)
index 0000000..6087ec4
--- /dev/null
@@ -0,0 +1,800 @@
+/*
+ * Copyright 2016 Decebal Suiu
+ *
+ * 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 ro.fortsoft.pf4j;
+
+import com.github.zafarkhaja.semver.Version;
+import com.github.zafarkhaja.semver.expr.Expression;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class implements the boilerplate plugin code that any {@link PluginManager}
+ * implementation would have to support.
+ * It helps cut the noise out of the subclass that handles plugin management.
+ *
+ * @author Decebal Suiu
+ */
+public abstract class AbstractPluginManager implements PluginManager {
+
+    private static final Logger log = LoggerFactory.getLogger(AbstractPluginManager.class);
+
+    private Path pluginsRoot;
+
+    private ExtensionFinder extensionFinder;
+
+    private PluginDescriptorFinder pluginDescriptorFinder;
+
+    /*
+     * A map of plugins this manager is responsible for (the key is the 'pluginId').
+     */
+    protected Map<String, PluginWrapper> plugins;
+
+    /*
+     * A map of plugin class loaders (he key is the 'pluginId').
+     */
+    private Map<String, ClassLoader> pluginClassLoaders;
+
+    /*
+     * A list with unresolved plugins (unresolved dependency).
+     */
+    private List<PluginWrapper> unresolvedPlugins;
+
+    /**
+     * A list with resolved plugins (resolved dependency).
+     */
+    private List<PluginWrapper> resolvedPlugins;
+
+    /*
+     * A list with started plugins.
+     */
+    private List<PluginWrapper> startedPlugins;
+
+    /*
+     * The registered {@link PluginStateListener}s.
+     */
+    private List<PluginStateListener> pluginStateListeners;
+
+    /*
+     * Cache value for the runtime mode.
+     * No need to re-read it because it wont change at runtime.
+     */
+    private RuntimeMode runtimeMode;
+
+    /*
+     * The system version used for comparisons to the plugin requires attribute.
+     */
+    private Version systemVersion = Version.forIntegers(0, 0, 0);
+
+    /**
+     * A relation between 'pluginPath' and 'pluginId'
+     */
+    private Map<Path, String> pathToIdMap;
+
+    private PluginRepository pluginRepository;
+    private PluginFactory pluginFactory;
+    private ExtensionFactory extensionFactory;
+    private PluginStatusProvider pluginStatusProvider;
+    private DependencyResolver dependencyResolver;
+    private PluginLoader pluginLoader;
+
+    /**
+     * The plugins root is supplied by {@code System.getProperty("pf4j.pluginsDir", "plugins")}.
+     */
+    public AbstractPluginManager() {
+        initialize();
+    }
+
+    /**
+     * Constructs {@code DefaultPluginManager} which the given plugins root.
+     *
+     * @param pluginsRoot the root to search for plugins
+     */
+    public AbstractPluginManager(Path pluginsRoot) {
+        this.pluginsRoot = pluginsRoot;
+
+        initialize();
+    }
+
+    @Override
+    public void setSystemVersion(Version version) {
+        systemVersion = version;
+    }
+
+    @Override
+    public Version getSystemVersion() {
+        return systemVersion;
+    }
+
+    /**
+     * Returns a copy of plugins.
+     *
+     * @return
+     */
+    @Override
+    public List<PluginWrapper> getPlugins() {
+        return new ArrayList<>(plugins.values());
+    }
+
+    /**
+     * Returns a copy of plugins with that state.
+     *
+     * @param pluginState
+     * @return
+     */
+    @Override
+    public List<PluginWrapper> getPlugins(PluginState pluginState) {
+        List<PluginWrapper> plugins = new ArrayList<>();
+        for (PluginWrapper plugin : getPlugins()) {
+            if (pluginState.equals(plugin.getPluginState())) {
+                plugins.add(plugin);
+            }
+        }
+
+        return plugins;
+    }
+
+    @Override
+    public List<PluginWrapper> getResolvedPlugins() {
+        return resolvedPlugins;
+    }
+
+    @Override
+    public List<PluginWrapper> getUnresolvedPlugins() {
+        return unresolvedPlugins;
+    }
+
+    @Override
+    public List<PluginWrapper> getStartedPlugins() {
+        return startedPlugins;
+    }
+
+    @Override
+    public PluginWrapper getPlugin(String pluginId) {
+        return plugins.get(pluginId);
+    }
+
+    @Override
+    public String loadPlugin(Path pluginPath) {
+        if ((pluginPath == null) || Files.notExists(pluginPath)) {
+            throw new IllegalArgumentException(String.format("Specified plugin %s does not exist!", pluginPath));
+        }
+
+        log.debug("Loading plugin from '{}'", pluginPath);
+
+        try {
+            PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath);
+            // TODO uninstalled plugin dependencies?
+            getUnresolvedPlugins().remove(pluginWrapper);
+            getResolvedPlugins().add(pluginWrapper);
+
+            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, null));
+
+            return pluginWrapper.getDescriptor().getPluginId();
+        } catch (PluginException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Load plugins.
+     */
+    @Override
+    public void loadPlugins() {
+        log.debug("Lookup plugins in '{}'", pluginsRoot);
+        // check for plugins root
+        if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot)) {
+            log.error("No '{}' root", pluginsRoot);
+            return;
+        }
+
+        // get all plugin paths from repository
+        List<Path> pluginPaths = pluginRepository.getPluginPaths();
+
+        // check for no plugins
+        if (pluginPaths.isEmpty()) {
+            log.info("No plugins");
+            return;
+        }
+
+        log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
+
+        // load plugins from plugin paths
+        // TODO
+        for (Path pluginPath : pluginPaths) {
+            try {
+                loadPluginFromPath(pluginPath);
+            } catch (PluginException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+
+        // resolve 'unresolvedPlugins'
+        try {
+            resolvePlugins();
+        } catch (PluginException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public boolean unloadPlugin(String pluginId) {
+        try {
+            PluginState pluginState = stopPlugin(pluginId);
+            if (PluginState.STARTED == pluginState) {
+                return false;
+            }
+
+            PluginWrapper pluginWrapper = getPlugin(pluginId);
+            PluginDescriptor descriptor = pluginWrapper.getDescriptor();
+            List<PluginDependency> dependencies = descriptor.getDependencies();
+            for (PluginDependency dependency : dependencies) {
+                if (!unloadPlugin(dependency.getPluginId())) {
+                    return false;
+                }
+            }
+
+            // remove the plugin
+            plugins.remove(pluginId);
+            getResolvedPlugins().remove(pluginWrapper);
+            pathToIdMap.remove(pluginWrapper.getPluginPath());
+
+            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
+
+            // remove the classloader
+            Map<String, ClassLoader> pluginClassLoaders = getPluginClassLoaders();
+            if (pluginClassLoaders.containsKey(pluginId)) {
+                ClassLoader classLoader = pluginClassLoaders.remove(pluginId);
+                if (classLoader instanceof Closeable) {
+                    try {
+                        ((Closeable) classLoader).close();
+                    } catch (IOException e) {
+                        log.error("Cannot close classloader", e);
+                    }
+                }
+            }
+
+            return true;
+        } catch (IllegalArgumentException e) {
+            // ignore not found exceptions because this method is recursive
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean deletePlugin(String pluginId) {
+        if (!plugins.containsKey(pluginId)) {
+            throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
+        }
+
+        PluginWrapper pluginWrapper = getPlugin(pluginId);
+        PluginState pluginState = stopPlugin(pluginId);
+        if (PluginState.STARTED == pluginState) {
+            log.error("Failed to stop plugin '{}' on delete", pluginId);
+            return false;
+        }
+
+        if (!unloadPlugin(pluginId)) {
+            log.error("Failed to unload plugin '{}' on delete", pluginId);
+            return false;
+        }
+
+        Path pluginPath = pluginWrapper.getPluginPath();
+
+        pluginRepository.deletePluginPath(pluginPath);
+
+        return true;
+    }
+
+    /**
+     * Start all active plugins.
+     */
+    @Override
+    public void startPlugins() {
+        for (PluginWrapper pluginWrapper : resolvedPlugins) {
+            PluginState pluginState = pluginWrapper.getPluginState();
+            if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
+                try {
+                    PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
+                    log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+                    pluginWrapper.getPlugin().start();
+                    pluginWrapper.setPluginState(PluginState.STARTED);
+                    startedPlugins.add(pluginWrapper);
+
+                    firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
+                } catch (PluginException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Start the specified plugin and it's dependencies.
+     */
+    @Override
+    public PluginState startPlugin(String pluginId) {
+        if (!plugins.containsKey(pluginId)) {
+            throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
+        }
+
+        PluginWrapper pluginWrapper = getPlugin(pluginId);
+        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
+        PluginState pluginState = pluginWrapper.getPluginState();
+        if (PluginState.STARTED == pluginState) {
+            log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+            return PluginState.STARTED;
+        }
+
+        if (PluginState.DISABLED == pluginState) {
+            // automatically enable plugin on manual plugin start
+            if (!enablePlugin(pluginId)) {
+                return pluginState;
+            }
+        }
+
+        for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
+            startPlugin(dependency.getPluginId());
+        }
+
+        try {
+            log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+            pluginWrapper.getPlugin().start();
+            pluginWrapper.setPluginState(PluginState.STARTED);
+            startedPlugins.add(pluginWrapper);
+
+            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
+        } catch (PluginException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return pluginWrapper.getPluginState();
+    }
+
+    /**
+     * Stop all active plugins.
+     */
+    @Override
+    public void stopPlugins() {
+        // stop started plugins in reverse order
+        Collections.reverse(startedPlugins);
+        Iterator<PluginWrapper> itr = startedPlugins.iterator();
+        while (itr.hasNext()) {
+            PluginWrapper pluginWrapper = itr.next();
+            PluginState pluginState = pluginWrapper.getPluginState();
+            if (PluginState.STARTED == pluginState) {
+                try {
+                    PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
+                    log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+                    pluginWrapper.getPlugin().stop();
+                    pluginWrapper.setPluginState(PluginState.STOPPED);
+                    itr.remove();
+
+                    firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
+                } catch (PluginException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Stop the specified plugin and it's dependencies.
+     */
+    @Override
+    public PluginState stopPlugin(String pluginId) {
+        return stopPlugin(pluginId, true);
+    }
+
+    private PluginState stopPlugin(String pluginId, boolean stopDependents) {
+        if (!plugins.containsKey(pluginId)) {
+            throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
+        }
+
+        PluginWrapper pluginWrapper = getPlugin(pluginId);
+        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
+        PluginState pluginState = pluginWrapper.getPluginState();
+        if (PluginState.STOPPED == pluginState) {
+            log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+            return PluginState.STOPPED;
+        }
+
+        // test for disabled plugin
+        if (PluginState.DISABLED == pluginState) {
+            // do nothing
+            return pluginState;
+        }
+
+        if (stopDependents) {
+            List<String> dependents = dependencyResolver.getDependents(pluginId);
+            while (!dependents.isEmpty()) {
+                String dependent = dependents.remove(0);
+                stopPlugin(dependent, false);
+                dependents.addAll(0, dependencyResolver.getDependents(dependent));
+            }
+        }
+
+        try {
+            log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+            pluginWrapper.getPlugin().stop();
+            pluginWrapper.setPluginState(PluginState.STOPPED);
+            startedPlugins.remove(pluginWrapper);
+
+            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
+        } catch (PluginException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return pluginWrapper.getPluginState();
+    }
+
+    @Override
+    public boolean disablePlugin(String pluginId) {
+        if (!plugins.containsKey(pluginId)) {
+            throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
+        }
+
+        PluginWrapper pluginWrapper = getPlugin(pluginId);
+        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
+        PluginState pluginState = pluginWrapper.getPluginState();
+        if (PluginState.DISABLED == pluginState) {
+            log.debug("Already disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+            return true;
+        }
+
+        if (PluginState.STOPPED == stopPlugin(pluginId)) {
+            pluginWrapper.setPluginState(PluginState.DISABLED);
+
+            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
+
+            if (!pluginStatusProvider.disablePlugin(pluginId)) {
+                return false;
+            }
+
+            log.info("Disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean enablePlugin(String pluginId) {
+        if (!plugins.containsKey(pluginId)) {
+            throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
+        }
+
+        PluginWrapper pluginWrapper = getPlugin(pluginId);
+        if (!isPluginValid(pluginWrapper)) {
+            log.warn("Plugin '{}:{}' can not be enabled", pluginWrapper.getPluginId(),
+                pluginWrapper.getDescriptor().getVersion());
+            return false;
+        }
+
+        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
+        PluginState pluginState = pluginWrapper.getPluginState();
+        if (PluginState.DISABLED != pluginState) {
+            log.debug("Plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+            return true;
+        }
+
+        if (!pluginStatusProvider.enablePlugin(pluginId)) {
+            return false;
+        }
+
+        pluginWrapper.setPluginState(PluginState.CREATED);
+
+        firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
+
+        log.info("Enabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
+
+        return true;
+    }
+
+    /**
+     * Get plugin class loader for this path.
+     */
+    @Override
+    public ClassLoader getPluginClassLoader(String pluginId) {
+        return pluginClassLoaders.get(pluginId);
+    }
+
+    @Override
+    public <T> List<T> getExtensions(Class<T> type) {
+        List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
+        List<T> extensions = new ArrayList<>(extensionsWrapper.size());
+        for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
+            extensions.add(extensionWrapper.getExtension());
+        }
+
+        return extensions;
+    }
+
+    @Override
+    public <T> List<T> getExtensions(Class<T> type, String pluginId) {
+        List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type, pluginId);
+        List<T> extensions = new ArrayList<>(extensionsWrapper.size());
+        for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
+            extensions.add(extensionWrapper.getExtension());
+        }
+
+        return extensions;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List getExtensions(String pluginId) {
+        List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
+        List extensions = new ArrayList<>(extensionsWrapper.size());
+        for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
+            extensions.add(extensionWrapper.getExtension());
+        }
+
+        return extensions;
+    }
+
+    @Override
+    public Set<String> getExtensionClassNames(String pluginId) {
+        return extensionFinder.findClassNames(pluginId);
+    }
+
+    @Override
+    public ExtensionFactory getExtensionFactory() {
+        return extensionFactory;
+    }
+
+    // TODO remove
+    public PluginLoader getPluginLoader() {
+        return pluginLoader;
+    }
+
+    public Path getPluginsRoot() {
+        return pluginsRoot;
+    }
+
+    @Override
+    public RuntimeMode getRuntimeMode() {
+        if (runtimeMode == null) {
+            // retrieves the runtime mode from system
+            String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
+            runtimeMode = RuntimeMode.byName(modeAsString);
+        }
+
+        return runtimeMode;
+    }
+
+    @Override
+    public PluginWrapper whichPlugin(Class<?> clazz) {
+        ClassLoader classLoader = clazz.getClassLoader();
+        for (PluginWrapper plugin : resolvedPlugins) {
+            if (plugin.getPluginClassLoader() == classLoader) {
+                return plugin;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public synchronized void addPluginStateListener(PluginStateListener listener) {
+        pluginStateListeners.add(listener);
+    }
+
+    @Override
+    public synchronized void removePluginStateListener(PluginStateListener listener) {
+        pluginStateListeners.remove(listener);
+    }
+
+    public Version getVersion() {
+        String version = null;
+
+        Package pf4jPackage = getClass().getPackage();
+        if (pf4jPackage != null) {
+            version = pf4jPackage.getImplementationVersion();
+            if (version == null) {
+                version = pf4jPackage.getSpecificationVersion();
+            }
+        }
+
+        return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0);
+    }
+
+    protected abstract PluginRepository createPluginRepository();
+
+    protected abstract PluginFactory createPluginFactory();
+
+    protected abstract ExtensionFactory createExtensionFactory();
+
+    protected abstract PluginDescriptorFinder createPluginDescriptorFinder();
+
+    protected abstract ExtensionFinder createExtensionFinder();
+
+    protected abstract PluginStatusProvider createPluginStatusProvider();
+
+    protected abstract PluginLoader createPluginLoader();
+
+    protected PluginDescriptorFinder getPluginDescriptorFinder() {
+        return pluginDescriptorFinder;
+    }
+
+    protected PluginFactory getPluginFactory() {
+        return pluginFactory;
+    }
+
+    protected Map<String, ClassLoader> getPluginClassLoaders() {
+        return pluginClassLoaders;
+    }
+
+    protected void initialize() {
+        plugins = new HashMap<>();
+        pluginClassLoaders = new HashMap<>();
+        unresolvedPlugins = new ArrayList<>();
+        resolvedPlugins = new ArrayList<>();
+        startedPlugins = new ArrayList<>();
+
+        pluginStateListeners = new ArrayList<>();
+        pathToIdMap = new HashMap<>();
+
+        if (pluginsRoot == null) {
+            pluginsRoot = createPluginsRoot();
+        }
+
+        System.setProperty("pf4j.pluginsDir", pluginsRoot.toString());
+
+        dependencyResolver = new DependencyResolver();
+
+        pluginRepository = createPluginRepository();
+        pluginFactory = createPluginFactory();
+        extensionFactory = createExtensionFactory();
+        pluginDescriptorFinder = createPluginDescriptorFinder();
+        extensionFinder = createExtensionFinder();
+        pluginStatusProvider = createPluginStatusProvider();
+        pluginLoader = createPluginLoader();
+    }
+
+    /**
+     * Add the possibility to override the plugins root.
+     * If a "pf4j.pluginsDir" system property is defined than this method returns
+     * that root.
+     * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
+     * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns
+     * DEFAULT_PLUGINS_DIRECTORY ("plugins").
+     * @return
+     */
+    protected Path createPluginsRoot() {
+        String pluginsDir = System.getProperty("pf4j.pluginsDir");
+        if (pluginsDir == null) {
+            if (isDevelopment()) {
+                pluginsDir = "../plugins";
+            } else {
+                pluginsDir = "plugins";
+            }
+        }
+
+        return Paths.get(pluginsDir);
+    }
+
+    protected boolean isPluginValid(PluginWrapper pluginWrapper) {
+        Expression requires = pluginWrapper.getDescriptor().getRequires();
+        Version system = getSystemVersion();
+        if (requires.interpret(system)) {
+            return true;
+        }
+
+        log.warn("Plugin '{}:{}' requires a minimum system version of {}",
+            pluginWrapper.getPluginId(),
+            pluginWrapper.getDescriptor().getVersion(),
+            requires);
+
+        return false;
+    }
+
+    protected boolean isPluginDisabled(String pluginId) {
+        return pluginStatusProvider.isPluginDisabled(pluginId);
+    }
+
+    protected void resolvePlugins() throws PluginException {
+        resolveDependencies();
+    }
+
+    protected void resolveDependencies() throws PluginException {
+        dependencyResolver.resolve(unresolvedPlugins);
+        resolvedPlugins = dependencyResolver.getSortedPlugins();
+        for (PluginWrapper pluginWrapper : resolvedPlugins) {
+            unresolvedPlugins.remove(pluginWrapper);
+            log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
+        }
+    }
+
+    protected synchronized void firePluginStateEvent(PluginStateEvent event) {
+        for (PluginStateListener listener : pluginStateListeners) {
+            log.debug("Fire '{}' to '{}'", event, listener);
+            listener.pluginStateChanged(event);
+        }
+    }
+
+    protected PluginWrapper loadPluginFromPath(Path pluginPath) throws PluginException {
+        // test for plugin duplication
+        if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
+            return null;
+        }
+
+        // retrieves the plugin descriptor
+        log.debug("Find plugin descriptor '{}'", pluginPath);
+        PluginDescriptor pluginDescriptor = getPluginDescriptorFinder().find(pluginPath);
+        log.debug("Descriptor {}", pluginDescriptor);
+        String pluginClassName = pluginDescriptor.getPluginClass();
+        log.debug("Class '{}' for plugin '{}'",  pluginClassName, pluginPath);
+
+        // load plugin
+        log.debug("Loading plugin '{}'", pluginPath);
+        ClassLoader pluginClassLoader = getPluginLoader().loadPlugin(pluginPath, pluginDescriptor);
+        log.debug("Loaded plugin '{}' with class loader '{}'", pluginPath, pluginClassLoader);
+
+        // create the plugin wrapper
+        log.debug("Creating wrapper for plugin '{}'", pluginPath);
+        PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
+        pluginWrapper.setPluginFactory(getPluginFactory());
+        pluginWrapper.setRuntimeMode(getRuntimeMode());
+
+        // test for disabled plugin
+        if (isPluginDisabled(pluginDescriptor.getPluginId())) {
+            log.info("Plugin '{}' is disabled", pluginPath);
+            pluginWrapper.setPluginState(PluginState.DISABLED);
+        }
+
+        // validate the plugin
+        if (!isPluginValid(pluginWrapper)) {
+            log.info("Plugin '{}' is disabled", pluginPath);
+            pluginWrapper.setPluginState(PluginState.DISABLED);
+        }
+
+        log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
+
+        String pluginId = pluginDescriptor.getPluginId();
+
+        // add plugin to the list with plugins
+        plugins.put(pluginId, pluginWrapper);
+        getUnresolvedPlugins().add(pluginWrapper);
+
+        // add plugin class loader to the list with class loaders
+        getPluginClassLoaders().put(pluginId, pluginClassLoader);
+
+        return pluginWrapper;
+    }
+
+    // TODO add this method in PluginManager as default method for Java 8.
+    protected boolean isDevelopment() {
+        return RuntimeMode.DEVELOPMENT.equals(getRuntimeMode());
+    }
+
+}
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/BasePluginRepository.java b/pf4j/src/main/java/ro/fortsoft/pf4j/BasePluginRepository.java
new file mode 100644 (file)
index 0000000..dd9fc80
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 Decebal Suiu
+ *
+ * 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 ro.fortsoft.pf4j;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Decebal Suiu
+ * @author Mário Franco
+ */
+public class BasePluginRepository implements PluginRepository {
+
+    protected final Path pluginsRoot;
+    protected FileFilter filter;
+
+    public BasePluginRepository(Path pluginsRoot) {
+        this.pluginsRoot = pluginsRoot;
+    }
+
+    public BasePluginRepository(Path pluginsRoot, FileFilter filter) {
+        this.pluginsRoot = pluginsRoot;
+        this.filter = filter;
+    }
+
+    public void setFilter(FileFilter filter) {
+        this.filter = filter;
+    }
+
+    @Override
+    public List<Path> getPluginPaths() {
+        File[] files = pluginsRoot.toFile().listFiles(filter);
+
+        if ((files == null) || files.length == 0) {
+            return Collections.emptyList();
+        }
+
+        List<Path> paths = new ArrayList<>(files.length);
+        for (File file : files) {
+            paths.add(file.toPath());
+        }
+
+        return paths;
+    }
+
+    @Override
+    public boolean deletePluginPath(Path pluginPath) {
+        try {
+            return Files.deleteIfExists(pluginPath);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
index 6556f05bc47cbf59a34ee55feb7baf1e532a2a2f..b0341ead3e9ce1c51ee720d932bfcff27491ceca 100644 (file)
@@ -15,7 +15,7 @@
  */
 package ro.fortsoft.pf4j;
 
-import java.io.File;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -32,19 +32,19 @@ public class CompoundPluginRepository implements PluginRepository {
     }
 
     @Override
-    public List<File> getPluginArchives() {
-        List<File> file = new ArrayList<>();
+    public List<Path> getPluginPaths() {
+        List<Path> paths = new ArrayList<>();
         for (PluginRepository repository : repositories) {
-            file.addAll(repository.getPluginArchives());
+            paths.addAll(repository.getPluginPaths());
         }
 
-        return file;
+        return paths;
     }
 
     @Override
-    public boolean deletePluginArchive(String pluginPath) {
+    public boolean deletePluginPath(Path pluginPath) {
         for (PluginRepository repository : repositories) {
-            if (repository.deletePluginArchive(pluginPath)) {
+            if (repository.deletePluginPath(pluginPath)) {
                 return true;
             }
         }
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/CyclicDependencyException.java b/pf4j/src/main/java/ro/fortsoft/pf4j/CyclicDependencyException.java
deleted file mode 100644 (file)
index 16b6e56..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2012 Decebal Suiu
- *
- * 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 ro.fortsoft.pf4j;
-
-/**
- * CyclicDependencyException will be thrown if a cyclic dependency is detected.
- *
- * @author Decebal Suiu
- */
-class CyclicDependencyException extends PluginException {
-
-       private static final long serialVersionUID = 1L;
-
-       public CyclicDependencyException(String message) {
-               super(message);
-       }
-
-}
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginClasspath.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginClasspath.java
new file mode 100644 (file)
index 0000000..71631c1
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Decebal Suiu
+ *
+ * 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 ro.fortsoft.pf4j;
+
+/**
+ * The default values are {@code classes} and {@code lib}.
+ *
+ * @author Decebal Suiu
+ */
+public class DefaultPluginClasspath extends PluginClasspath {
+
+    public DefaultPluginClasspath() {
+        super();
+
+        addClassesDirectories("classes");
+        addLibDirectories("lib");
+    }
+
+}
index 84abcba395e5bc887f94331b9ce3ab469ee2bcf6..e58aa05ff8b996370e066242822d477a14a19e3e 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.jar.Manifest;
+
 /**
- * The default implementation for PluginDescriptorFinder.
- * Now, this class it's a "link" to {@link ro.fortsoft.pf4j.ManifestPluginDescriptorFinder}.
+ * The default implementation for {@link PluginDescriptorFinder}.
+ * Now, this class it's a "link" to {@link ManifestPluginDescriptorFinder}.
  *
  * @author Decebal Suiu
  */
 public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
 
-       public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
-               super(pluginClasspath);
-       }
+    private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
+
+    private PluginClasspath pluginClasspath;
+
+    public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
+        this.pluginClasspath = pluginClasspath;
+    }
+
+       @Override
+    public Manifest readManifest(Path pluginPath) throws PluginException {
+        // TODO it's ok with first classes root? Another idea is to specify in PluginClasspath the folder.
+        String classes = pluginClasspath.getClassesDirectories().get(0);
+        Path manifestPath = pluginPath.resolve(Paths.get(classes,"/META-INF/MANIFEST.MF"));
+        log.debug("Lookup plugin descriptor in '{}'", manifestPath);
+        if (Files.notExists(manifestPath)) {
+            throw new PluginException("Cannot find '" + manifestPath + "' path");
+        }
+
+        try (InputStream input = Files.newInputStream(manifestPath)) {
+            return new Manifest(input);
+        } catch (IOException e) {
+            throw new PluginException(e.getMessage(), e);
+        }
+    }
 
 }
index 76c2760a8aa606f48ed9355a306d373250eebf2f..9bc3173ba9bedecf0f894444de9f45c1e411400b 100644 (file)
@@ -22,8 +22,8 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Modifier;
 
 /**
- * The default implementation for PluginFactory.
- * It uses Class.newInstance() method.
+ * The default implementation for {@link PluginFactory}.
+ * It uses {@link Class#newInstance()} method.
  *
  * @author Decebal Suiu
  */
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginLoader.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginLoader.java
new file mode 100644 (file)
index 0000000..88bbbef
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 Decebal Suiu
+ *
+ * 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 ro.fortsoft.pf4j;
+
+import ro.fortsoft.pf4j.util.FileUtils;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * Load all information needed by a plugin.
+ * This means add to classpath all jar files from {@code lib} directory
+ * and all class files from {@code classes}.
+ *
+ * @author Decebal Suiu
+ */
+public class DefaultPluginLoader implements PluginLoader {
+
+    protected PluginManager pluginManager;
+    protected PluginClasspath pluginClasspath;
+
+    public DefaultPluginLoader(PluginManager pluginManager, PluginClasspath pluginClasspath) {
+        this.pluginManager = pluginManager;
+        this.pluginClasspath = pluginClasspath;
+    }
+
+    @Override
+    public ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor) {
+        PluginClassLoader pluginClassLoader = createPluginClassLoader(pluginPath, pluginDescriptor);
+
+        loadClasses(pluginPath, pluginClassLoader);
+        loadJars(pluginPath, pluginClassLoader);
+
+        return pluginClassLoader;
+    }
+
+    protected PluginClassLoader createPluginClassLoader(Path pluginPath, PluginDescriptor pluginDescriptor) {
+        return new PluginClassLoader(pluginManager, pluginDescriptor, getClass().getClassLoader());
+    }
+
+    /**
+     * Add all {@code *.class} files from {@code classes} directories to plugin class loader.
+     */
+    protected void loadClasses(Path pluginPath, PluginClassLoader pluginClassLoader) {
+        for (String directory : pluginClasspath.getClassesDirectories()) {
+            File file = pluginPath.resolve(directory).toFile();
+            if (file.exists() && file.isDirectory()) {
+                pluginClassLoader.addFile(file);
+            }
+        }
+    }
+
+    /**
+     * Add all {@code *.jar} files from {@code lib} directories to plugin class loader.
+     */
+    protected void loadJars(Path pluginPath, PluginClassLoader pluginClassLoader) {
+        for (String libDirectory : pluginClasspath.getLibDirectories()) {
+            File file = pluginPath.resolve(libDirectory).toFile();
+            List<File> jars = FileUtils.getJars(file);
+            for (File jar : jars) {
+                pluginClassLoader.addFile(jar);
+            }
+        }
+    }
+
+}
index 60b09e8a6173a3031af7ee9ff94e8bb8362323c8..81bce1b2de95e2c92da4734f0e19c0e283400812 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
-import com.github.zafarkhaja.semver.Version;
-import com.github.zafarkhaja.semver.expr.Expression;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import ro.fortsoft.pf4j.util.AndFileFilter;
-import ro.fortsoft.pf4j.util.DirectoryFileFilter;
-import ro.fortsoft.pf4j.util.FileUtils;
-import ro.fortsoft.pf4j.util.HiddenFilter;
-import ro.fortsoft.pf4j.util.NameFileFilter;
-import ro.fortsoft.pf4j.util.NotFileFilter;
-import ro.fortsoft.pf4j.util.OrFileFilter;
-import ro.fortsoft.pf4j.util.Unzip;
-import ro.fortsoft.pf4j.util.ZipFileFilter;
 
 import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.nio.file.Path;
 
 /**
- * Default implementation of the PluginManager interface.
+ * Default implementation of the {@link PluginManager} interface.
  *
  * @author Decebal Suiu
  */
-public class DefaultPluginManager implements PluginManager {
+public class DefaultPluginManager extends AbstractPluginManager {
 
        private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
 
-       public static final String DEFAULT_PLUGINS_DIRECTORY = "plugins";
-       public static final String DEVELOPMENT_PLUGINS_DIRECTORY = "../plugins";
-
-    protected File pluginsDirectory;
-
-    protected ExtensionFinder extensionFinder;
-
-    protected PluginDescriptorFinder pluginDescriptorFinder;
-
     protected PluginClasspath pluginClasspath;
 
-    /**
-     * A map of plugins this manager is responsible for (the key is the 'pluginId').
-     */
-    protected Map<String, PluginWrapper> plugins;
-
-    /**
-     * A map of plugin class loaders (he key is the 'pluginId').
-     */
-    protected Map<String, PluginClassLoader> pluginClassLoaders;
-
-    /**
-     * A relation between 'pluginPath' and 'pluginId'
-     */
-    protected Map<String, String> pathToIdMap;
-
-    /**
-     * A list with unresolved plugins (unresolved dependency).
-     */
-    protected List<PluginWrapper> unresolvedPlugins;
-
-    /**
-     * A list with resolved plugins (resolved dependency).
-     */
-    protected List<PluginWrapper> resolvedPlugins;
-
-    /**
-     * A list with started plugins.
-     */
-    protected List<PluginWrapper> startedPlugins;
-
-    /**
-     * The registered {@link PluginStateListener}s.
-     */
-    protected List<PluginStateListener> pluginStateListeners;
-
-    /**
-     * Cache value for the runtime mode. No need to re-read it because it wont change at
-        * runtime.
-     */
-    protected RuntimeMode runtimeMode;
-
-    /**
-     * The system version used for comparisons to the plugin requires attribute.
-     */
-    protected Version systemVersion = Version.forIntegers(0, 0, 0);
-
-    protected PluginFactory pluginFactory;
-    protected ExtensionFactory extensionFactory;
-    protected PluginStatusProvider pluginStatusProvider;
-    protected DependencyResolver dependencyResolver;
-
-    /**
-     * The plugins repository.
-     */
-    protected PluginRepository pluginRepository;
-
-    /**
-     * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
-     */
     public DefaultPluginManager() {
-        this.pluginsDirectory = createPluginsDirectory();
-
-        initialize();
-    }
-
-    /**
-     * Constructs DefaultPluginManager which the given plugins directory.
-     *
-     * @param pluginsDirectory the directory to search for plugins
-     */
-    public DefaultPluginManager(File pluginsDirectory) {
-        this.pluginsDirectory = pluginsDirectory;
-
-        initialize();
-    }
-
-    @Override
-    public void setSystemVersion(Version version) {
-       systemVersion = version;
-    }
-
-    @Override
-    public Version getSystemVersion() {
-       return systemVersion;
-    }
-
-       @Override
-    public List<PluginWrapper> getPlugins() {
-        return new ArrayList<>(plugins.values());
-    }
-
-    @Override
-    public List<PluginWrapper> getPlugins(PluginState pluginState) {
-        List<PluginWrapper> plugins= new ArrayList<>();
-        for (PluginWrapper plugin : getPlugins()) {
-            if (pluginState.equals(plugin.getPluginState())) {
-                plugins.add(plugin);
-            }
-        }
-
-        return plugins;
-    }
-
-    @Override
-       public List<PluginWrapper> getResolvedPlugins() {
-               return resolvedPlugins;
-       }
-
-       @Override
-    public List<PluginWrapper> getUnresolvedPlugins() {
-               return unresolvedPlugins;
-       }
-
-       @Override
-       public List<PluginWrapper> getStartedPlugins() {
-               return startedPlugins;
-       }
-
-    @Override
-    public PluginWrapper getPlugin(String pluginId) {
-        return plugins.get(pluginId);
-    }
-
-       @Override
-       public String loadPlugin(File pluginArchiveFile) {
-               if ((pluginArchiveFile == null) || !pluginArchiveFile.exists()) {
-                       throw new IllegalArgumentException(String.format("Specified plugin %s does not exist!", pluginArchiveFile));
-               }
-
-        log.debug("Loading plugin from '{}'", pluginArchiveFile);
-
-               File pluginDirectory = null;
-               try {
-                       pluginDirectory = expandPluginArchive(pluginArchiveFile);
-               } catch (IOException e) {
-                       log.error(e.getMessage(), e);
-               }
-               if ((pluginDirectory == null) || !pluginDirectory.exists()) {
-                       throw new IllegalArgumentException(String.format("Failed to expand %s", pluginArchiveFile));
-               }
-
-               try {
-                       PluginWrapper pluginWrapper = loadPluginDirectory(pluginDirectory);
-                       // TODO uninstalled plugin dependencies?
-               unresolvedPlugins.remove(pluginWrapper);
-               resolvedPlugins.add(pluginWrapper);
-
-            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, null));
-
-                       return pluginWrapper.getDescriptor().getPluginId();
-               } catch (PluginException e) {
-                       log.error(e.getMessage(), e);
-               }
-
-               return null;
-       }
-
-    /**
-     * Start all active plugins.
-     */
-       @Override
-    public void startPlugins() {
-        for (PluginWrapper pluginWrapper : resolvedPlugins) {
-            PluginState pluginState = pluginWrapper.getPluginState();
-            if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
-                try {
-                    PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
-                    log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-                    pluginWrapper.getPlugin().start();
-                    pluginWrapper.setPluginState(PluginState.STARTED);
-                    startedPlugins.add(pluginWrapper);
-
-                    firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-                } catch (PluginException e) {
-                    log.error(e.getMessage(), e);
-                }
-            }
-        }
-    }
-
-       /**
-     * Start the specified plugin and it's dependencies.
-     */
-    @Override
-    public PluginState startPlugin(String pluginId) {
-       if (!plugins.containsKey(pluginId)) {
-               throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
-       }
-
-       PluginWrapper pluginWrapper = getPlugin(pluginId);
-       PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
-        PluginState pluginState = pluginWrapper.getPluginState();
-       if (PluginState.STARTED == pluginState) {
-               log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-               return PluginState.STARTED;
-       }
-
-        if (PluginState.DISABLED == pluginState) {
-            // automatically enable plugin on manual plugin start
-            if (!enablePlugin(pluginId)) {
-                return pluginState;
-            }
-        }
-
-        for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
-               startPlugin(dependency.getPluginId());
-       }
-
-       try {
-               log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-               pluginWrapper.getPlugin().start();
-               pluginWrapper.setPluginState(PluginState.STARTED);
-               startedPlugins.add(pluginWrapper);
-
-            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-        } catch (PluginException e) {
-               log.error(e.getMessage(), e);
-       }
-
-       return pluginWrapper.getPluginState();
-    }
-
-    /**
-     * Stop all active plugins.
-     */
-    @Override
-    public void stopPlugins() {
-       // stop started plugins in reverse order
-       Collections.reverse(startedPlugins);
-       Iterator<PluginWrapper> itr = startedPlugins.iterator();
-        while (itr.hasNext()) {
-               PluginWrapper pluginWrapper = itr.next();
-            PluginState pluginState = pluginWrapper.getPluginState();
-            if (PluginState.STARTED == pluginState) {
-                try {
-                    PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
-                    log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-                    pluginWrapper.getPlugin().stop();
-                    pluginWrapper.setPluginState(PluginState.STOPPED);
-                    itr.remove();
-
-                    firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-                } catch (PluginException e) {
-                    log.error(e.getMessage(), e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Stop the specified plugin and it's dependencies.
-     */
-    @Override
-    public PluginState stopPlugin(String pluginId) {
-        return stopPlugin(pluginId, true);
+        super();
     }
 
-    private PluginState stopPlugin(String pluginId, boolean stopDependents) {
-       if (!plugins.containsKey(pluginId)) {
-               throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
-       }
-
-       PluginWrapper pluginWrapper = getPlugin(pluginId);
-       PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
-        PluginState pluginState = pluginWrapper.getPluginState();
-       if (PluginState.STOPPED == pluginState) {
-               log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-               return PluginState.STOPPED;
-       }
-
-        // test for disabled plugin
-        if (PluginState.DISABLED == pluginState) {
-            // do nothing
-            return pluginState;
-        }
-
-        if (stopDependents) {
-            List<String> dependents = dependencyResolver.getDependents(pluginId);
-            while (!dependents.isEmpty()) {
-                String dependent = dependents.remove(0);
-                stopPlugin(dependent, false);
-                dependents.addAll(0, dependencyResolver.getDependents(dependent));
-            }
-        }
-
-       try {
-               log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-               pluginWrapper.getPlugin().stop();
-               pluginWrapper.setPluginState(PluginState.STOPPED);
-               startedPlugins.remove(pluginWrapper);
-
-            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-       } catch (PluginException e) {
-               log.error(e.getMessage(), e);
-       }
-
-       return pluginWrapper.getPluginState();
-    }
-
-    /**
-     * Load plugins.
-     */
-    @Override
-    public void loadPlugins() {
-       log.debug("Lookup plugins in '{}'", pluginsDirectory.getAbsolutePath());
-       // check for plugins directory
-        if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
-            log.error("No '{}' directory", pluginsDirectory.getAbsolutePath());
-            return;
-        }
-
-        // expand all plugin archives
-        List<File> pluginArchives = pluginRepository.getPluginArchives();
-        for (File archiveFile : pluginArchives) {
-            try {
-                expandPluginArchive(archiveFile);
-            } catch (IOException e) {
-                log.error(e.getMessage(), e);
-            }
-        }
-
-        // check for no plugins
-        AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
-        pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter()));
-        File[] directories = pluginsDirectory.listFiles(pluginsFilter);
-        if (directories == null) {
-               directories = new File[0];
-        }
-        log.debug("Found {} possible plugins: {}", directories.length, directories);
-        if (directories.length == 0) {
-               log.info("No plugins");
-               return;
-        }
-
-        // load any plugin from plugins directory
-               for (File directory : directories) {
-                       try {
-                               loadPluginDirectory(directory);
-               } catch (PluginException e) {
-                       log.error(e.getMessage(), e);
-               }
-               }
-
-        // resolve 'unresolvedPlugins'
-        try {
-                       resolvePlugins();
-               } catch (PluginException e) {
-                       log.error(e.getMessage(), e);
-               }
-    }
-
-    @Override
-    public boolean unloadPlugin(String pluginId) {
-       try {
-               PluginState pluginState = stopPlugin(pluginId);
-               if (PluginState.STARTED == pluginState) {
-                       return false;
-               }
-
-               PluginWrapper pluginWrapper = getPlugin(pluginId);
-               PluginDescriptor descriptor = pluginWrapper.getDescriptor();
-               List<PluginDependency> dependencies = descriptor.getDependencies();
-               for (PluginDependency dependency : dependencies) {
-                       if (!unloadPlugin(dependency.getPluginId())) {
-                               return false;
-                       }
-               }
-
-               // remove the plugin
-               plugins.remove(pluginId);
-               resolvedPlugins.remove(pluginWrapper);
-               pathToIdMap.remove(pluginWrapper.getPluginPath());
-
-            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-
-               // remove the classloader
-               if (pluginClassLoaders.containsKey(pluginId)) {
-                       PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId);
-                classLoader.dispose();
-               }
-
-               return true;
-       } catch (IllegalArgumentException e) {
-               // ignore not found exceptions because this method is recursive
-       }
-
-       return false;
-    }
-
-    @Override
-    public boolean disablePlugin(String pluginId) {
-        if (!plugins.containsKey(pluginId)) {
-            throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
-        }
-
-        PluginWrapper pluginWrapper = getPlugin(pluginId);
-        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
-        PluginState pluginState = pluginWrapper.getPluginState();
-        if (PluginState.DISABLED == pluginState) {
-               log.debug("Already disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-               return true;
-        }
-
-        if (PluginState.STOPPED == stopPlugin(pluginId)) {
-            pluginWrapper.setPluginState(PluginState.DISABLED);
-
-            firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
-
-            if (!pluginStatusProvider.disablePlugin(pluginId)) {
-                return false;
-            }
-
-            log.info("Disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-
-            return true;
-        }
-
-        return false;
+    @Deprecated
+    public DefaultPluginManager(File pluginsDir) {
+        this(pluginsDir.toPath());
     }
 
-    @Override
-    public boolean enablePlugin(String pluginId) {
-        if (!plugins.containsKey(pluginId)) {
-            throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
-        }
-
-        PluginWrapper pluginWrapper = getPlugin(pluginId);
-               if (!isPluginValid(pluginWrapper)) {
-               log.warn("Plugin '{}:{}' can not be enabled", pluginWrapper.getPluginId(),
-                               pluginWrapper.getDescriptor().getVersion());
-            return false;
-        }
-
-        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
-        PluginState pluginState = pluginWrapper.getPluginState();
-        if (PluginState.DISABLED != pluginState) {
-            log.debug("Plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-            return true;
-        }
-
-        if (!pluginStatusProvider.enablePlugin(pluginId)) {
-            return false;
-        }
-
-        pluginWrapper.setPluginState(PluginState.CREATED);
-
-        firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-
-        log.info("Enabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-
-        return true;
+    public DefaultPluginManager(Path pluginsRoot) {
+        super(pluginsRoot);
     }
 
-    @Override
-       public boolean deletePlugin(String pluginId) {
-       if (!plugins.containsKey(pluginId)) {
-               throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
-       }
-
-               PluginWrapper pluginWrapper = getPlugin(pluginId);
-               PluginState pluginState = stopPlugin(pluginId);
-               if (PluginState.STARTED == pluginState) {
-                       log.error("Failed to stop plugin {} on delete", pluginId);
-                       return false;
-               }
-
-               if (!unloadPlugin(pluginId)) {
-                       log.error("Failed to unload plugin {} on delete", pluginId);
-                       return false;
-               }
-
-               File pluginFolder = new File(pluginsDirectory, pluginWrapper.getPluginPath());
-
-               if (pluginFolder.exists()) {
-                       FileUtils.delete(pluginFolder);
-               }
-
-        pluginRepository.deletePluginArchive(pluginWrapper.getPluginPath());
-
-               return true;
-       }
-
     /**
-     * Get plugin class loader for this path.
-     */
-    @Override
-    public PluginClassLoader getPluginClassLoader(String pluginId) {
-       return pluginClassLoaders.get(pluginId);
-    }
-
-    @Override
-       public <T> List<T> getExtensions(Class<T> type) {
-               List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
-               List<T> extensions = new ArrayList<>(extensionsWrapper.size());
-               for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
-                       extensions.add(extensionWrapper.getExtension());
-               }
-
-               return extensions;
-       }
-
+        * By default if {@link DefaultPluginManager#isDevelopment()} returns true
+     * than a {@link PropertiesPluginDescriptorFinder} is returned
+     * else this method returns {@link DefaultPluginDescriptorFinder}.
+        */
     @Override
-    public <T> List<T> getExtensions(Class<T> type, String pluginId) {
-        List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type, pluginId);
-        List<T> extensions = new ArrayList<>(extensionsWrapper.size());
-        for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
-            extensions.add(extensionWrapper.getExtension());
-        }
-
-        return extensions;
+    protected PluginDescriptorFinder createPluginDescriptorFinder() {
+       return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new DefaultPluginDescriptorFinder(pluginClasspath);
     }
 
     @Override
-    @SuppressWarnings("unchecked")
-    public List getExtensions(String pluginId) {
-        List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
-        List extensions = new ArrayList<>(extensionsWrapper.size());
-        for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
-            extensions.add(extensionWrapper.getExtension());
-        }
+    protected ExtensionFinder createExtensionFinder() {
+       DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this);
+        addPluginStateListener(extensionFinder);
 
-        return extensions;
+        return extensionFinder;
     }
 
     @Override
-    public Set<String> getExtensionClassNames(String pluginId) {
-        return extensionFinder.findClassNames(pluginId);
+    protected PluginFactory createPluginFactory() {
+        return new DefaultPluginFactory();
     }
 
     @Override
-    public ExtensionFactory getExtensionFactory() {
-        return extensionFactory;
+    protected ExtensionFactory createExtensionFactory() {
+        return new DefaultExtensionFactory();
     }
 
     @Override
-       public RuntimeMode getRuntimeMode() {
-       if (runtimeMode == null) {
-               // retrieves the runtime mode from system
-               String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
-               runtimeMode = RuntimeMode.byName(modeAsString);
-       }
-
-               return runtimeMode;
-       }
-
-    @Override
-    public PluginWrapper whichPlugin(Class<?> clazz) {
-        ClassLoader classLoader = clazz.getClassLoader();
-        for (PluginWrapper plugin : resolvedPlugins) {
-            if (plugin.getPluginClassLoader() == classLoader) {
-               return plugin;
-            }
-        }
-
-        return null;
+    protected PluginStatusProvider createPluginStatusProvider() {
+        return new DefaultPluginStatusProvider(getPluginsRoot());
     }
 
     @Override
-    public synchronized void addPluginStateListener(PluginStateListener listener) {
-        pluginStateListeners.add(listener);
+    protected PluginRepository createPluginRepository() {
+        return new DefaultPluginRepository(getPluginsRoot(), isDevelopment());
     }
 
     @Override
-    public synchronized void removePluginStateListener(PluginStateListener listener) {
-        pluginStateListeners.remove(listener);
-    }
-
-    public Version getVersion() {
-        String version = null;
-
-        Package pf4jPackage = getClass().getPackage();
-        if (pf4jPackage != null) {
-            version = pf4jPackage.getImplementationVersion();
-            if (version == null) {
-                version = pf4jPackage.getSpecificationVersion();
-            }
-        }
-
-        return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0);
+    protected PluginLoader createPluginLoader() {
+        return new DefaultPluginLoader(this, pluginClasspath);
     }
 
     /**
-        * Add the possibility to override the PluginDescriptorFinder.
-        * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
-        * PropertiesPluginDescriptorFinder is returned else this method returns
-        * DefaultPluginDescriptorFinder.
-        */
-    protected PluginDescriptorFinder createPluginDescriptorFinder() {
-       if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
-               return new PropertiesPluginDescriptorFinder();
-       }
-
-       return new DefaultPluginDescriptorFinder(pluginClasspath);
-    }
-
-    /**
-     * Add the possibility to override the ExtensionFinder.
-     */
-    protected ExtensionFinder createExtensionFinder() {
-       DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this);
-        addPluginStateListener(extensionFinder);
-
-        return extensionFinder;
-    }
-
-    /**
-     * Add the possibility to override the PluginClassPath.
-     * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
-        * DevelopmentPluginClasspath is returned else this method returns
-        * PluginClasspath.
+     * By default if {@link DefaultPluginManager#isDevelopment()} returns true
+     * than a {@link DevelopmentPluginClasspath} is returned
+     * else this method returns {@link DefaultPluginClasspath}.
      */
     protected PluginClasspath createPluginClasspath() {
-       if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
-               return new DevelopmentPluginClasspath();
-       }
-
-       return new PluginClasspath();
-    }
-
-    protected PluginStatusProvider createPluginStatusProvider() {
-        return new DefaultPluginStatusProvider(pluginsDirectory);
-    }
-
-    protected PluginRepository createPluginRepository() {
-        return new DefaultPluginRepository(pluginsDirectory, new ZipFileFilter());
-    }
-
-    protected boolean isPluginDisabled(String pluginId) {
-       return pluginStatusProvider.isPluginDisabled(pluginId);
-    }
-
-    protected boolean isPluginValid(PluginWrapper pluginWrapper) {
-       Expression requires = pluginWrapper.getDescriptor().getRequires();
-       Version system = getSystemVersion();
-       if (requires.interpret(system)) {
-               return true;
-       }
-
-       log.warn("Plugin '{}:{}' requires a minimum system version of {}",
-                       pluginWrapper.getPluginId(),
-                       pluginWrapper.getDescriptor().getVersion(),
-                       requires);
-
-       return false;
-    }
-
-    protected FileFilter createHiddenPluginFilter() {
-       OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
-
-        if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
-            hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
-        }
-
-        return hiddenPluginFilter;
-    }
-
-    /**
-     * Add the possibility to override the plugins directory.
-     * If a "pf4j.pluginsDir" system property is defined than this method returns
-     * that directory.
-     * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
-        * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns
-        * DEFAULT_PLUGINS_DIRECTORY ("plugins").
-     * @return
-     */
-    protected File createPluginsDirectory() {
-       String pluginsDir = System.getProperty("pf4j.pluginsDir");
-       if (pluginsDir == null) {
-               if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
-                       pluginsDir = DEVELOPMENT_PLUGINS_DIRECTORY;
-               } else {
-                       pluginsDir = DEFAULT_PLUGINS_DIRECTORY;
-               }
-       }
-
-       return new File(pluginsDir);
-    }
-
-    /**
-     * Add the possibility to override the PluginFactory..
-     */
-    protected PluginFactory createPluginFactory() {
-        return new DefaultPluginFactory();
-    }
-
-    /**
-     * Add the possibility to override the ExtensionFactory.
-     */
-    protected ExtensionFactory createExtensionFactory() {
-        return new DefaultExtensionFactory();
-    }
-
-    /**
-     * Add the possibility to override the PluginClassLoader.
-     */
-    protected PluginClassLoader createPluginClassLoader(PluginDescriptor pluginDescriptor) {
-        return new PluginClassLoader(this, pluginDescriptor, getClass().getClassLoader());
+        return isDevelopment() ? new DevelopmentPluginClasspath() : new DefaultPluginClasspath();
     }
 
-    private void initialize() {
-               plugins = new HashMap<>();
-        pluginClassLoaders = new HashMap<>();
-        pathToIdMap = new HashMap<>();
-        unresolvedPlugins = new ArrayList<>();
-        resolvedPlugins = new ArrayList<>();
-        startedPlugins = new ArrayList<>();
-
-        pluginStateListeners = new ArrayList<>();
-
-        dependencyResolver = new DependencyResolver();
-
-        log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
-
+    @Override
+    protected void initialize() {
         pluginClasspath = createPluginClasspath();
-        pluginFactory = createPluginFactory();
-        extensionFactory = createExtensionFactory();
-        pluginDescriptorFinder = createPluginDescriptorFinder();
-        extensionFinder = createExtensionFinder();
-        pluginStatusProvider = createPluginStatusProvider();
-        pluginRepository = createPluginRepository();
-
-        try {
-            pluginsDirectory = pluginsDirectory.getCanonicalFile();
-        } catch (IOException e) {
-            log.error(e.getMessage(), e);
-        }
-        System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath());
-       }
-
-    private PluginWrapper loadPluginDirectory(File pluginDirectory) throws PluginException {
-        // try to load the plugin
-               String pluginName = pluginDirectory.getName();
-        String pluginPath = "/".concat(pluginName);
-
-        // test for plugin duplication
-        if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
-            return null;
-        }
-
-        // retrieves the plugin descriptor
-        log.debug("Find plugin descriptor '{}'", pluginPath);
-        PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory);
-        log.debug("Descriptor " + pluginDescriptor);
-        String pluginClassName = pluginDescriptor.getPluginClass();
-        log.debug("Class '{}' for plugin '{}'",  pluginClassName, pluginPath);
-
-        // load plugin
-        log.debug("Loading plugin '{}'", pluginPath);
-        PluginClassLoader pluginClassLoader = createPluginClassLoader(pluginDescriptor);
-        log.debug("Created class loader '{}'", pluginClassLoader);
-        PluginLoader pluginLoader = new PluginLoader(pluginDirectory, pluginClassLoader, pluginClasspath);
-        pluginLoader.load();
-        log.debug("Loaded plugin '{}'", pluginPath);
-
-        // create the plugin wrapper
-        log.debug("Creating wrapper for plugin '{}'", pluginPath);
-        PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
-        pluginWrapper.setPluginFactory(pluginFactory);
-        pluginWrapper.setRuntimeMode(getRuntimeMode());
-
-        // test for disabled plugin
-        if (isPluginDisabled(pluginDescriptor.getPluginId())) {
-            log.info("Plugin '{}' is disabled", pluginPath);
-            pluginWrapper.setPluginState(PluginState.DISABLED);
-        }
-
-        // validate the plugin
-        if (!isPluginValid(pluginWrapper)) {
-               log.info("Plugin '{}' is disabled", pluginPath);
-               pluginWrapper.setPluginState(PluginState.DISABLED);
-        }
 
-        log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
+        super.initialize();
 
-        String pluginId = pluginDescriptor.getPluginId();
-
-        // add plugin to the list with plugins
-        plugins.put(pluginId, pluginWrapper);
-        unresolvedPlugins.add(pluginWrapper);
-
-        // add plugin class loader to the list with class loaders
-        pluginClassLoaders.put(pluginId, pluginClassLoader);
-
-        return pluginWrapper;
-    }
-
-    private File expandPluginArchive(File pluginArchiveFile) throws IOException {
-       String fileName = pluginArchiveFile.getName();
-        long pluginArchiveDate = pluginArchiveFile.lastModified();
-        String pluginName = fileName.substring(0, fileName.length() - 4);
-        File pluginDirectory = new File(pluginsDirectory, pluginName);
-        // check if exists directory or the '.zip' file is "newer" than directory
-        if (!pluginDirectory.exists() || (pluginArchiveDate > pluginDirectory.lastModified())) {
-               log.debug("Expand plugin archive '{}' in '{}'", pluginArchiveFile, pluginDirectory);
-
-               // do not overwrite an old version, remove it
-               if (pluginDirectory.exists()) {
-                       FileUtils.delete(pluginDirectory);
-               }
-
-            // create directory for plugin
-            pluginDirectory.mkdirs();
-
-            // expand '.zip' file
-            Unzip unzip = new Unzip();
-            unzip.setSource(pluginArchiveFile);
-            unzip.setDestination(pluginDirectory);
-            unzip.extract();
-        }
-
-        return pluginDirectory;
-    }
-
-       private void resolvePlugins() throws PluginException {
-               resolveDependencies();
-       }
-
-       private void resolveDependencies() throws PluginException {
-               dependencyResolver.resolve(unresolvedPlugins);
-               resolvedPlugins = dependencyResolver.getSortedPlugins();
-        for (PluginWrapper pluginWrapper : resolvedPlugins) {
-               unresolvedPlugins.remove(pluginWrapper);
-               log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
-        }
+        log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
        }
 
-    private synchronized void firePluginStateEvent(PluginStateEvent event) {
-        for (PluginStateListener listener : pluginStateListeners) {
-            log.debug("Fire '{}' to '{}'", event, listener);
-            listener.pluginStateChanged(event);
-        }
-    }
-
 }
index 327e4fee0346247cdcff784a3310f6312b8b5c9c..6efb205e5e40635a7732aedbe9f891c0efc2e0cf 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ro.fortsoft.pf4j.util.AndFileFilter;
+import ro.fortsoft.pf4j.util.DirectoryFileFilter;
 import ro.fortsoft.pf4j.util.FileUtils;
+import ro.fortsoft.pf4j.util.HiddenFilter;
+import ro.fortsoft.pf4j.util.NameFileFilter;
+import ro.fortsoft.pf4j.util.NotFileFilter;
+import ro.fortsoft.pf4j.util.OrFileFilter;
+import ro.fortsoft.pf4j.util.Unzip;
+import ro.fortsoft.pf4j.util.ZipFileFilter;
 
 import java.io.File;
 import java.io.FileFilter;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 
 /**
  * @author Decebal Suiu
- * @author Mário Franco
  */
-public class DefaultPluginRepository implements PluginRepository {
+public class DefaultPluginRepository extends BasePluginRepository {
 
-    private final File directory;
-    private final FileFilter filter;
+    private static final Logger log = LoggerFactory.getLogger(DefaultPluginRepository.class);
 
-    public DefaultPluginRepository(File directory, FileFilter filter) {
-        this.directory = directory;
-        this.filter = filter;
+    public DefaultPluginRepository(Path pluginsRoot, boolean development) {
+        super(pluginsRoot);
+
+        AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
+        pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter(development)));
+        setFilter(pluginsFilter);
     }
 
     @Override
-    public List<File> getPluginArchives() {
-        File[] files = directory.listFiles(filter);
+    public List<Path> getPluginPaths() {
+        // expand plugins zip files
+        File[] pluginZips = pluginsRoot.toFile().listFiles(new ZipFileFilter());
+        if ((pluginZips != null) && pluginZips.length > 0) {
+            for (File pluginZip : pluginZips) {
+                try {
+                    expandPluginZip(pluginZip);
+                } catch (IOException e) {
+                    log.error("Cannot expand plugin zip '{}'", pluginZip);
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
 
-        return (files != null) ? Arrays.asList(files) : Collections.<File>emptyList();
+        return super.getPluginPaths();
     }
 
     @Override
-    public boolean deletePluginArchive(String pluginPath) {
-        File[] files = directory.listFiles(filter);
-        if (files != null) {
-            File pluginArchive = null;
-            // strip prepended "/" from the plugin path
-            String dirName = pluginPath.substring(1);
-            // find the zip file that matches the plugin path
-            for (File archive : files) {
-                String name = archive.getName().substring(0, archive.getName().lastIndexOf('.'));
-                if (name.equals(dirName)) {
-                    pluginArchive = archive;
-                    break;
-                }
-            }
-            if (pluginArchive != null && pluginArchive.exists()) {
-                return FileUtils.delete(pluginArchive);
+    public boolean deletePluginPath(Path pluginPath) {
+        // TODO remove plugin zip ?
+        return super.deletePluginPath(pluginPath);
+    }
+
+    protected FileFilter createHiddenPluginFilter(boolean development) {
+        OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
+
+        if (development) {
+            hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
+        }
+
+        return hiddenPluginFilter;
+    }
+
+    /**
+     * Unzip a plugin zip file in a directory that has the same name as the zip file
+     * and it's relative to {@code pluginsRoot}.
+     * For example if the zip file is {@code my-plugin.zip} then the resulted directory
+     * is {@code my-plugin}.
+     *
+     * @param pluginZip
+     * @return
+     * @throws IOException
+     */
+    private File expandPluginZip(File pluginZip) throws IOException {
+        String fileName = pluginZip.getName();
+        long pluginZipDate = pluginZip.lastModified();
+        String pluginName = fileName.substring(0, fileName.length() - 4);
+        File pluginDirectory = pluginsRoot.resolve(pluginName).toFile();
+        // check if exists root or the '.zip' file is "newer" than root
+        if (!pluginDirectory.exists() || (pluginZipDate > pluginDirectory.lastModified())) {
+            log.debug("Expand plugin zip '{}' in '{}'", pluginZip, pluginDirectory);
+
+            // do not overwrite an old version, remove it
+            if (pluginDirectory.exists()) {
+                FileUtils.delete(pluginDirectory);
             }
+
+            // create root for plugin
+            pluginDirectory.mkdirs();
+
+            // expand '.zip' file
+            Unzip unzip = new Unzip();
+            unzip.setSource(pluginZip);
+            unzip.setDestination(pluginDirectory);
+            unzip.extract();
         }
 
-        return false;
+        return pluginDirectory;
     }
 
 }
index 8522e9c5e12107bb62815e33aeea71e0b4dd8a11..f5cb514397ed34b571c0e379cf0996af5cdb7ee0 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import ro.fortsoft.pf4j.util.FileUtils;
 
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+
 /**
- * The default implementation for PluginStatusProvider.
+ * The default implementation for {@link PluginStatusProvider}.
  *
  * @author Decebal Suiu
  * @author Mário Franco
@@ -33,24 +33,25 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultPluginStatusProvider.class);
 
-    private final File pluginsDirectory;
+    private final Path pluginsRoot;
+
+    private List<String> enabledPlugins;
+    private List<String> disabledPlugins;
 
-    private List<String> enabledPlugins = new ArrayList<>();
-    private List<String> disabledPlugins = new ArrayList<>();
+    public DefaultPluginStatusProvider(Path pluginsRoot) {
+        this.pluginsRoot = pluginsRoot;
 
-    public DefaultPluginStatusProvider(File pluginsDirectory) {
-        this.pluginsDirectory = pluginsDirectory;
         initialize();
     }
 
     private void initialize() {
         try {
             // create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file)
-            enabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "enabled.txt"), true);
+            enabledPlugins = FileUtils.readLines(pluginsRoot.resolve("enabled.txt").toFile(), true);
             log.info("Enabled plugins: {}", enabledPlugins);
 
             // create a list with plugin identifiers that should not be accepted by this manager (blacklist from plugins/disabled.txt file)
-            disabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "disabled.txt"), true);
+            disabledPlugins = FileUtils.readLines(pluginsRoot.resolve("disabled.txt").toFile(), true);
             log.info("Disabled plugins: {}", disabledPlugins);
         } catch (IOException e) {
             log.error(e.getMessage(), e);
@@ -70,12 +71,13 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
     public boolean disablePlugin(String pluginId) {
         if (disabledPlugins.add(pluginId)) {
             try {
-                FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
+                FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
             } catch (IOException e) {
                 log.error("Failed to disable plugin {}", pluginId, e);
                 return false;
             }
         }
+
         return true;
     }
 
@@ -83,12 +85,13 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
     public boolean enablePlugin(String pluginId) {
         try {
             if (disabledPlugins.remove(pluginId)) {
-                FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
+                FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
             }
         } catch (IOException e) {
             log.error("Failed to enable plugin {}", pluginId, e);
             return false;
         }
+
         return true;
     }
 
index e2814957610e0723f3e7859ee9bda1a962cbe59a..c26881cafc3ba99e249d13febb03afbaf07dcfb8 100644 (file)
@@ -71,7 +71,7 @@ public class DependencyResolver {
                List<String> pluginsId = dependenciesGraph.reverseTopologicalSort();
 
                if (pluginsId == null) {
-                       throw new CyclicDependencyException("Cyclic dependencies !!!" + dependenciesGraph.toString());
+                       throw new PluginException("Cyclic dependencies !!!" + dependenciesGraph.toString());
                }
 
                log.debug("Plugins order: {}", pluginsId);
index 0109222dad8ac124a415df6e8c65b5154b5a5c0f..ea70f9a75a34a5736b02914311867d3ea12fb81a 100644 (file)
 package ro.fortsoft.pf4j;
 
 /**
- * Overwrite classes directories to "target/classes" and lib directories to "target/lib".
+ * Overwrite classes directories to {@code target/classes} and lib directories to {@code target/lib}.
  *
  * @author Decebal Suiu
  */
 public class DevelopmentPluginClasspath extends PluginClasspath {
 
-       private static final String DEVELOPMENT_CLASSES_DIRECTORY = "target/classes";
-       private static final String DEVELOPMENT_LIB_DIRECTORY = "target/lib";
-
        public DevelopmentPluginClasspath() {
                super();
-       }
-
-       @Override
-       protected void addResources() {
-               classesDirectories.add(DEVELOPMENT_CLASSES_DIRECTORY);
-               libDirectories.add(DEVELOPMENT_LIB_DIRECTORY);
-       }
 
+        addClassesDirectories("target/classes");
+        addLibDirectories("target/lib");
+    }
 
 }
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/JarPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/JarPluginManager.java
new file mode 100644 (file)
index 0000000..b2442ff
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2016 Decebal Suiu
+ *
+ * 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 ro.fortsoft.pf4j;
+
+import ro.fortsoft.pf4j.util.AndFileFilter;
+import ro.fortsoft.pf4j.util.DirectoryFileFilter;
+import ro.fortsoft.pf4j.util.HiddenFilter;
+import ro.fortsoft.pf4j.util.JarFileFilter;
+import ro.fortsoft.pf4j.util.NameFileFilter;
+import ro.fortsoft.pf4j.util.NotFileFilter;
+import ro.fortsoft.pf4j.util.OrFileFilter;
+
+import java.io.FileFilter;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+/**
+ * It's a {@link PluginManager} that load plugin from a jar file.
+ * Actually, a plugin is a fat jar, a jar which contains classes from all the libraries,
+ * on which your project depends and, of course, the classes of current project.
+ *
+ * @author Decebal Suiu
+ */
+public class JarPluginManager extends DefaultPluginManager {
+
+    @Override
+    protected PluginRepository createPluginRepository() {
+        return new JarPluginRepository(getPluginsRoot(), isDevelopment());
+    }
+
+    @Override
+    protected PluginDescriptorFinder createPluginDescriptorFinder() {
+        return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new JarPluginDescriptorFinder();
+    }
+
+    @Override
+    protected PluginLoader createPluginLoader() {
+        return new JarPluginLoader(this, pluginClasspath);
+    }
+
+    class JarPluginRepository extends BasePluginRepository {
+
+        public JarPluginRepository(Path pluginsRoot, boolean development) {
+            super(pluginsRoot);
+
+            if (development) {
+                AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
+                pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter(development)));
+                setFilter(pluginsFilter);
+            } else {
+                setFilter(new JarFileFilter());
+            }
+        }
+
+        protected FileFilter createHiddenPluginFilter(boolean development) {
+            OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
+
+            if (development) {
+                hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
+            }
+
+            return hiddenPluginFilter;
+        }
+
+    }
+
+    class JarPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
+
+        @Override
+        public Manifest readManifest(Path pluginPath) throws PluginException {
+            try {
+                return new JarFile(pluginPath.toFile()).getManifest();
+            } catch (IOException e) {
+                throw new PluginException(e);
+            }
+        }
+
+    }
+
+    class JarPluginLoader extends DefaultPluginLoader {
+
+        public JarPluginLoader(PluginManager pluginManager, PluginClasspath pluginClasspath) {
+            super(pluginManager, pluginClasspath);
+        }
+
+        @Override
+        public ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor) {
+            if (isDevelopment()) {
+                return super.loadPlugin(pluginPath, pluginDescriptor);
+            }
+
+            PluginClassLoader pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, getClass().getClassLoader());
+            pluginClassLoader.addFile(pluginPath.toFile());
+
+            return pluginClassLoader;
+        }
+
+    }
+
+}
index 24ae75196e993f16983635a69e6645d2969d9fe5..fba63ea046cb5dde1fd4a3a7ed88d242feafb43c 100644 (file)
 package ro.fortsoft.pf4j;
 
 import com.github.zafarkhaja.semver.Version;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import ro.fortsoft.pf4j.util.StringUtils;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
+import java.nio.file.Path;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
 
@@ -32,19 +27,11 @@ import java.util.jar.Manifest;
  *
  * @author Decebal Suiu
  */
-public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
-
-       private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
-
-       private PluginClasspath pluginClasspath;
-
-       public ManifestPluginDescriptorFinder(PluginClasspath pluginClasspath) {
-               this.pluginClasspath = pluginClasspath;
-       }
+public abstract class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
 
        @Override
-       public PluginDescriptor find(File pluginRepository) throws PluginException {
-        Manifest manifest = readManifest(pluginRepository);
+       public PluginDescriptor find(Path pluginPath) throws PluginException {
+        Manifest manifest = readManifest(pluginPath);
 
         PluginDescriptor pluginDescriptor = createPluginDescriptor(manifest);
         validatePluginDescriptor(pluginDescriptor);
@@ -52,37 +39,7 @@ public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
                return pluginDescriptor;
        }
 
-    protected Manifest readManifest(File pluginRepository) throws PluginException {
-        // TODO it's ok with first classes directory? Another idea is to specify in PluginClasspath the folder.
-        String classes = pluginClasspath.getClassesDirectories().get(0);
-        File manifestFile = new File(pluginRepository, classes + "/META-INF/MANIFEST.MF");
-        log.debug("Lookup plugin descriptor in '{}'", manifestFile);
-        if (!manifestFile.exists()) {
-            throw new PluginException("Cannot find '" + manifestFile + "' file");
-        }
-
-        FileInputStream input = null;
-        try {
-            input = new FileInputStream(manifestFile);
-        } catch (FileNotFoundException e) {
-            // not happening
-        }
-
-        Manifest manifest = null;
-        try {
-            manifest = new Manifest(input);
-        } catch (IOException e) {
-            throw new PluginException(e.getMessage(), e);
-        } finally {
-            try {
-                input.close();
-            } catch (IOException e) {
-                throw new PluginException(e.getMessage(), e);
-            }
-        }
-
-        return manifest;
-    }
+    public abstract Manifest readManifest(Path pluginPath) throws PluginException;
 
     protected PluginDescriptor createPluginDescriptor(Manifest manifest) {
         PluginDescriptor pluginDescriptor = createPluginDescriptorInstance();
index 093623bea6202d65edd4a98e234825ab7b269b23..4e31fc34a64b9f49ba00bf281bb2c10156ea2b41 100644 (file)
@@ -18,6 +18,7 @@ package ro.fortsoft.pf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
@@ -25,8 +26,8 @@ import java.util.List;
 
 /**
  * One instance of this class should be created by plugin manager for every available plug-in.
- * This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars before delegating
- * to the parent class loader.
+ * This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars
+ * before delegating to the parent class loader.
  *
  * @author Decebal Suiu
  */
@@ -48,13 +49,23 @@ public class PluginClassLoader extends URLClassLoader {
 
        @Override
        public void addURL(URL url) {
+        log.debug("Add '{}'", url);
                super.addURL(url);
        }
 
+       public void addFile(File file) {
+        try {
+            addURL(file.getCanonicalFile().toURI().toURL());
+        } catch (IOException e) {
+//            throw new RuntimeException(e);
+            log.error(e.getMessage(), e);
+        }
+    }
+
     /**
-     * This implementation of loadClass uses a child first delegation model rather than the standard parent first.
+     * Uses a child first delegation model rather than the standard parent first.
      * If the requested class cannot be found in this class loader, the parent class loader will be consulted
-     * via the standard ClassLoader.loadClass(String) mechanism.
+     * via the standard {@link ClassLoader#loadClass(String)} mechanism.
      */
        @Override
     public Class<?> loadClass(String className) throws ClassNotFoundException {
@@ -67,9 +78,6 @@ public class PluginClassLoader extends URLClassLoader {
                     return getClass().getClassLoader().loadClass(className);
                 } catch (ClassNotFoundException e) {
                     // try next step
-                    // TODO if I uncomment below lines (the correct approach) I received ClassNotFoundException for demo (ro.fortsoft.pf4j.demo)
-                    //                log.error(e.getMessage(), e);
-                    //                throw e;
                 }
             }
 
@@ -93,7 +101,7 @@ public class PluginClassLoader extends URLClassLoader {
             log.trace("Search in dependencies for class '{}'", className);
             List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
             for (PluginDependency dependency : dependencies) {
-                PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
+                ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
                 try {
                     return classLoader.loadClass(className);
                 } catch (ClassNotFoundException e) {
@@ -103,14 +111,14 @@ public class PluginClassLoader extends URLClassLoader {
 
             log.trace("Couldn't find class '{}' in plugin classpath. Delegating to parent", className);
 
-            // use the standard URLClassLoader (which follows normal parent delegation)
+            // use the standard ClassLoader (which follows normal parent delegation)
             return super.loadClass(className);
         }
     }
 
     /**
-     * Load the named resource from this plugin. This implementation checks the plugin's classpath first
-     * then delegates to the parent.
+     * Load the named resource from this plugin.
+     * This implementation checks the plugin's classpath first then delegates to the parent.
      *
      * @param name the name of the resource.
      * @return the URL to the resource, <code>null</code> if the resource was not found.
@@ -134,18 +142,4 @@ public class PluginClassLoader extends URLClassLoader {
         return super.findResource(name);
     }
 
-    /**
-     * Release all resources acquired by this class loader.
-     * The current implementation is incomplete.
-     * For now, this instance can no longer be used to load
-     * new classes or resources that are defined by this loader.
-     */
-    public void dispose() {
-        try {
-            close();
-        } catch (IOException e) {
-            log.error(e.getMessage(), e);
-        }
-    }
-
 }
index a49260ed99e999c1a103b9a8cd1725a3e6aca108..3a6e99dcc140743fcf4f3883aad7a11d55410482 100644 (file)
 package ro.fortsoft.pf4j;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
- * The classpath of the plugin after it was unpacked.
- * It contains classes directories and lib directories (directories that contains jars).
- * All directories are relative to plugin repository.
- * The default values are "classes" and "lib".
+ * The classpath of the plugin.
+ * It contains {@code classes} directories and {@code lib} directories (directories that contains jars).
  *
  * @author Decebal Suiu
  */
 public class PluginClasspath {
 
-       private static final String DEFAULT_CLASSES_DIRECTORY = "classes";
-       private static final String DEFAULT_LIB_DIRECTORY = "lib";
-
-       protected List<String> classesDirectories;
-       protected List<String> libDirectories;
+       private List<String> classesDirectories;
+       private List<String> libDirectories;
 
        public PluginClasspath() {
                classesDirectories = new ArrayList<>();
                libDirectories = new ArrayList<>();
-
-               addResources();
        }
 
        public List<String> getClassesDirectories() {
                return classesDirectories;
        }
 
-       public void setClassesDirectories(List<String> classesDirectories) {
-               this.classesDirectories = classesDirectories;
+       public void addClassesDirectories(String... classesDirectories) {
+               this.classesDirectories.addAll(Arrays.asList(classesDirectories));
        }
 
        public List<String> getLibDirectories() {
                return libDirectories;
        }
 
-       public void setLibDirectories(List<String> libDirectories) {
-               this.libDirectories = libDirectories;
-       }
-
-       protected void addResources() {
-               classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
-               libDirectories.add(DEFAULT_LIB_DIRECTORY);
+       public void addLibDirectories(String... libDirectories) {
+               this.libDirectories.addAll(Arrays.asList(libDirectories));
        }
 
 }
index 6cb208cd543b198c895654ecb8b5d32c44240254..c84adaf586aef8fcd61b62e2920282dfe7ffebc1 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
-import java.io.File;
+import java.nio.file.Path;
 
 /**
- * Find a plugin descriptor in a directory (plugin repository).
- * You can find in manifest file @see DefaultPluginDescriptorFinder,
- * xml file, properties file, java services (with ServiceLoader), etc.
+ * Find a plugin descriptor for a plugin path.
+ * You can find in manifest file {@link DefaultPluginDescriptorFinder},
+ * xml file, properties file, java services (with {@link java.util.ServiceLoader}), etc.
  *
  * @author Decebal Suiu
  */
 public interface PluginDescriptorFinder {
 
-       PluginDescriptor find(File pluginRepository) throws PluginException;
+       PluginDescriptor find(Path pluginPath) throws PluginException;
 
 }
index 9333cb0b22d0741334b9545af01381ca7dc3ff4b..ecd0e080f91b46553733b1267aff21ea1c99283b 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import ro.fortsoft.pf4j.util.DirectoryFileFilter;
-import ro.fortsoft.pf4j.util.JarFileFilter;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.net.MalformedURLException;
-import java.util.List;
-import java.util.Vector;
+import java.nio.file.Path;
 
 /**
- * Load all information needed by a plugin.
- * This means add all jar files from 'lib' directory, 'classes' to classpath.
- * It's a class for only the internal use.
+ * Load all information (classes) needed by a plugin.
  *
  * @author Decebal Suiu
  */
-class PluginLoader {
-
-       private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
-
-    /*
-     * The plugin repository.
-     */
-    private File pluginRepository;
-
-    private PluginClasspath pluginClasspath;
-    private PluginClassLoader pluginClassLoader;
-
-    public PluginLoader(File pluginRepository, PluginClassLoader pluginClassLoader, PluginClasspath pluginClasspath) {
-        this.pluginRepository = pluginRepository;
-        this.pluginClassLoader = pluginClassLoader;
-        this.pluginClasspath = pluginClasspath;
-    }
-
-    public boolean load() {
-        return loadClassesAndJars();
-    }
-
-       private boolean loadClassesAndJars() {
-       return loadClasses() && loadJars();
-    }
-
-    private boolean loadClasses() {
-       List<String> classesDirectories = pluginClasspath.getClassesDirectories();
-
-       // add each classes directory to plugin class loader
-       for (String classesDirectory : classesDirectories) {
-               // make 'classesDirectory' absolute
-               File file = new File(pluginRepository, classesDirectory).getAbsoluteFile();
-
-               if (file.exists() && file.isDirectory()) {
-                   log.debug("Found '{}' directory", file.getPath());
-
-                   try {
-                       pluginClassLoader.addURL(file.toURI().toURL());
-                       log.debug("Added '{}' to the class loader path", file);
-                   } catch (MalformedURLException e) {
-                       e.printStackTrace();
-                       log.error(e.getMessage(), e);
-                       return false;
-                   }
-               }
-       }
-
-        return true;
-    }
-
-    /**
-     * Add all *.jar files from lib directories to class loader.
-     */
-    private boolean loadJars() {
-       List<String> libDirectories = pluginClasspath.getLibDirectories();
-
-       // add each jars directory to plugin class loader
-       for (String libDirectory : libDirectories) {
-               // make 'libDirectory' absolute
-               File file = new File(pluginRepository, libDirectory).getAbsoluteFile();
-
-               // collect all jars from current lib directory in jars variable
-               Vector<File> jars = new Vector<>();
-               getJars(jars, file);
-               for (File jar : jars) {
-                   try {
-                       pluginClassLoader.addURL(jar.toURI().toURL());
-                       log.debug("Added '{}' to the class loader path", jar);
-                   } catch (MalformedURLException e) {
-                       e.printStackTrace();
-                       log.error(e.getMessage(), e);
-                       return false;
-                   }
-               }
-       }
-
-        return true;
-    }
-
-    private void getJars(Vector<File> bucket, File file) {
-        FileFilter jarFilter = new JarFileFilter();
-        FileFilter directoryFilter = new DirectoryFileFilter();
-
-        if (file.exists() && file.isDirectory() && file.isAbsolute()) {
-            File[] jars = file.listFiles(jarFilter);
-            for (int i = 0; (jars != null) && (i < jars.length); ++i) {
-                bucket.addElement(jars[i]);
-            }
+public interface PluginLoader {
 
-            File[] directories = file.listFiles(directoryFilter);
-            for (int i = 0; (directories != null) && (i < directories.length); ++i) {
-                File directory = directories[i];
-                getJars(bucket, directory);
-            }
-        }
-    }
+    ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor);
 
 }
index 5f23e5cc383eaed1d832054fa0a15054ef2e7f13..f023e296a6ef4a9d698f239235bde218970cae30 100644 (file)
@@ -16,7 +16,8 @@
 package ro.fortsoft.pf4j;
 
 import com.github.zafarkhaja.semver.Version;
-import java.io.File;
+
+import java.nio.file.Path;
 import java.util.List;
 import java.util.Set;
 
@@ -69,10 +70,10 @@ public interface PluginManager {
     /**
      * Load a plugin.
      *
-     * @param pluginArchiveFile
+     * @param pluginPath
      * @return the pluginId of the installed plugin or null
      */
-       String loadPlugin(File pluginArchiveFile);
+       String loadPlugin(Path pluginPath);
 
     /**
      * Start all active plugins.
@@ -130,7 +131,7 @@ public interface PluginManager {
      */
     boolean deletePlugin(String pluginId);
 
-       PluginClassLoader getPluginClassLoader(String pluginId);
+       ClassLoader getPluginClassLoader(String pluginId);
 
        <T> List<T> getExtensions(Class<T> type);
 
index 5db96b5e2d5bb806d604a0b8357fb6ead591f1b6..6f57c57c3cbb533d71a7da656f2af7875ec2013b 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
-import java.io.File;
+import java.nio.file.Path;
 import java.util.List;
 
 /**
- * Directory whose contents are .zip files used as plugins.
+ * Directory that contains plugins. A plugin could be a zip file.
  *
  * @author Decebal Suiu
  * @author Mário Franco
@@ -27,11 +27,11 @@ import java.util.List;
 public interface PluginRepository {
 
     /**
-     * List all plugin archive filed.
+     * List all plugin paths.
      *
      * @return a list of files
      */
-    List<File> getPluginArchives();
+    List<Path> getPluginPaths();
 
     /**
      * Removes a plugin from the repository.
@@ -39,6 +39,6 @@ public interface PluginRepository {
      * @param pluginPath the plugin path
      * @return true if deleted
      */
-    boolean deletePluginArchive(String pluginPath);
+    boolean deletePluginPath(Path pluginPath);
 
 }
index 468d30b467a4bd8ec4e4fa1779797d38954e76d5..e594f685a79313f2bd8ab3b32931d68a1cc8e99f 100644 (file)
@@ -15,6 +15,8 @@
  */
 package ro.fortsoft.pf4j;
 
+import java.nio.file.Path;
+
 /**
  * A wrapper over plugin instance.
  *
@@ -22,16 +24,17 @@ package ro.fortsoft.pf4j;
  */
 public class PluginWrapper {
 
-    PluginManager pluginManager;
-       PluginDescriptor descriptor;
-       String pluginPath;
-       PluginClassLoader pluginClassLoader;
-       PluginFactory pluginFactory;
-       PluginState pluginState;
-       RuntimeMode runtimeMode;
+    private PluginManager pluginManager;
+       private PluginDescriptor descriptor;
+       private Path pluginPath;
+       private ClassLoader pluginClassLoader;
+       private PluginFactory pluginFactory;
+       private PluginState pluginState;
+       private RuntimeMode runtimeMode;
+
     Plugin plugin; // cache
 
-       public PluginWrapper(PluginManager pluginManager, PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) {
+       public PluginWrapper(PluginManager pluginManager, PluginDescriptor descriptor, Path pluginPath, ClassLoader pluginClassLoader) {
         this.pluginManager = pluginManager;
                this.descriptor = descriptor;
                this.pluginPath = pluginPath;
@@ -55,9 +58,9 @@ public class PluginWrapper {
     }
 
     /**
-     * Returns the path of this plugin relative to plugins directory.
+     * Returns the path of this plugin.
      */
-    public String getPluginPath() {
+    public Path getPluginPath() {
        return pluginPath;
     }
 
index 544d76e48d28fdcc26dde819ebbc3cd33debc463..b8595bbf92195a6764faec1e908a81abba702cc0 100644 (file)
@@ -20,11 +20,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import ro.fortsoft.pf4j.util.StringUtils;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Properties;
 
 /**
@@ -49,8 +49,8 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
        }
 
        @Override
-       public PluginDescriptor find(File pluginRepository) throws PluginException {
-        Properties properties = readProperties(pluginRepository);
+       public PluginDescriptor find(Path pluginPath) throws PluginException {
+        Properties properties = readProperties(pluginPath);
 
         PluginDescriptor pluginDescriptor = createPluginDescriptor(properties);
         validatePluginDescriptor(pluginDescriptor);
@@ -58,31 +58,18 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
         return pluginDescriptor;
        }
 
-    protected Properties readProperties(File pluginRepository) throws PluginException {
-        File propertiesFile = new File(pluginRepository, propertiesFileName);
-        log.debug("Lookup plugin descriptor in '{}'", propertiesFile);
-        if (!propertiesFile.exists()) {
-            throw new PluginException("Cannot find '" + propertiesFile + "' file");
-        }
-
-        InputStream input = null;
-        try {
-            input = new FileInputStream(propertiesFile);
-        } catch (FileNotFoundException e) {
-            // not happening
+    protected Properties readProperties(Path pluginPath) throws PluginException {
+        Path propertiesPath = pluginPath.resolve(Paths.get(propertiesFileName));
+        log.debug("Lookup plugin descriptor in '{}'", propertiesPath);
+        if (Files.notExists(propertiesPath)) {
+            throw new PluginException("Cannot find '" + pluginPath + "' path");
         }
 
         Properties properties = new Properties();
-        try {
+        try (InputStream input = Files.newInputStream(propertiesPath)) {
             properties.load(input);
         } catch (IOException e) {
             throw new PluginException(e.getMessage(), e);
-        } finally {
-            try {
-                input.close();
-            } catch (IOException e) {
-                throw new PluginException(e.getMessage(), e);
-            }
         }
 
         return properties;
index d394219b20ac0ff5990fa0f48e3794d704be6ede..9e2ca1014e7c8d587ef31a910d6b0f1c6be1ef0a 100644 (file)
@@ -18,6 +18,7 @@ package ro.fortsoft.pf4j.util;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileFilter;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -95,4 +96,29 @@ public class FileUtils {
                return success;
        }
 
+       public static List<File> getJars(File folder) {
+           List<File> bucket = new ArrayList<>();
+           getJars(bucket, folder);
+
+           return bucket;
+    }
+
+    private static void getJars(List<File> bucket, File folder) {
+        FileFilter jarFilter = new JarFileFilter();
+        FileFilter directoryFilter = new DirectoryFileFilter();
+
+        if (folder.exists() && folder.isDirectory() && folder.isAbsolute()) {
+            File[] jars = folder.listFiles(jarFilter);
+            for (int i = 0; (jars != null) && (i < jars.length); ++i) {
+                bucket.add(jars[i]);
+            }
+
+            File[] directories = folder.listFiles(directoryFilter);
+            for (int i = 0; (directories != null) && (i < directories.length); ++i) {
+                File directory = directories[i];
+                getJars(bucket, directory);
+            }
+        }
+    }
+
 }
index abbc712b6ae6417d428515514f2675d4443c9e3a..fc9f6d06207b666ca52943ad7b8ba42d8a617434 100644 (file)
@@ -27,7 +27,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This class extracts the containt of the plugin archive into a directory.
+ * This class extracts the content of the plugin zip into a directory.
  * It's a class for only the internal use.
  *
  * @author Decebal Suiu
@@ -70,8 +70,8 @@ public class Unzip {
         removeDirectory(destination);
 
        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source));
-           ZipEntry zipEntry = null;
 
+           ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                try {
                        File file = new File(destination, zipEntry.getName());
index 7f51d6f02c0c0beac6b1ec4e4bcd1ea44d2e5c8e..2b1dc326de8990dff4a9fb2d566b384cf18c3b96 100644 (file)
@@ -19,10 +19,9 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import ro.fortsoft.pf4j.util.ZipFileFilter;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
@@ -31,6 +30,7 @@ import static org.junit.Assert.assertTrue;
 
 /**
  * @author Mario Franco
+ * @author Decebal Suiu
  */
 public class DefaultPluginRepositoryTest {
 
@@ -39,52 +39,53 @@ public class DefaultPluginRepositoryTest {
 
     @Before
     public void setUp() throws IOException {
-        testFolder.newFile("plugin-1.zip");
-        testFolder.newFile("plugin-2.zip");
-        testFolder.newFile("plugin-3.zi_");
+        testFolder.newFolder("plugin-1");
+        testFolder.newFolder("plugin-2");
+        testFolder.newFolder("plugin-3");
     }
 
     /**
-     * Test of getPluginArchives method, of class DefaultPluginRepository.
+     * Test of {@link DefaultPluginRepository#getPluginPaths()} method.
      */
     @Test
     public void testGetPluginArchives() {
-        DefaultPluginRepository instance = new DefaultPluginRepository(testFolder.getRoot(), new ZipFileFilter());
+        Path pluginsRoot = getPluginsRoot();
 
-        List<File> result = instance.getPluginArchives();
+        PluginRepository instance = new DefaultPluginRepository(pluginsRoot, false);
 
-        assertEquals(2, result.size());
-        assertFileExists(result, "plugin-1.zip");
-        assertFileExists(result, "plugin-2.zip");
+        List<Path> result = instance.getPluginPaths();
+
+        assertEquals(3, result.size());
+        assertPathExists(result, pluginsRoot.resolve("plugin-1"));
+        assertPathExists(result, pluginsRoot.resolve("plugin-2"));
+        assertPathExists(result, pluginsRoot.resolve("plugin-3"));
     }
 
     /**
-     * Test of deletePluginArchive method, of class DefaultPluginRepository.
+     * Test of {@link DefaultPluginRepository#deletePluginPath(Path)} method.
      */
     @Test
-    public void testDeletePluginArchive() {
-        DefaultPluginRepository instance = new DefaultPluginRepository(testFolder.getRoot(), new ZipFileFilter());
+    public void testDeletePluginPath() {
+        Path pluginsRoot = getPluginsRoot();
+
+        PluginRepository instance = new DefaultPluginRepository(pluginsRoot, false);
 
-        assertTrue(instance.deletePluginArchive("/plugin-1"));
-        assertFalse(instance.deletePluginArchive("/plugin-3"));
+        assertTrue(instance.deletePluginPath(pluginsRoot.resolve("plugin-1")));
+        assertTrue(instance.deletePluginPath(pluginsRoot.resolve("plugin-3")));
+        assertFalse(instance.deletePluginPath(pluginsRoot.resolve("plugin-4")));
 
-        List<File> result = instance.getPluginArchives();
+        List<Path> result = instance.getPluginPaths();
 
         assertEquals(1, result.size());
-        assertEquals(result.get(0).getName(), "plugin-2.zip");
+        assertEquals(pluginsRoot.relativize(result.get(0)).toString(), "plugin-2");
     }
 
-    public static void assertFileExists(List<File> files, String file) {
-        boolean found = false;
-
-        for (File f : files) {
-            if (f.getName().equals(file)) {
-                found = true;
-                break;
-            }
-        }
+    private void assertPathExists(List<Path> paths, Path path) {
+        assertTrue("The directory must contains the file " + path, paths.contains(path));
+    }
 
-        assertTrue("The directory must contains the file " + file, found);
+    private Path getPluginsRoot() {
+        return testFolder.getRoot().toPath();
     }
 
 }
index 25dc7a1a474b9607fceaed9b880f273d50d62a8d..db33d65a5a1e7fe5770ee547f1960afdcd00e5bd 100644 (file)
@@ -22,13 +22,16 @@ import ro.fortsoft.pf4j.util.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * @author Mario Franco
+ * @author Decebal Suiu
  */
 public class DefaultPluginStatusProviderTest {
 
@@ -42,7 +45,8 @@ public class DefaultPluginStatusProviderTest {
     public void testIsPluginDisabled() throws IOException {
         createEnabledFile();
         createDisabledFile();
-        DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+        PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
 
         assertFalse(instance.isPluginDisabled("plugin-1"));
         assertTrue(instance.isPluginDisabled("plugin-2"));
@@ -55,7 +59,8 @@ public class DefaultPluginStatusProviderTest {
     @Test
     public void testIsPluginDisabledWithEnableEmpty() throws IOException {
         createDisabledFile();
-        DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+        PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
 
         assertFalse(instance.isPluginDisabled("plugin-1"));
         assertTrue(instance.isPluginDisabled("plugin-2"));
@@ -69,7 +74,8 @@ public class DefaultPluginStatusProviderTest {
     public void testDisablePlugin() throws IOException {
         createEnabledFile();
         createDisabledFile();
-        DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+        PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
 
         assertTrue(instance.disablePlugin("plugin-1"));
         assertTrue(instance.isPluginDisabled("plugin-1"));
@@ -83,7 +89,8 @@ public class DefaultPluginStatusProviderTest {
     @Test
     public void testDisablePluginWithEnableEmpty() throws IOException {
         createDisabledFile();
-        DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+        PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
 
         assertTrue(instance.disablePlugin("plugin-1"));
         assertTrue(instance.isPluginDisabled("plugin-1"));
@@ -97,7 +104,8 @@ public class DefaultPluginStatusProviderTest {
     @Test
     public void testEnablePlugin() throws IOException {
         createEnabledFile();
-        DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+        PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
 
         assertTrue(instance.enablePlugin("plugin-2"));
         assertFalse(instance.isPluginDisabled("plugin-1"));
@@ -110,7 +118,7 @@ public class DefaultPluginStatusProviderTest {
      */
     @Test
     public void testEnablePluginWithEnableEmpty() {
-        DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+        PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
 
         assertTrue(instance.enablePlugin("plugin-2"));
         assertFalse(instance.isPluginDisabled("plugin-1"));
@@ -123,7 +131,7 @@ public class DefaultPluginStatusProviderTest {
      */
     @Test
     public void testDisablePluginWithoutDisabledFile() throws IOException {
-        DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+        PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
 
         assertFalse(instance.isPluginDisabled("plugin-1"));
         assertTrue(instance.disablePlugin("plugin-1"));
@@ -150,4 +158,8 @@ public class DefaultPluginStatusProviderTest {
         FileUtils.writeLines(lines, file);
     }
 
+    private Path getPluginsRoot() {
+        return testFolder.getRoot().toPath();
+    }
+
 }
index cffbc04da4d386d55253cbc5437833b5740323fb..0e94fcdc596e1f734d96a8af9fb4aa57cf0a8ee0 100644 (file)
@@ -21,18 +21,19 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
-import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.List;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * @author Mario Franco
+ * @author Decebal Suiu
  */
 public class ManifestPluginDescriptorFinderTest {
 
@@ -43,43 +44,42 @@ public class ManifestPluginDescriptorFinderTest {
     public void setUp() throws IOException {
         Charset charset = Charset.forName("UTF-8");
 
-        File plugin = testFolder.newFolder("test-plugin-1", "classes", "META-INF");
-        Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
-        Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin1Manifest(), charset);
+        Path pluginPath = testFolder.newFolder("test-plugin-1", "classes", "META-INF").toPath();
+        Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+        Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin1Manifest(), charset);
 
-        plugin = testFolder.newFolder("test-plugin-2", "classes", "META-INF");
-        Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
-        Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin2Manifest(), charset);
+        pluginPath = testFolder.newFolder("test-plugin-2", "classes", "META-INF").toPath();
+        Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+        Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin2Manifest(), charset);
 
-        // Empty Plugin
+        // empty plugin
         testFolder.newFolder("test-plugin-3");
 
-        // No Plugin Class
-        plugin = testFolder.newFolder("test-plugin-4", "classes", "META-INF");
-        Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
-        Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin4Manifest(), charset);
+        // no plugin class
+        pluginPath = testFolder.newFolder("test-plugin-4", "classes", "META-INF").toPath();
+        Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+        Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin4Manifest(), charset);
 
-        // No Plugin Version
-        plugin = testFolder.newFolder("test-plugin-5", "classes", "META-INF");
-        Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
-        Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin5Manifest(), charset);
+        // no plugin version
+        pluginPath = testFolder.newFolder("test-plugin-5", "classes", "META-INF").toPath();
+        Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+        Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin5Manifest(), charset);
 
-        // No Plugin Id
-        plugin = testFolder.newFolder("test-plugin-6", "classes", "META-INF");
-        Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
-        Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin6Manifest(), charset);
+        // no plugin id
+        pluginPath = testFolder.newFolder("test-plugin-6", "classes", "META-INF").toPath();
+        Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+        Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin6Manifest(), charset);
     }
 
     /**
-     * Test of find method, of class ManifestPluginDescriptorFinder.
+     * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
      */
     @Test
     public void testFind() throws Exception {
-        DefaultPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
+        PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
 
-        PluginDescriptor plugin1 = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-1").toFile());
-
-        PluginDescriptor plugin2 = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-2").toFile());
+        PluginDescriptor plugin1 = instance.find(getPluginsRoot().resolve("test-plugin-1"));
+        PluginDescriptor plugin2 = instance.find(getPluginsRoot().resolve("test-plugin-2"));
 
         assertEquals("test-plugin-1", plugin1.getPluginId());
         assertEquals("Test Plugin 1", plugin1.getPluginDescription());
@@ -102,48 +102,44 @@ public class ManifestPluginDescriptorFinderTest {
     }
 
     /**
-     * Test of find method, of class ManifestPluginDescriptorFinder.
+     * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
      */
     @Test(expected = PluginException.class)
     public void testFindNotFound() throws Exception {
-
-        ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
-        PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-3").toFile());
+        PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+        instance.find(getPluginsRoot().resolve("test-plugin-3"));
     }
 
     /**
-     * Test of find method, of class ManifestPluginDescriptorFinder.
+     * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
      */
     @Test(expected = PluginException.class)
     public void testFindMissingPluginClass() throws Exception {
-
-        ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
-        PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-4").toFile());
+        PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+        instance.find(getPluginsRoot().resolve("test-plugin-4"));
     }
 
     /**
-     * Test of find method, of class ManifestPluginDescriptorFinder.
+     * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
      */
     @Test(expected = PluginException.class)
     public void testFindMissingPluginVersion() throws Exception {
-
-        ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
-        PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-5").toFile());
+        PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+        instance.find(getPluginsRoot().resolve("test-plugin-5"));
     }
 
     /**
-     * Test of find method, of class ManifestPluginDescriptorFinder.
+     * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
      */
     @Test(expected = PluginException.class)
     public void testFindMissingPluginId() throws Exception {
-
-        ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
-        PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-6").toFile());
+        PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+        instance.find(getPluginsRoot().resolve("test-plugin-6"));
     }
 
     private List<String> getPlugin1Manifest() {
-
-        String[] lines = new String[]{"Manifest-Version: 1.0\n"
+        String[] lines = new String[] {
+            "Manifest-Version: 1.0\n"
             + "Implementation-Title: Test Plugin #1\n"
             + "Implementation-Version: 0.10.0-SNAPSHOT\n"
             + "Archiver-Version: Plexus Archiver\n"
@@ -161,14 +157,15 @@ public class ManifestPluginDescriptorFinderTest {
             + "Build-Jdk: 1.8.0_45\n"
             + "Specification-Version: 0.10.0-SNAPSHOT\n"
             + "\n"
-            + ""};
+            + ""
+        };
 
         return Arrays.asList(lines);
     }
 
     private List<String> getPlugin2Manifest() {
-
-        String[] lines = new String[]{"Manifest-Version: 1.0\n"
+        String[] lines = new String[] {
+            "Manifest-Version: 1.0\n"
             + "Plugin-Dependencies: \n"
             + "Implementation-Title: Test Plugin #2\n"
             + "Implementation-Version: 0.10.0-SNAPSHOT\n"
@@ -184,14 +181,15 @@ public class ManifestPluginDescriptorFinderTest {
             + "Build-Jdk: 1.8.0_45\n"
             + "Specification-Version: 0.10.0-SNAPSHOT\n"
             + "\n"
-            + ""};
+            + ""
+        };
 
         return Arrays.asList(lines);
     }
 
     private List<String> getPlugin4Manifest() {
-
-        String[] lines = new String[]{"Manifest-Version: 1.0\n"
+        String[] lines = new String[] {
+            "Manifest-Version: 1.0\n"
             + "Implementation-Title: Test Plugin #4\n"
             + "Implementation-Version: 0.10.0-SNAPSHOT\n"
             + "Archiver-Version: Plexus Archiver\n"
@@ -205,14 +203,15 @@ public class ManifestPluginDescriptorFinderTest {
             + "Build-Jdk: 1.8.0_45\n"
             + "Specification-Version: 0.10.0-SNAPSHOT\n"
             + "\n"
-            + ""};
+            + ""
+        };
 
         return Arrays.asList(lines);
     }
 
     private List<String> getPlugin5Manifest() {
-
-        String[] lines = new String[]{"Manifest-Version: 1.0\n"
+        String[] lines = new String[] {
+            "Manifest-Version: 1.0\n"
             + "Implementation-Title: Test Plugin #5\n"
             + "Implementation-Version: 0.10.0-SNAPSHOT\n"
             + "Archiver-Version: Plexus Archiver\n"
@@ -226,14 +225,15 @@ public class ManifestPluginDescriptorFinderTest {
             + "Build-Jdk: 1.8.0_45\n"
             + "Specification-Version: 0.10.0-SNAPSHOT\n"
             + "\n"
-            + ""};
+            + ""
+        };
 
         return Arrays.asList(lines);
     }
 
     private List<String> getPlugin6Manifest() {
-
-        String[] lines = new String[]{"Manifest-Version: 1.0\n"
+        String[] lines = new String[] {
+            "Manifest-Version: 1.0\n"
             + "Implementation-Title: Test Plugin #6\n"
             + "Implementation-Version: 0.10.0-SNAPSHOT\n"
             + "Archiver-Version: Plexus Archiver\n"
@@ -246,8 +246,14 @@ public class ManifestPluginDescriptorFinderTest {
             + "Build-Jdk: 1.8.0_45\n"
             + "Specification-Version: 0.10.0-SNAPSHOT\n"
             + "\n"
-            + ""};
+            + ""
+        };
 
         return Arrays.asList(lines);
     }
+
+    private Path getPluginsRoot() {
+        return testFolder.getRoot().toPath();
+    }
+
 }
diff --git a/pf4j/src/test/resources/log4j.properties b/pf4j/src/test/resources/log4j.properties
new file mode 100644 (file)
index 0000000..0454ba2
--- /dev/null
@@ -0,0 +1,20 @@
+log4j.rootLogger=DEBUG, Console
+
+#
+# PF4J log
+#
+log4j.logger.ro.fortsoft.pf4j=DEBUG, Console
+# !!! Put the bellow classes on level TRACE when you are in trouble
+log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=WARN, Console
+log4j.logger.ro.fortsoft.pf4j.AbstractExtensionFinder=DEBUG, Console
+log4j.additivity.ro.fortsoft.pf4j=false
+log4j.additivity.ro.fortsoft.pf4j.PluginClassLoader=false
+log4j.additivity.ro.fortsoft.pf4j.AbstractExtensionFinder=false
+
+#
+# Appenders
+#
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.layout=org.apache.log4j.PatternLayout
+#log4j.appender.Console.layout.conversionPattern=%-5p - %-32.32c{1} - %m\n
+log4j.appender.Console.layout.ConversionPattern=%d %p %c - %m%n