diff --git a/README.md b/README.md index 1914775..6542dcc 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,23 @@ Plugin Framework for Java (PF4J) ===================== - A plugin is a way for a third party to extend the functionality of an application. A plugin implements extension points -declared by application or other plugins. Also a plugin can define extension points. +declared by application or other plugins. Also a plugin can define extension points. -Components +Current build status: [![Build Status](https://buildhive.cloudbees.com/job/decebals/job/pf4j/badge/icon)](https://buildhive.cloudbees.com/job/decebals/job/pf4j/) + +Features/Benefits ------------------- +With PF4J you can easily transform a monolithic java application in a modular application. +PF4J is an open source (Apache license) lightweight (around 35KB) plugin framework for java, with minimal dependencies and very extensible (see PluginDescriptorFinder and ExtensionFinder). + +No XML, only Java. + +You can mark any interface or abstract class as an extension point (with marker interface ExtensionPoint) and you specified that an class is an extension with @Extension annotation. + +Also, PF4J can be used in web applications. For my web applications when I want modularity I use [Wicket Plugin](https://github.com/decebals/wicket-plugin). +Components +------------------- - **Plugin** is the base class for all plugins types. Each plugin is loaded into a separate class loader to avoid conflicts. - **PluginManager** is used for all aspects of plugins management (loading, starting, stopping). - **ExtensionPoint** is a point in the application where custom code can be invoked. It's a java interface marker. @@ -15,13 +26,11 @@ Any java interface or abstract class can be marked as an extension point (implem Artifacts ------------------- - - PF4J `pf4j` (jar) - PF4J Demo `pf4j-demo` (executable jar) Using Maven ------------------- - In your pom.xml you must define the dependencies to PF4J artifacts with: ```xml @@ -34,9 +43,10 @@ In your pom.xml you must define the dependencies to PF4J artifacts with: where ${pf4j.version} is the last pf4j version. +You may want to check for the latest released version using [Maven Search](http://search.maven.org/#search%7Cga%7C1%7Cpf4j) + How to use ------------------- - It's very simple to add pf4j in your application: public static void main(String[] args) { @@ -51,7 +61,7 @@ It's very simple to add pf4j in your application: In above code, I created a **DefaultPluginManager** (it's the default implementation for **PluginManager** interface) that loads and starts all active(resolved) plugins. -The available plugins are loaded using a **PluginClassLoader**. +Each available plugin is loaded using a **PluginClassLoader**. The **PluginClassLoader** contains only classes found in _classes_ and _lib_ folders of plugin and runtime classes and libraries of the required plugins. The plugins are stored in a folder. You can specify the plugins folder in the constructor of DefaultPluginManager. If the plugins folder is not specified than the location is returned by `System.getProperty("pf4j.pluginsDir", "plugins")`. @@ -125,22 +135,77 @@ The output is: >>> Welcome >>> Hello +You can inject your custom component (for example PluginDescriptorFinder, ExtensionFinder) in DefaultPluginManager just override createXXX methods (factory method pattern). + +Example: + + protected PluginDescriptorFinder createPluginDescriptorFinder() { + return new PropertiesPluginDescriptorFinder(); + } + +and in plugin respository you must have a plugin.properties file with the below content: + + plugin.class=ro.fortsoft.pf4j.demo.welcome.WelcomePlugin + plugin.dependencies=x, y, z + plugin.id=welcome-plugin + plugin.provider=Decebal Suiu + plugin.version=0.0.1 + + For more information please see the demo sources. -Demo +Enable/Disable plugins ------------------- +In theory, it's a relation **1:N** between an extension point and the extensions for this extension point. +This works well, except for when you develop multiple plugins for this extension point as different options for your clients to decide on which one to use. +In this situation you wish a possibility to disable all but one extension. +For example I have an extension point for sending mail (EmailSender interface) with two extensions: one based on Sendgrid and another +based on Amazon Simple Email Service. +The first extension is located in Plugin1 and the second extension is located in Plugin2. +I want to go only with one extension ( **1:1** relation between extension point and extensions) and to achieve this I have two options: +1) uninstall Plugin1 or Plugin2 (remove folder pluginX.zip and pluginX from plugins folder) +2) disable Plugin1 or Plugin2 + +For option two you must create a simple file **enabled.txt** or **disabled.txt** in your plugins folder. +The content for **enabled.txt** is similar with: + + ######################################## + # - load only these plugins + # - add one plugin id on each line + # - put this file in plugins folder + ######################################## + welcome-plugin + +The content for **disabled.txt** is similar with: + + ######################################## + # - load all plugins except these + # - add one plugin id on each line + # - put this file in plugins folder + ######################################## + welcome-plugin + +All comment lines (line that start with # character) are ignored. +If a file with enabled.txt exists than disabled.txt is ignored. See enabled.txt and disabled.txt from the demo folder. +Demo +------------------- I have a tiny demo application. The demo application is in demo folder. In demo/api folder I declared an extension point (_Greeting_). In demo/plugin* I implemented two plugins: plugin1, plugin2 (each plugin adds an extension for _Greeting_). To run the demo application use: - ./run-demo.sh + ./run-demo.sh (for Linux/Unix) + ./run-demo.bat (for Windows) + +Mailing list +-------------- + +Much of the conversation between developers and users is managed through [mailing list] (http://groups.google.com/group/pf4j). License -------------- - Copyright 2012 Decebal Suiu Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with diff --git a/demo/api/pom.xml b/demo/api/pom.xml index 1b99d58..2a2a04b 100644 --- a/demo/api/pom.xml +++ b/demo/api/pom.xml @@ -3,24 +3,16 @@ ro.fortsoft.pf4j.demo - pom - 0.4-SNAPSHOT + pf4j-demo-parent + 0.5-SNAPSHOT 4.0.0 pf4j-demo-api - 0.4-SNAPSHOT + 0.5-SNAPSHOT jar Demo Api - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - diff --git a/demo/app/pom.xml b/demo/app/pom.xml index b9db6ab..cef9586 100644 --- a/demo/app/pom.xml +++ b/demo/app/pom.xml @@ -3,23 +3,15 @@ ro.fortsoft.pf4j.demo - pom - 0.4-SNAPSHOT + pf4j-demo-parent + 0.5-SNAPSHOT 4.0.0 pf4j-demo-app - 0.4-SNAPSHOT + 0.5-SNAPSHOT jar Demo App - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - ro.fortsoft.pf4j.demo.Boot diff --git a/demo/app/src/main/resources/log4j.properties b/demo/app/src/main/resources/log4j.properties index b3e5aa8..f2160e8 100644 --- a/demo/app/src/main/resources/log4j.properties +++ b/demo/app/src/main/resources/log4j.properties @@ -4,7 +4,3 @@ log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n -log4j.logger.org.apache.wicket=INFO -log4j.logger.org.apache.wicket.protocol.http.HttpSessionStore=INFO -log4j.logger.org.apache.wicket.version=INFO -log4j.logger.org.apache.wicket.RequestCycle=INFO diff --git a/demo/disabled.txt b/demo/disabled.txt new file mode 100644 index 0000000..b8fedab --- /dev/null +++ b/demo/disabled.txt @@ -0,0 +1,6 @@ +######################################## +# - load all plugins except these +# - add one plugin id on each line +# - put this file in plugins folder +######################################## +welcome-plugin diff --git a/demo/enabled.txt b/demo/enabled.txt new file mode 100644 index 0000000..96a92b3 --- /dev/null +++ b/demo/enabled.txt @@ -0,0 +1,6 @@ +######################################## +# - load only these plugins +# - add one plugin id on each line +# - put this file in plugins folder +######################################## +welcome-plugin diff --git a/demo/plugin1/pom.xml b/demo/plugin1/pom.xml index b332a96..49cf1df 100644 --- a/demo/plugin1/pom.xml +++ b/demo/plugin1/pom.xml @@ -3,24 +3,16 @@ ro.fortsoft.pf4j.demo - pom - 0.4-SNAPSHOT + pf4j-demo-parent + 0.5-SNAPSHOT 4.0.0 pf4j-demo-plugin1 - 0.4-SNAPSHOT + 0.5-SNAPSHOT jar Demo Plugin #1 - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - welcome-plugin ro.fortsoft.pf4j.demo.welcome.WelcomePlugin diff --git a/demo/plugin2/pom.xml b/demo/plugin2/pom.xml index 88e586f..b69f767 100644 --- a/demo/plugin2/pom.xml +++ b/demo/plugin2/pom.xml @@ -3,24 +3,16 @@ ro.fortsoft.pf4j.demo - pom - 0.4-SNAPSHOT + pf4j-demo-parent + 0.5-SNAPSHOT 4.0.0 pf4j-demo-plugin2 - 0.4-SNAPSHOT + 0.5-SNAPSHOT jar Demo Plugin #2 - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - hello-plugin ro.fortsoft.pf4j.demo.hello.HelloPlugin diff --git a/demo/pom.xml b/demo/pom.xml index 6520ea5..76bd4d7 100644 --- a/demo/pom.xml +++ b/demo/pom.xml @@ -3,24 +3,16 @@ ro.fortsoft.pf4j - pom - 0.4-SNAPSHOT + pf4j-parent + 0.5-SNAPSHOT 4.0.0 ro.fortsoft.pf4j.demo - pom - 0.4-SNAPSHOT + pf4j-demo-parent + 0.5-SNAPSHOT pom - PF4J Demo - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + Demo Parent diff --git a/pf4j/pom.xml b/pf4j/pom.xml index e0ccfb8..68b0710 100644 --- a/pf4j/pom.xml +++ b/pf4j/pom.xml @@ -3,31 +3,16 @@ ro.fortsoft.pf4j - pom - 0.4-SNAPSHOT + pf4j-parent + 0.5-SNAPSHOT 4.0.0 pf4j - 0.4-SNAPSHOT + 0.5-SNAPSHOT jar - PF4J Library + PF4J Plugin Framework for Java - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - scm:git:https://github.com/decebals/pf4j.git - scm:git:https://github.com/decebals/pf4j.git - git@github.com/decebals/pf4j.git - HEAD - diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java index 66c896c..492a701 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java @@ -30,7 +30,7 @@ import net.java.sezpoz.IndexItem; */ public class DefaultExtensionFinder implements ExtensionFinder { - private static final Logger LOG = LoggerFactory.getLogger(DefaultExtensionFinder.class); + private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class); private volatile List> indices; private ClassLoader classLoader; @@ -41,7 +41,7 @@ public class DefaultExtensionFinder implements ExtensionFinder { @Override public List> find(Class type) { - LOG.debug("Find extensions for " + type); + log.debug("Find extensions for " + type); List> result = new ArrayList>(); getIndices(); // System.out.println("indices = "+ indices); @@ -49,16 +49,16 @@ public class DefaultExtensionFinder implements ExtensionFinder { try { AnnotatedElement element = item.element(); Class extensionType = (Class) element; - LOG.debug("Checking extension type " + extensionType); + log.debug("Checking extension type " + extensionType); if (type.isAssignableFrom(extensionType)) { Object instance = item.instance(); if (instance != null) { - LOG.debug("Added extension " + extensionType); + log.debug("Added extension " + extensionType); result.add(new ExtensionWrapper(type.cast(instance), item.annotation().ordinal())); } } } catch (InstantiationException e) { - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); } } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java index 201ebb1..3e23ff1 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.util.jar.Attributes; import java.util.jar.Manifest; +import ro.fortsoft.pf4j.util.StringUtils; + /** * Read the plugin descriptor from the manifest file. * @@ -31,7 +33,6 @@ public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder { // TODO it's ok with classes/ ? File manifestFile = new File(pluginRepository, "classes/META-INF/MANIFEST.MF"); if (!manifestFile.exists()) { - // not found a 'plugin.xml' file for this plugin throw new PluginException("Cannot find '" + manifestFile + "' file"); } @@ -60,19 +61,19 @@ public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder { // TODO validate !!! Attributes attrs = manifest.getMainAttributes(); String id = attrs.getValue("Plugin-Id"); - if (isEmpty(id)) { + if (StringUtils.isEmpty(id)) { throw new PluginException("Plugin-Id cannot be empty"); } pluginDescriptor.setPluginId(id); String clazz = attrs.getValue("Plugin-Class"); - if (isEmpty(clazz)) { + if (StringUtils.isEmpty(clazz)) { throw new PluginException("Plugin-Class cannot be empty"); } pluginDescriptor.setPluginClass(clazz); String version = attrs.getValue("Plugin-Version"); - if (isEmpty(version)) { + if (StringUtils.isEmpty(version)) { throw new PluginException("Plugin-Version cannot be empty"); } pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version)); @@ -84,8 +85,5 @@ public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder { return pluginDescriptor; } - - private boolean isEmpty(String value) { - return (value == null) || value.isEmpty(); - } + } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java index 750e334..20aba2d 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.util.CompoundClassLoader; import ro.fortsoft.pf4j.util.DirectoryFilter; +import ro.fortsoft.pf4j.util.FileUtils; import ro.fortsoft.pf4j.util.Unzip; import ro.fortsoft.pf4j.util.ZipFilter; @@ -36,7 +37,7 @@ import ro.fortsoft.pf4j.util.ZipFilter; */ public class DefaultPluginManager implements PluginManager { - private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginManager.class); + private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class); /** * The plugins repository. @@ -71,21 +72,19 @@ public class DefaultPluginManager implements PluginManager { * A list with resolved plugins (resolved dependency). */ private List resolvedPlugins; - - /** - * A list with disabled plugins. - */ - private List disabledPlugins; /** * A list with started plugins. */ private List startedPlugins; + private List enabledPlugins; + private List disabledPlugins; + /** * A compound class loader of resolved plugins. */ - private CompoundClassLoader compoundClassLoader; + protected CompoundClassLoader compoundClassLoader; /** * Th plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). @@ -108,16 +107,29 @@ public class DefaultPluginManager implements PluginManager { pathToIdMap = new HashMap(); unresolvedPlugins = new ArrayList(); resolvedPlugins = new ArrayList(); - disabledPlugins = new ArrayList(); startedPlugins = new ArrayList(); - pluginDescriptorFinder = new DefaultPluginDescriptorFinder(); + disabledPlugins = new ArrayList(); compoundClassLoader = new CompoundClassLoader(); - extensionFinder = new DefaultExtensionFinder(compoundClassLoader); + pluginDescriptorFinder = createPluginDescriptorFinder(); + extensionFinder = createExtensionFinder(); + + 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); + 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); + log.info("Disabled plugins: " + disabledPlugins); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath()); } - @Override + @Override public List getPlugins() { return new ArrayList(plugins.values()); } @@ -136,10 +148,6 @@ public class DefaultPluginManager implements PluginManager { return unresolvedPlugins; } - public List getDisabledPlugins() { - return disabledPlugins; - } - @Override public List getStartedPlugins() { return startedPlugins; @@ -152,12 +160,12 @@ public class DefaultPluginManager implements PluginManager { public void startPlugins() { for (PluginWrapper pluginWrapper : resolvedPlugins) { try { - LOG.info("Start plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'"); + log.info("Start plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'"); pluginWrapper.getPlugin().start(); pluginWrapper.setPluginState(PluginState.STARTED); startedPlugins.add(pluginWrapper); } catch (PluginException e) { - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); } } } @@ -171,11 +179,11 @@ public class DefaultPluginManager implements PluginManager { Collections.reverse(startedPlugins); for (PluginWrapper pluginWrapper : startedPlugins) { try { - LOG.info("Stop plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'"); + log.info("Stop plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'"); pluginWrapper.getPlugin().stop(); pluginWrapper.setPluginState(PluginState.STOPPED); } catch (PluginException e) { - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); } } } @@ -187,7 +195,7 @@ public class DefaultPluginManager implements PluginManager { public void loadPlugins() { // check for plugins directory if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) { - LOG.error("No '" + pluginsDirectory + "' directory"); + log.error("No '" + pluginsDirectory + "' directory"); return; } @@ -198,32 +206,32 @@ public class DefaultPluginManager implements PluginManager { try { expandPluginArchive(zipFile); } catch (IOException e) { - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); } } - // load any plugin from plugins directory + // check for no plugins FilenameFilter directoryFilter = new DirectoryFilter(); String[] directories = pluginsDirectory.list(directoryFilter); + if (directories.length == 0) { + log.info("No plugins"); + return; + } + + // load any plugin from plugins directory for (String directory : directories) { try { loadPlugin(directory); } catch (PluginException e) { - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); } } - // check for no plugins - if (directories.length == 0) { - LOG.info("No plugins"); - return; - } - // resolve 'unresolvedPlugins' try { resolvePlugins(); } catch (PluginException e) { - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); } } @@ -270,33 +278,34 @@ public class DefaultPluginManager implements PluginManager { // try to load the plugin String pluginPath = "/".concat(fileName); - // test for disabled plugin - if (disabledPlugins.contains(pluginPath)) { - return; - } - // test for plugin duplication if (plugins.get(pathToIdMap.get(pluginPath)) != null) { return; } // retrieves the plugin descriptor - LOG.debug("Find plugin descriptor '" + pluginPath + "'"); + log.debug("Find plugin descriptor '" + pluginPath + "'"); PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory); - LOG.debug("Descriptor " + pluginDescriptor); + log.debug("Descriptor " + pluginDescriptor); String pluginClassName = pluginDescriptor.getPluginClass(); - LOG.debug("Class '" + pluginClassName + "'" + " for plugin '" + pluginPath + "'"); + log.debug("Class '" + pluginClassName + "'" + " for plugin '" + pluginPath + "'"); + + // test for disabled plugin + if (isPluginDisabled(pluginDescriptor.getPluginId())) { + log.info("Plugin '" + pluginPath + "' is disabled"); + return; + } // load plugin - LOG.debug("Loading plugin '" + pluginPath + "'"); + log.debug("Loading plugin '" + pluginPath + "'"); PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory); pluginLoader.load(); - LOG.debug("Loaded plugin '" + pluginPath + "'"); + log.debug("Loaded plugin '" + pluginPath + "'"); // create the plugin wrapper - LOG.debug("Creating wrapper for plugin '" + pluginPath + "'"); + log.debug("Creating wrapper for plugin '" + pluginPath + "'"); PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader()); - LOG.debug("Created wrapper '" + pluginWrapper + "' for plugin '" + pluginPath + "'"); + log.debug("Created wrapper '" + pluginWrapper + "' for plugin '" + pluginPath + "'"); String pluginId = pluginDescriptor.getPluginId(); @@ -309,6 +318,28 @@ public class DefaultPluginManager implements PluginManager { pluginClassLoaders.put(pluginId, pluginClassLoader); } + /** + * Add the possibility to override the PluginDescriptorFinder. + */ + protected PluginDescriptorFinder createPluginDescriptorFinder() { + return new DefaultPluginDescriptorFinder(); + } + + /** + * Add the possibility to override the ExtensionFinder. + */ + protected ExtensionFinder createExtensionFinder() { + return new DefaultExtensionFinder(compoundClassLoader); + } + + protected boolean isPluginDisabled(String pluginId) { + if (enabledPlugins.isEmpty()) { + return disabledPlugins.contains(pluginId); + } + + return !enabledPlugins.contains(pluginId); + } + private void expandPluginArchive(String fileName) throws IOException { File pluginArchiveFile = new File(pluginsDirectory, fileName); long pluginArchiveDate = pluginArchiveFile.lastModified(); @@ -316,7 +347,7 @@ public class DefaultPluginManager implements PluginManager { 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 '" + pluginArchiveFile + "' in '" + pluginDirectory + "'"); + log.debug("Expand plugin archive '" + pluginArchiveFile + "' in '" + pluginDirectory + "'"); // create directorie for plugin pluginDirectory.mkdirs(); @@ -338,8 +369,8 @@ public class DefaultPluginManager implements PluginManager { for (PluginWrapper pluginWrapper : resolvedPlugins) { unresolvedPlugins.remove(pluginWrapper); compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader()); - LOG.info("Plugin '" + pluginWrapper.getDescriptor().getPluginId() + "' resolved"); + log.info("Plugin '" + pluginWrapper.getDescriptor().getPluginId() + "' resolved"); } } - + } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java index 70c1b90..8611fef 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java @@ -25,7 +25,7 @@ import ro.fortsoft.pf4j.util.DirectedGraph; */ class DependencyResolver { - private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class); + private static final Logger log = LoggerFactory.getLogger(DependencyResolver.class); private List plugins; @@ -51,14 +51,14 @@ class DependencyResolver { } } - LOG.debug("Graph: " + graph); + log.debug("Graph: " + graph); List pluginsId = graph.reverseTopologicalSort(); if (pluginsId == null) { throw new CyclicDependencyException("Cyclic dependences !!!" + graph.toString()); } - LOG.debug("Plugins order: " + pluginsId); + log.debug("Plugins order: " + pluginsId); List sortedPlugins = new ArrayList(); for (String pluginId : pluginsId) { sortedPlugins.add(getPlugin(pluginId)); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java index 80832bc..60c79ca 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java @@ -23,8 +23,8 @@ import java.util.List; */ public class PluginClassLoader extends URLClassLoader { - private static final String JAVA_PACKAGE_PREFIX = "java."; - private static final String JAVAX_PACKAGE_PREFIX = "javax."; +// private static final String JAVA_PACKAGE_PREFIX = "java."; +// private static final String JAVAX_PACKAGE_PREFIX = "javax."; private static final String PLUGIN_PACKAGE_PREFIX = "ro.fortsoft.pf4j."; private PluginManager pluginManager; @@ -46,10 +46,13 @@ public class PluginClassLoader extends URLClassLoader { public Class loadClass(String className) throws ClassNotFoundException { // System.out.println(">>>" + className); + /* + // javax.mail is not in JDK ?! // first check whether it's a system class, delegate to the system loader if (className.startsWith(JAVA_PACKAGE_PREFIX) || className.startsWith(JAVAX_PACKAGE_PREFIX)) { return findSystemClass(className); } + */ // second check whether it's already been loaded Class loadedClass = findLoadedClass(className); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java index 7533f57..78603f4 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java @@ -33,7 +33,7 @@ import ro.fortsoft.pf4j.util.JarFilter; */ class PluginLoader { - private static final Logger LOG = LoggerFactory.getLogger(PluginLoader.class); + private static final Logger log = LoggerFactory.getLogger(PluginLoader.class); /* * The plugin repository. @@ -58,7 +58,7 @@ class PluginLoader { libDirectory = new File(pluginRepository, "lib"); ClassLoader parent = getClass().getClassLoader(); pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent); - LOG.debug("Created class loader " + pluginClassLoader); + log.debug("Created class loader " + pluginClassLoader); } public File getPluginRepository() { @@ -100,14 +100,14 @@ class PluginLoader { classesDirectory = classesDirectory.getAbsoluteFile(); if (classesDirectory.exists() && classesDirectory.isDirectory()) { - LOG.debug("Found '" + classesDirectory.getPath() + "' directory"); + log.debug("Found '" + classesDirectory.getPath() + "' directory"); try { pluginClassLoader.addURL(classesDirectory.toURI().toURL()); - LOG.debug("Added '" + classesDirectory + "' to the class loader path"); + log.debug("Added '" + classesDirectory + "' to the class loader path"); } catch (MalformedURLException e) { e.printStackTrace(); - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); return false; } } @@ -128,10 +128,10 @@ class PluginLoader { File jarFile = new File(libDirectory, jar); try { pluginClassLoader.addURL(jarFile.toURI().toURL()); - LOG.debug("Added '" + jarFile + "' to the class loader path"); + log.debug("Added '" + jarFile + "' to the class loader path"); } catch (MalformedURLException e) { e.printStackTrace(); - LOG.error(e.getMessage(), e); + log.error(e.getMessage(), e); return false; } } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PropertiesPluginDescriptorFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PropertiesPluginDescriptorFinder.java new file mode 100644 index 0000000..4f0e753 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PropertiesPluginDescriptorFinder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or 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.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import ro.fortsoft.pf4j.util.StringUtils; + +/** + * Find a plugin descriptor in a properties file (in plugin repository). + * + * @author Decebal Suiu + */ +public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder { + + private String propertiesFileName; + + public PropertiesPluginDescriptorFinder() { + this("plugin.properties"); + } + + public PropertiesPluginDescriptorFinder(String propertiesFileName) { + this.propertiesFileName = propertiesFileName; + } + + @Override + public PluginDescriptor find(File pluginRepository) throws PluginException { + File propertiesFile = new File(pluginRepository, propertiesFileName); + if (!propertiesFile.exists()) { + throw new PluginException("Cannot find '" + propertiesFile + "' file"); + } + + InputStream input = null; + try { + input = new FileInputStream(propertiesFile); + } catch (FileNotFoundException e) { + // not happening + } + + Properties properties = new Properties(); + try { + 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); + } + } + + PluginDescriptor pluginDescriptor = new PluginDescriptor(); + + // TODO validate !!! + String id = properties.getProperty("plugin.id"); + if (StringUtils.isEmpty(id)) { + throw new PluginException("plugin.id cannot be empty"); + } + pluginDescriptor.setPluginId(id); + + String clazz = properties.getProperty("plugin.class"); + if (StringUtils.isEmpty(clazz)) { + throw new PluginException("plugin.class cannot be empty"); + } + pluginDescriptor.setPluginClass(clazz); + + String version = properties.getProperty("plugin.version"); + if (StringUtils.isEmpty(version)) { + throw new PluginException("plugin.version cannot be empty"); + } + pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version)); + + String provider = properties.getProperty("plugin.provider"); + pluginDescriptor.setProvider(provider); + String dependencies = properties.getProperty("plugin.dependencies"); + pluginDescriptor.setDependencies(dependencies); + + return pluginDescriptor; + } + +} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java b/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java new file mode 100644 index 0000000..0b8c38a --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or 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.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Decebal Suiu + */ +public class FileUtils { + + public static List readLines(File file, boolean ignoreComments) throws IOException { + if (!file.exists() || !file.isFile()) { + return Collections.emptyList(); + } + + List lines = new ArrayList(); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) { + if (ignoreComments && !line.startsWith("#") && !lines.contains(line)) { + lines.add(line); + } + } + } finally { + if (reader != null) { + reader.close(); + } + } + + return lines; + } + +} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/util/StringUtils.java b/pf4j/src/main/java/ro/fortsoft/pf4j/util/StringUtils.java new file mode 100644 index 0000000..c133255 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/StringUtils.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or 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.util; + +/** + * @author Decebal Suiu + */ +public class StringUtils { + + public static boolean isEmpty(String str) { + return (str == null) || str.isEmpty(); + } + +} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java b/pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java index 4a2c9a3..e3f3ed3 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java @@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory; */ public class Unzip { - private static final Logger LOG = LoggerFactory.getLogger(Unzip.class); + private static final Logger log = LoggerFactory.getLogger(Unzip.class); /** * Holds the destination directory. @@ -61,7 +61,7 @@ public class Unzip { } public void extract() throws IOException { - LOG.debug("Extract content of " + source + " to " + destination); + log.debug("Extract content of " + source + " to " + destination); // delete destination file if exists removeDirectory(destination); @@ -91,7 +91,7 @@ public class Unzip { fos.close(); } } catch (FileNotFoundException e) { - LOG.error("File '" + zipEntry.getName() + "' not found"); + log.error("File '" + zipEntry.getName() + "' not found"); } } diff --git a/pom.xml b/pom.xml index 14a07b5..3c9bb2e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,10 +9,10 @@ 4.0.0 ro.fortsoft.pf4j - pom - 0.4-SNAPSHOT + pf4j-parent + 0.5-SNAPSHOT pom - PF4J + PF4J Parent Plugin Framework for Java