Browse Source

Merge branch 'master' of https://github.com/decebals/pf4j

pull/3/head
Decebal Suiu 12 years ago
parent
commit
60fb88af76
  1. 85
      README.md
  2. 14
      demo/api/pom.xml
  3. 14
      demo/app/pom.xml
  4. 4
      demo/app/src/main/resources/log4j.properties
  5. 6
      demo/disabled.txt
  6. 6
      demo/enabled.txt
  7. 14
      demo/plugin1/pom.xml
  8. 14
      demo/plugin2/pom.xml
  9. 18
      demo/pom.xml
  10. 23
      pf4j/pom.xml
  11. 10
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java
  12. 14
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java
  13. 121
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
  14. 6
      pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java
  15. 7
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java
  16. 14
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java
  17. 97
      pf4j/src/main/java/ro/fortsoft/pf4j/PropertiesPluginDescriptorFinder.java
  18. 53
      pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java
  19. 24
      pf4j/src/main/java/ro/fortsoft/pf4j/util/StringUtils.java
  20. 6
      pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java
  21. 6
      pom.xml

85
README.md

@ -1,12 +1,23 @@
Plugin Framework for Java (PF4J) 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 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. - **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). - **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. - **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 Artifacts
------------------- -------------------
- PF4J `pf4j` (jar) - PF4J `pf4j` (jar)
- PF4J Demo `pf4j-demo` (executable jar) - PF4J Demo `pf4j-demo` (executable jar)
Using Maven Using Maven
------------------- -------------------
In your pom.xml you must define the dependencies to PF4J artifacts with: In your pom.xml you must define the dependencies to PF4J artifacts with:
```xml ```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. 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 How to use
------------------- -------------------
It's very simple to add pf4j in your application: It's very simple to add pf4j in your application:
public static void main(String[] args) { 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 In above code, I created a **DefaultPluginManager** (it's the default implementation for
**PluginManager** interface) that loads and starts all active(resolved) plugins. **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 **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 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")`. than the location is returned by `System.getProperty("pf4j.pluginsDir", "plugins")`.
@ -125,22 +135,77 @@ The output is:
>>> Welcome >>> Welcome
>>> Hello >>> 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. 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. 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/api folder I declared an extension point (_Greeting_).
In demo/plugin* I implemented two plugins: plugin1, plugin2 (each plugin adds an extension for _Greeting_). In demo/plugin* I implemented two plugins: plugin1, plugin2 (each plugin adds an extension for _Greeting_).
To run the demo application use: 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 License
-------------- --------------
Copyright 2012 Decebal Suiu Copyright 2012 Decebal Suiu
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with

14
demo/api/pom.xml

@ -3,24 +3,16 @@
<parent> <parent>
<groupId>ro.fortsoft.pf4j.demo</groupId> <groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-api</artifactId> <artifactId>pf4j-demo-api</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo Api</name> <name>Demo Api</name>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

14
demo/app/pom.xml

@ -3,23 +3,15 @@
<parent> <parent>
<groupId>ro.fortsoft.pf4j.demo</groupId> <groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-app</artifactId> <artifactId>pf4j-demo-app</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo App</name> <name>Demo App</name>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<properties> <properties>
<main.class>ro.fortsoft.pf4j.demo.Boot</main.class> <main.class>ro.fortsoft.pf4j.demo.Boot</main.class>

4
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=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n 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

6
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

6
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

14
demo/plugin1/pom.xml

@ -3,24 +3,16 @@
<parent> <parent>
<groupId>ro.fortsoft.pf4j.demo</groupId> <groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin1</artifactId> <artifactId>pf4j-demo-plugin1</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo Plugin #1</name> <name>Demo Plugin #1</name>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<properties> <properties>
<plugin.id>welcome-plugin</plugin.id> <plugin.id>welcome-plugin</plugin.id>
<plugin.class>ro.fortsoft.pf4j.demo.welcome.WelcomePlugin</plugin.class> <plugin.class>ro.fortsoft.pf4j.demo.welcome.WelcomePlugin</plugin.class>

14
demo/plugin2/pom.xml

@ -3,24 +3,16 @@
<parent> <parent>
<groupId>ro.fortsoft.pf4j.demo</groupId> <groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin2</artifactId> <artifactId>pf4j-demo-plugin2</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo Plugin #2</name> <name>Demo Plugin #2</name>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<properties> <properties>
<plugin.id>hello-plugin</plugin.id> <plugin.id>hello-plugin</plugin.id>
<plugin.class>ro.fortsoft.pf4j.demo.hello.HelloPlugin</plugin.class> <plugin.class>ro.fortsoft.pf4j.demo.hello.HelloPlugin</plugin.class>

18
demo/pom.xml

@ -3,24 +3,16 @@
<parent> <parent>
<groupId>ro.fortsoft.pf4j</groupId> <groupId>ro.fortsoft.pf4j</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>ro.fortsoft.pf4j.demo</groupId> <groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>PF4J Demo</name> <name>Demo Parent</name>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<build> <build>
<resources> <resources>

23
pf4j/pom.xml

@ -3,31 +3,16 @@
<parent> <parent>
<groupId>ro.fortsoft.pf4j</groupId> <groupId>ro.fortsoft.pf4j</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j</artifactId> <artifactId>pf4j</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>PF4J Library</name> <name>PF4J</name>
<description>Plugin Framework for Java</description> <description>Plugin Framework for Java</description>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:https://github.com/decebals/pf4j.git</connection>
<developerConnection>scm:git:https://github.com/decebals/pf4j.git</developerConnection>
<url>git@github.com/decebals/pf4j.git</url>
<tag>HEAD</tag>
</scm>
<dependencies> <dependencies>
<dependency> <dependency>

10
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java

@ -30,7 +30,7 @@ import net.java.sezpoz.IndexItem;
*/ */
public class DefaultExtensionFinder implements ExtensionFinder { 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<IndexItem<Extension, Object>> indices; private volatile List<IndexItem<Extension, Object>> indices;
private ClassLoader classLoader; private ClassLoader classLoader;
@ -41,7 +41,7 @@ public class DefaultExtensionFinder implements ExtensionFinder {
@Override @Override
public <T> List<ExtensionWrapper<T>> find(Class<T> type) { public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
LOG.debug("Find extensions for " + type); log.debug("Find extensions for " + type);
List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>(); List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>();
getIndices(); getIndices();
// System.out.println("indices = "+ indices); // System.out.println("indices = "+ indices);
@ -49,16 +49,16 @@ public class DefaultExtensionFinder implements ExtensionFinder {
try { try {
AnnotatedElement element = item.element(); AnnotatedElement element = item.element();
Class<?> extensionType = (Class<?>) element; Class<?> extensionType = (Class<?>) element;
LOG.debug("Checking extension type " + extensionType); log.debug("Checking extension type " + extensionType);
if (type.isAssignableFrom(extensionType)) { if (type.isAssignableFrom(extensionType)) {
Object instance = item.instance(); Object instance = item.instance();
if (instance != null) { if (instance != null) {
LOG.debug("Added extension " + extensionType); log.debug("Added extension " + extensionType);
result.add(new ExtensionWrapper<T>(type.cast(instance), item.annotation().ordinal())); result.add(new ExtensionWrapper<T>(type.cast(instance), item.annotation().ordinal()));
} }
} }
} catch (InstantiationException e) { } catch (InstantiationException e) {
LOG.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }

14
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.Attributes;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import ro.fortsoft.pf4j.util.StringUtils;
/** /**
* Read the plugin descriptor from the manifest file. * Read the plugin descriptor from the manifest file.
* *
@ -31,7 +33,6 @@ public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder {
// TODO it's ok with classes/ ? // TODO it's ok with classes/ ?
File manifestFile = new File(pluginRepository, "classes/META-INF/MANIFEST.MF"); File manifestFile = new File(pluginRepository, "classes/META-INF/MANIFEST.MF");
if (!manifestFile.exists()) { if (!manifestFile.exists()) {
// not found a 'plugin.xml' file for this plugin
throw new PluginException("Cannot find '" + manifestFile + "' file"); throw new PluginException("Cannot find '" + manifestFile + "' file");
} }
@ -60,19 +61,19 @@ public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder {
// TODO validate !!! // TODO validate !!!
Attributes attrs = manifest.getMainAttributes(); Attributes attrs = manifest.getMainAttributes();
String id = attrs.getValue("Plugin-Id"); String id = attrs.getValue("Plugin-Id");
if (isEmpty(id)) { if (StringUtils.isEmpty(id)) {
throw new PluginException("Plugin-Id cannot be empty"); throw new PluginException("Plugin-Id cannot be empty");
} }
pluginDescriptor.setPluginId(id); pluginDescriptor.setPluginId(id);
String clazz = attrs.getValue("Plugin-Class"); String clazz = attrs.getValue("Plugin-Class");
if (isEmpty(clazz)) { if (StringUtils.isEmpty(clazz)) {
throw new PluginException("Plugin-Class cannot be empty"); throw new PluginException("Plugin-Class cannot be empty");
} }
pluginDescriptor.setPluginClass(clazz); pluginDescriptor.setPluginClass(clazz);
String version = attrs.getValue("Plugin-Version"); String version = attrs.getValue("Plugin-Version");
if (isEmpty(version)) { if (StringUtils.isEmpty(version)) {
throw new PluginException("Plugin-Version cannot be empty"); throw new PluginException("Plugin-Version cannot be empty");
} }
pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version)); pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version));
@ -84,8 +85,5 @@ public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder {
return pluginDescriptor; return pluginDescriptor;
} }
private boolean isEmpty(String value) {
return (value == null) || value.isEmpty();
}
} }

121
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.CompoundClassLoader;
import ro.fortsoft.pf4j.util.DirectoryFilter; import ro.fortsoft.pf4j.util.DirectoryFilter;
import ro.fortsoft.pf4j.util.FileUtils;
import ro.fortsoft.pf4j.util.Unzip; import ro.fortsoft.pf4j.util.Unzip;
import ro.fortsoft.pf4j.util.ZipFilter; import ro.fortsoft.pf4j.util.ZipFilter;
@ -36,7 +37,7 @@ import ro.fortsoft.pf4j.util.ZipFilter;
*/ */
public class DefaultPluginManager implements PluginManager { 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. * The plugins repository.
@ -71,21 +72,19 @@ public class DefaultPluginManager implements PluginManager {
* A list with resolved plugins (resolved dependency). * A list with resolved plugins (resolved dependency).
*/ */
private List<PluginWrapper> resolvedPlugins; private List<PluginWrapper> resolvedPlugins;
/**
* A list with disabled plugins.
*/
private List<PluginWrapper> disabledPlugins;
/** /**
* A list with started plugins. * A list with started plugins.
*/ */
private List<PluginWrapper> startedPlugins; private List<PluginWrapper> startedPlugins;
private List<String> enabledPlugins;
private List<String> disabledPlugins;
/** /**
* A compound class loader of resolved plugins. * A compound class loader of resolved plugins.
*/ */
private CompoundClassLoader compoundClassLoader; protected CompoundClassLoader compoundClassLoader;
/** /**
* Th plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). * Th plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
@ -108,16 +107,29 @@ public class DefaultPluginManager implements PluginManager {
pathToIdMap = new HashMap<String, String>(); pathToIdMap = new HashMap<String, String>();
unresolvedPlugins = new ArrayList<PluginWrapper>(); unresolvedPlugins = new ArrayList<PluginWrapper>();
resolvedPlugins = new ArrayList<PluginWrapper>(); resolvedPlugins = new ArrayList<PluginWrapper>();
disabledPlugins = new ArrayList<PluginWrapper>();
startedPlugins = new ArrayList<PluginWrapper>(); startedPlugins = new ArrayList<PluginWrapper>();
pluginDescriptorFinder = new DefaultPluginDescriptorFinder(); disabledPlugins = new ArrayList<String>();
compoundClassLoader = new CompoundClassLoader(); 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()); System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath());
} }
@Override @Override
public List<PluginWrapper> getPlugins() { public List<PluginWrapper> getPlugins() {
return new ArrayList<PluginWrapper>(plugins.values()); return new ArrayList<PluginWrapper>(plugins.values());
} }
@ -136,10 +148,6 @@ public class DefaultPluginManager implements PluginManager {
return unresolvedPlugins; return unresolvedPlugins;
} }
public List<PluginWrapper> getDisabledPlugins() {
return disabledPlugins;
}
@Override @Override
public List<PluginWrapper> getStartedPlugins() { public List<PluginWrapper> getStartedPlugins() {
return startedPlugins; return startedPlugins;
@ -152,12 +160,12 @@ public class DefaultPluginManager implements PluginManager {
public void startPlugins() { public void startPlugins() {
for (PluginWrapper pluginWrapper : resolvedPlugins) { for (PluginWrapper pluginWrapper : resolvedPlugins) {
try { try {
LOG.info("Start plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'"); log.info("Start plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'");
pluginWrapper.getPlugin().start(); pluginWrapper.getPlugin().start();
pluginWrapper.setPluginState(PluginState.STARTED); pluginWrapper.setPluginState(PluginState.STARTED);
startedPlugins.add(pluginWrapper); startedPlugins.add(pluginWrapper);
} catch (PluginException e) { } 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); Collections.reverse(startedPlugins);
for (PluginWrapper pluginWrapper : startedPlugins) { for (PluginWrapper pluginWrapper : startedPlugins) {
try { try {
LOG.info("Stop plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'"); log.info("Stop plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'");
pluginWrapper.getPlugin().stop(); pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED); pluginWrapper.setPluginState(PluginState.STOPPED);
} catch (PluginException e) { } 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() { public void loadPlugins() {
// check for plugins directory // check for plugins directory
if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) { if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
LOG.error("No '" + pluginsDirectory + "' directory"); log.error("No '" + pluginsDirectory + "' directory");
return; return;
} }
@ -198,32 +206,32 @@ public class DefaultPluginManager implements PluginManager {
try { try {
expandPluginArchive(zipFile); expandPluginArchive(zipFile);
} catch (IOException e) { } 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(); FilenameFilter directoryFilter = new DirectoryFilter();
String[] directories = pluginsDirectory.list(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) { for (String directory : directories) {
try { try {
loadPlugin(directory); loadPlugin(directory);
} catch (PluginException e) { } 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' // resolve 'unresolvedPlugins'
try { try {
resolvePlugins(); resolvePlugins();
} catch (PluginException e) { } 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 // try to load the plugin
String pluginPath = "/".concat(fileName); String pluginPath = "/".concat(fileName);
// test for disabled plugin
if (disabledPlugins.contains(pluginPath)) {
return;
}
// test for plugin duplication // test for plugin duplication
if (plugins.get(pathToIdMap.get(pluginPath)) != null) { if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
return; return;
} }
// retrieves the plugin descriptor // retrieves the plugin descriptor
LOG.debug("Find plugin descriptor '" + pluginPath + "'"); log.debug("Find plugin descriptor '" + pluginPath + "'");
PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory); PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory);
LOG.debug("Descriptor " + pluginDescriptor); log.debug("Descriptor " + pluginDescriptor);
String pluginClassName = pluginDescriptor.getPluginClass(); 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 // load plugin
LOG.debug("Loading plugin '" + pluginPath + "'"); log.debug("Loading plugin '" + pluginPath + "'");
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory); PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory);
pluginLoader.load(); pluginLoader.load();
LOG.debug("Loaded plugin '" + pluginPath + "'"); log.debug("Loaded plugin '" + pluginPath + "'");
// create the plugin wrapper // 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()); 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(); String pluginId = pluginDescriptor.getPluginId();
@ -309,6 +318,28 @@ public class DefaultPluginManager implements PluginManager {
pluginClassLoaders.put(pluginId, pluginClassLoader); 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 { private void expandPluginArchive(String fileName) throws IOException {
File pluginArchiveFile = new File(pluginsDirectory, fileName); File pluginArchiveFile = new File(pluginsDirectory, fileName);
long pluginArchiveDate = pluginArchiveFile.lastModified(); long pluginArchiveDate = pluginArchiveFile.lastModified();
@ -316,7 +347,7 @@ public class DefaultPluginManager implements PluginManager {
File pluginDirectory = new File(pluginsDirectory, pluginName); File pluginDirectory = new File(pluginsDirectory, pluginName);
// check if exists directory or the '.zip' file is "newer" than directory // check if exists directory or the '.zip' file is "newer" than directory
if (!pluginDirectory.exists() || (pluginArchiveDate > pluginDirectory.lastModified())) { 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 // create directorie for plugin
pluginDirectory.mkdirs(); pluginDirectory.mkdirs();
@ -338,8 +369,8 @@ public class DefaultPluginManager implements PluginManager {
for (PluginWrapper pluginWrapper : resolvedPlugins) { for (PluginWrapper pluginWrapper : resolvedPlugins) {
unresolvedPlugins.remove(pluginWrapper); unresolvedPlugins.remove(pluginWrapper);
compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader()); compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader());
LOG.info("Plugin '" + pluginWrapper.getDescriptor().getPluginId() + "' resolved"); log.info("Plugin '" + pluginWrapper.getDescriptor().getPluginId() + "' resolved");
} }
} }
} }

6
pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java

@ -25,7 +25,7 @@ import ro.fortsoft.pf4j.util.DirectedGraph;
*/ */
class DependencyResolver { class DependencyResolver {
private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class); private static final Logger log = LoggerFactory.getLogger(DependencyResolver.class);
private List<PluginWrapper> plugins; private List<PluginWrapper> plugins;
@ -51,14 +51,14 @@ class DependencyResolver {
} }
} }
LOG.debug("Graph: " + graph); log.debug("Graph: " + graph);
List<String> pluginsId = graph.reverseTopologicalSort(); List<String> pluginsId = graph.reverseTopologicalSort();
if (pluginsId == null) { if (pluginsId == null) {
throw new CyclicDependencyException("Cyclic dependences !!!" + graph.toString()); throw new CyclicDependencyException("Cyclic dependences !!!" + graph.toString());
} }
LOG.debug("Plugins order: " + pluginsId); log.debug("Plugins order: " + pluginsId);
List<PluginWrapper> sortedPlugins = new ArrayList<PluginWrapper>(); List<PluginWrapper> sortedPlugins = new ArrayList<PluginWrapper>();
for (String pluginId : pluginsId) { for (String pluginId : pluginsId) {
sortedPlugins.add(getPlugin(pluginId)); sortedPlugins.add(getPlugin(pluginId));

7
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java

@ -23,8 +23,8 @@ import java.util.List;
*/ */
public class PluginClassLoader extends URLClassLoader { public class PluginClassLoader extends URLClassLoader {
private static final String JAVA_PACKAGE_PREFIX = "java."; // private static final String JAVA_PACKAGE_PREFIX = "java.";
private static final String JAVAX_PACKAGE_PREFIX = "javax."; // private static final String JAVAX_PACKAGE_PREFIX = "javax.";
private static final String PLUGIN_PACKAGE_PREFIX = "ro.fortsoft.pf4j."; private static final String PLUGIN_PACKAGE_PREFIX = "ro.fortsoft.pf4j.";
private PluginManager pluginManager; private PluginManager pluginManager;
@ -46,10 +46,13 @@ public class PluginClassLoader extends URLClassLoader {
public Class<?> loadClass(String className) throws ClassNotFoundException { public Class<?> loadClass(String className) throws ClassNotFoundException {
// System.out.println(">>>" + className); // System.out.println(">>>" + className);
/*
// javax.mail is not in JDK ?!
// first check whether it's a system class, delegate to the system loader // first check whether it's a system class, delegate to the system loader
if (className.startsWith(JAVA_PACKAGE_PREFIX) || className.startsWith(JAVAX_PACKAGE_PREFIX)) { if (className.startsWith(JAVA_PACKAGE_PREFIX) || className.startsWith(JAVAX_PACKAGE_PREFIX)) {
return findSystemClass(className); return findSystemClass(className);
} }
*/
// second check whether it's already been loaded // second check whether it's already been loaded
Class<?> loadedClass = findLoadedClass(className); Class<?> loadedClass = findLoadedClass(className);

14
pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java

@ -33,7 +33,7 @@ import ro.fortsoft.pf4j.util.JarFilter;
*/ */
class PluginLoader { class PluginLoader {
private static final Logger LOG = LoggerFactory.getLogger(PluginLoader.class); private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
/* /*
* The plugin repository. * The plugin repository.
@ -58,7 +58,7 @@ class PluginLoader {
libDirectory = new File(pluginRepository, "lib"); libDirectory = new File(pluginRepository, "lib");
ClassLoader parent = getClass().getClassLoader(); ClassLoader parent = getClass().getClassLoader();
pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent); pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent);
LOG.debug("Created class loader " + pluginClassLoader); log.debug("Created class loader " + pluginClassLoader);
} }
public File getPluginRepository() { public File getPluginRepository() {
@ -100,14 +100,14 @@ class PluginLoader {
classesDirectory = classesDirectory.getAbsoluteFile(); classesDirectory = classesDirectory.getAbsoluteFile();
if (classesDirectory.exists() && classesDirectory.isDirectory()) { if (classesDirectory.exists() && classesDirectory.isDirectory()) {
LOG.debug("Found '" + classesDirectory.getPath() + "' directory"); log.debug("Found '" + classesDirectory.getPath() + "' directory");
try { try {
pluginClassLoader.addURL(classesDirectory.toURI().toURL()); 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) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
LOG.error(e.getMessage(), e); log.error(e.getMessage(), e);
return false; return false;
} }
} }
@ -128,10 +128,10 @@ class PluginLoader {
File jarFile = new File(libDirectory, jar); File jarFile = new File(libDirectory, jar);
try { try {
pluginClassLoader.addURL(jarFile.toURI().toURL()); 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) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
LOG.error(e.getMessage(), e); log.error(e.getMessage(), e);
return false; return false;
} }
} }

97
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;
}
}

53
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<String> readLines(File file, boolean ignoreComments) throws IOException {
if (!file.exists() || !file.isFile()) {
return Collections.emptyList();
}
List<String> lines = new ArrayList<String>();
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;
}
}

24
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();
}
}

6
pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java

@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
*/ */
public class Unzip { 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. * Holds the destination directory.
@ -61,7 +61,7 @@ public class Unzip {
} }
public void extract() throws IOException { 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 // delete destination file if exists
removeDirectory(destination); removeDirectory(destination);
@ -91,7 +91,7 @@ public class Unzip {
fos.close(); fos.close();
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
LOG.error("File '" + zipEntry.getName() + "' not found"); log.error("File '" + zipEntry.getName() + "' not found");
} }
} }

6
pom.xml

@ -9,10 +9,10 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>ro.fortsoft.pf4j</groupId> <groupId>ro.fortsoft.pf4j</groupId>
<artifactId>pom</artifactId> <artifactId>pf4j-parent</artifactId>
<version>0.4-SNAPSHOT</version> <version>0.5-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>PF4J</name> <name>PF4J Parent</name>
<description>Plugin Framework for Java</description> <description>Plugin Framework for Java</description>
<licenses> <licenses>

Loading…
Cancel
Save