Browse Source

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

pull/3/head
Decebal Suiu 11 years ago
parent
commit
c6a1bbda46
  1. 36
      README.md
  2. 212
      demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java
  3. 254
      demo/plugins/plugin1/pom.xml
  4. 10
      demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java
  5. 246
      demo/plugins/plugin2/pom.xml
  6. 3
      demo/plugins/plugin2/src/main/java/ro/fortsoft/pf4j/demo/hello/HelloPlugin.java
  7. 143
      demo/plugins/pom.xml
  8. 852
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
  9. 36
      pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java
  10. 13
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java
  11. 5
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java
  12. 9
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java
  13. 55
      pf4j/src/main/java/ro/fortsoft/pf4j/RuntimeMode.java

36
README.md

@ -61,8 +61,8 @@ 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.
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.
Each available plugin is loaded using a different java class loader, **PluginClassLoader**.
The **PluginClassLoader** contains only classes found in **PluginClasspath** (default _classes_ and _lib_ folders) of plugin and runtime classes and libraries of the required/dependent 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")`.
@ -71,12 +71,12 @@ The structure of plugins folder is:
* plugin2.zip (or plugin2 folder)
In plugins folder you can put a plugin as folder or archive file (zip).
A plugin folder has this structure:
A plugin folder has this structure by default:
* `classes` folder
* `lib` folder (optional - if the plugin used third party libraries)
The plugin manager searches plugins metadata using a **PluginDescriptorFinder**.
**DefaultPluginDescriptorFinder** lookups plugins descriptors in MANIFEST.MF file.
**DefaultPluginDescriptorFinder** is a "link" to **ManifestPluginDescriptorFinder** that lookups plugins descriptors in MANIFEST.MF file.
In this case the `classes/META-INF/MANIFEST.MF` file looks like:
Manifest-Version: 1.0
@ -102,7 +102,7 @@ You can define an extension point in your application using **ExtensionPoint** i
}
Another important internal component is **ExtensionFinder** that describes how plugin manager discovers extensions for extensions points.
**DefaultExtensionFinder** looks up extensions using **Extension** annotation.
**DefaultExtensionFinder** is a "link" to **SezpozExtensionFinder** that looks up extensions using **Extension** annotation.
public class WelcomePlugin extends Plugin {
@ -135,7 +135,7 @@ The output is:
>>> Welcome
>>> Hello
You can inject your custom component (for example PluginDescriptorFinder, ExtensionFinder) in DefaultPluginManager just override createXXX methods (factory method pattern).
You can inject your custom component (for example PluginDescriptorFinder, ExtensionFinder, PluginClasspath, ...) in DefaultPluginManager just override createXXX methods (factory method pattern).
Example:
@ -154,6 +154,26 @@ and in plugin respository you must have a plugin.properties file with the below
For more information please see the demo sources.
Development runtime mode
--------------------------
PF4J can run in two modes: **DEVELOPMENT** and **DEPLOYMENT**.
The DEPLOYMENT(default) mode is the standard workflow for plugins creation: create a new maven module for each plugin, codding the plugin (declares new extension points and/or
add new extensions), pack the plugin in a zip file, deploy the zip file to plugins folder. These operations are time consuming and from this reason I introduced the DEVELOPMENT runtime mode.
The main advantage of DEVELOPMENT runtime mode for a plugin developer is that he/she is not enforced to pack and deploy the plugins. In DEVELOPMENT mode you can developing plugins in a simple and fast mode.
Lets describe how DEVELOPMENT runtime mode works.
First, you can change the runtime mode using the "pf4j.mode" system property or overriding `DefaultPluginManager.getRuntimeMode()`.
For example I run the pf4j demo in eclipse in DEVELOPMENT mode adding only `"-Dpf4j.mode=development"` to the pf4j demo launcher.
You can retrieve the current runtime mode using `PluginManager.getRuntimeMode()` or in your Plugin implementation with `getWrapper().getRuntimeMode()`(see [WelcomePlugin](https://github.com/decebals/pf4j/blob/master/demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java)).
The DefaultPluginManager determines automatically the correct runtime mode and for DEVELOPMENT mode overrides some components(pluginsDirectory is __"../plugins"__, __PropertiesPluginDescriptorFinder__ as PluginDescriptorFinder, __DevelopmentPluginClasspath__ as PluginClassPath).
Another advantage of DEVELOPMENT runtime mode is that you can execute some code lines only in this mode (for example more debug messages).
If you use maven as build manger, after each dependency modification in you plugin (maven module) you must run Maven>Update Project...
For more details see the demo application.
Enable/Disable plugins
-------------------
In theory, it's a relation **1:N** between an extension point and the extensions for this extension point.
@ -191,8 +211,8 @@ If a file with enabled.txt exists than disabled.txt is ignored. See enabled.txt
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_).
In demo/api folder I declared an extension point ( _Greeting_).
In demo/plugins I implemented two plugins: plugin1, plugin2 (each plugin adds an extension for _Greeting_).
To run the demo application use:

212
demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java

@ -1,145 +1,67 @@
/*
* 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.demo;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.DefaultPluginManager;
import ro.fortsoft.pf4j.PluginClasspath;
import ro.fortsoft.pf4j.PluginDescriptorFinder;
import ro.fortsoft.pf4j.PluginManager;
import ro.fortsoft.pf4j.PropertiesPluginDescriptorFinder;
import ro.fortsoft.pf4j.demo.api.Greeting;
/**
* A boot class that start the demo.
*
* @author Decebal Suiu
*/
public class Boot {
public static void main(String[] args) {
// print logo
printLogo();
// create the plugin manager
final PluginManager pluginManager = createPluginManager();
// load and start (active/resolved) the plugins
pluginManager.loadPlugins();
pluginManager.startPlugins();
// retrieves the extensions for Greeting extension point
List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.getGreeting());
}
// stop the plugins
pluginManager.stopPlugins();
/*
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
pluginManager.stopPlugins();
}
});
*/
}
private static PluginManager createPluginManager() {
// retrieves the pf4j runtime mode
String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.PROD.toString());
RuntimeMode mode = RuntimeMode.byName(modeAsString);
System.out.println("PF4J runtime mode: '" + mode + "'");
// create the plugin manager depending on runtime mode
PluginManager pluginManager = null;
if (mode == RuntimeMode.PROD) {
pluginManager = new DefaultPluginManager();
} else if (mode == RuntimeMode.DEV) {
// run from eclipse IDE (for example)
pluginManager = new DefaultPluginManager(new File("../plugins")) {
@Override
protected PluginClasspath createPluginClasspath() {
PluginClasspath pluginClasspath = super.createPluginClasspath();
// modify plugin classes
List<String> pluginClasses = pluginClasspath.getClassesDirectories();
pluginClasses.clear();
pluginClasses.add("target/classes");
return pluginClasspath;
}
@Override
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new PropertiesPluginDescriptorFinder();
}
};
}
return pluginManager;
}
private static void printLogo() {
System.out.println(StringUtils.repeat("#", 40));
System.out.println(StringUtils.center("PF4J-DEMO", 40));
System.out.println(StringUtils.repeat("#", 40));
}
public enum RuntimeMode {
DEV("dev"), // development
PROD("prod"); // production
private final String name;
private static final Map<String, RuntimeMode> map = new HashMap<String, RuntimeMode>();
static {
for (RuntimeMode mode : RuntimeMode.values()) {
map.put(mode.name, mode);
}
}
private RuntimeMode(final String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
public static RuntimeMode byName(String name) {
if (map.containsKey(name)) {
return map.get(name);
}
throw new NoSuchElementException("Cannot found PF4J runtime mode with name '" + name + "'");
}
}
}
/*
* 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.demo;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.DefaultPluginManager;
import ro.fortsoft.pf4j.PluginManager;
import ro.fortsoft.pf4j.demo.api.Greeting;
/**
* A boot class that start the demo.
*
* @author Decebal Suiu
*/
public class Boot {
public static void main(String[] args) {
// print logo
printLogo();
// create the plugin manager
final PluginManager pluginManager = new DefaultPluginManager();
// load and start (active/resolved) the plugins
pluginManager.loadPlugins();
pluginManager.startPlugins();
// retrieves the extensions for Greeting extension point
List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.getGreeting());
}
// stop the plugins
pluginManager.stopPlugins();
/*
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
pluginManager.stopPlugins();
}
});
*/
}
private static void printLogo() {
System.out.println(StringUtils.repeat("#", 40));
System.out.println(StringUtils.center("PF4J-DEMO", 40));
System.out.println(StringUtils.repeat("#", 40));
}
}

254
demo/plugins/plugin1/pom.xml

@ -1,131 +1,123 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>0.6-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin1</artifactId>
<version>0.6-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo Plugin #1</name>
<properties>
<plugin.id>welcome-plugin</plugin.id>
<plugin.class>ro.fortsoft.pf4j.demo.welcome.WelcomePlugin</plugin.class>
<plugin.version>0.0.1</plugin.version>
<plugin.provider>Decebal Suiu</plugin.provider>
<plugin.dependencies />
</properties>
<build>
<plugins>
<!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>plugin.properties</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>unzip jar file</id>
<phase>package</phase>
<configuration>
<target>
<unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
<configuration>
<descriptors>
<descriptor>
src/main/assembly/assembly.xml
</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>ro.fortsoft.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>0.6-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin1</artifactId>
<version>0.6-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo Plugin #1</name>
<properties>
<plugin.id>welcome-plugin</plugin.id>
<plugin.class>ro.fortsoft.pf4j.demo.welcome.WelcomePlugin</plugin.class>
<plugin.version>0.0.1</plugin.version>
<plugin.provider>Decebal Suiu</plugin.provider>
<plugin.dependencies />
</properties>
<build>
<plugins>
<!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>plugin.properties</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>unzip jar file</id>
<phase>package</phase>
<configuration>
<target>
<unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
<configuration>
<descriptors>
<descriptor>
src/main/assembly/assembly.xml
</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>

10
demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java

@ -12,9 +12,12 @@
*/
package ro.fortsoft.pf4j.demo.welcome;
import org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.Extension;
import ro.fortsoft.pf4j.Plugin;
import ro.fortsoft.pf4j.PluginWrapper;
import ro.fortsoft.pf4j.RuntimeMode;
import ro.fortsoft.pf4j.demo.api.Greeting;
/**
@ -26,10 +29,16 @@ public class WelcomePlugin extends Plugin {
super(wrapper);
}
@Override
public void start() {
System.out.println("WelcomePlugin.start()");
// for testing the development mode
if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
System.out.println(StringUtils.upperCase("WelcomePlugin"));
}
}
@Override
public void stop() {
System.out.println("WelcomePlugin.stop()");
}
@ -37,6 +46,7 @@ public class WelcomePlugin extends Plugin {
@Extension
public static class WelcomeGreeting implements Greeting {
@Override
public String getGreeting() {
return "Welcome";
}

246
demo/plugins/plugin2/pom.xml

@ -1,131 +1,115 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>0.6-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin2</artifactId>
<version>0.6-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo Plugin #2</name>
<properties>
<plugin.id>hello-plugin</plugin.id>
<plugin.class>ro.fortsoft.pf4j.demo.hello.HelloPlugin</plugin.class>
<plugin.version>0.0.1</plugin.version>
<plugin.provider>Decebal Suiu</plugin.provider>
<plugin.dependencies />
</properties>
<build>
<plugins>
<!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>plugin.properties</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>unzip jar file</id>
<phase>package</phase>
<configuration>
<target>
<unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
<configuration>
<descriptors>
<descriptor>
src/main/assembly/assembly.xml
</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>ro.fortsoft.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>0.6-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin2</artifactId>
<version>0.6-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo Plugin #2</name>
<properties>
<plugin.id>hello-plugin</plugin.id>
<plugin.class>ro.fortsoft.pf4j.demo.hello.HelloPlugin</plugin.class>
<plugin.version>0.0.1</plugin.version>
<plugin.provider>Decebal Suiu</plugin.provider>
<plugin.dependencies />
</properties>
<build>
<plugins>
<!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>plugin.properties</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>unzip jar file</id>
<phase>package</phase>
<configuration>
<target>
<unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
<configuration>
<descriptors>
<descriptor>
src/main/assembly/assembly.xml
</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

3
demo/plugins/plugin2/src/main/java/ro/fortsoft/pf4j/demo/hello/HelloPlugin.java

@ -28,10 +28,12 @@ public class HelloPlugin extends Plugin {
super(wrapper);
}
@Override
public void start() {
System.out.println("HelloPlugin.start()");
}
@Override
public void stop() {
System.out.println("HelloPlugin.stop()");
}
@ -39,6 +41,7 @@ public class HelloPlugin extends Plugin {
@Extension
public static class HelloGreeting implements Greeting {
@Override
public String getGreeting() {
return "Hello";
}

143
demo/plugins/pom.xml

@ -1,37 +1,106 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId>
<version>0.6-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>0.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Demo Plugins Parent</name>
<build>
<resources>
<resource>
<filtering>false</filtering>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
<modules>
<module>plugin1</module>
<module>plugin2</module>
</modules>
</project>
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId>
<version>0.6-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>0.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Demo Plugins Parent</name>
<build>
<resources>
<resource>
<filtering>false</filtering>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeScope>provided</excludeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<versionRange>[2.0,)</versionRange>
<goals>
<goal>copy-dependencies</goal>
</goals>
</pluginExecutionFilter>
<action>
<!--
<execute/>
-->
<execute>
<runOnIncremental>true</runOnIncremental>
<runOnConfiguration>true</runOnConfiguration>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<modules>
<module>plugin1</module>
<module>plugin2</module>
</modules>
<dependencies>
<dependency>
<groupId>ro.fortsoft.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ro.fortsoft.pf4j.demo</groupId>
<artifactId>pf4j-demo-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

852
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java

@ -1,392 +1,460 @@
/*
* 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.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.AndFileFilter;
import ro.fortsoft.pf4j.util.CompoundClassLoader;
import ro.fortsoft.pf4j.util.DirectoryFileFilter;
import ro.fortsoft.pf4j.util.FileUtils;
import ro.fortsoft.pf4j.util.HiddenFilter;
import ro.fortsoft.pf4j.util.NotFileFilter;
import ro.fortsoft.pf4j.util.Unzip;
import ro.fortsoft.pf4j.util.ZipFileFilter;
/**
* Default implementation of the PluginManager interface.
*
* @author Decebal Suiu
*/
public class DefaultPluginManager implements PluginManager {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
/**
* The plugins repository.
*/
private File pluginsDirectory;
private final ExtensionFinder extensionFinder;
private final PluginDescriptorFinder pluginDescriptorFinder;
private final PluginClasspath pluginClasspath;
/**
* A map of plugins this manager is responsible for (the key is the 'pluginId').
*/
private Map<String, PluginWrapper> plugins;
/**
* A map of plugin class loaders (he key is the 'pluginId').
*/
private Map<String, PluginClassLoader> pluginClassLoaders;
/**
* A relation between 'pluginPath' and 'pluginId'
*/
private Map<String, String> pathToIdMap;
/**
* 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;
private List<String> enabledPlugins;
private List<String> disabledPlugins;
/**
* A compound class loader of resolved plugins.
*/
protected CompoundClassLoader compoundClassLoader;
/**
* Th plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
*/
public DefaultPluginManager() {
this(new File(System.getProperty("pf4j.pluginsDir", "plugins")));
}
/**
* Constructs DefaultPluginManager which the given plugins directory.
*
* @param pluginsDirectory
* the directory to search for plugins
*/
public DefaultPluginManager(File pluginsDirectory) {
this.pluginsDirectory = pluginsDirectory;
plugins = new HashMap<String, PluginWrapper>();
pluginClassLoaders = new HashMap<String, PluginClassLoader>();
pathToIdMap = new HashMap<String, String>();
unresolvedPlugins = new ArrayList<PluginWrapper>();
resolvedPlugins = new ArrayList<PluginWrapper>();
startedPlugins = new ArrayList<PluginWrapper>();
disabledPlugins = new ArrayList<String>();
compoundClassLoader = new CompoundClassLoader();
pluginClasspath = createPluginClasspath();
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
public List<PluginWrapper> getPlugins() {
return new ArrayList<PluginWrapper>(plugins.values());
}
@Override
public List<PluginWrapper> getResolvedPlugins() {
return resolvedPlugins;
}
public PluginWrapper getPlugin(String pluginId) {
return plugins.get(pluginId);
}
@Override
public List<PluginWrapper> getUnresolvedPlugins() {
return unresolvedPlugins;
}
@Override
public List<PluginWrapper> getStartedPlugins() {
return startedPlugins;
}
/**
* Start all active plugins.
*/
@Override
public void startPlugins() {
for (PluginWrapper pluginWrapper : resolvedPlugins) {
try {
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);
}
}
}
/**
* Stop all active plugins.
*/
@Override
public void stopPlugins() {
// stop started plugins in reverse order
Collections.reverse(startedPlugins);
for (PluginWrapper pluginWrapper : startedPlugins) {
try {
log.info("Stop plugin '{}'", pluginWrapper.getDescriptor().getPluginId());
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
}
/**
* 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
FileFilter zipFilter = new ZipFileFilter();
File[] zipFiles = pluginsDirectory.listFiles(zipFilter);
for (File zipFile : zipFiles) {
try {
expandPluginArchive(zipFile);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
// check for no plugins
List<FileFilter> filterList = new ArrayList<FileFilter>();
filterList.add(new DirectoryFileFilter());
filterList.add(new NotFileFilter(createHiddenPluginFilter()));
FileFilter pluginsFilter = new AndFileFilter(filterList);
File[] directories = pluginsDirectory.listFiles(pluginsFilter);
if (directories.length == 0) {
log.info("No plugins");
return;
}
// load any plugin from plugins directory
for (File directory : directories) {
try {
loadPlugin(directory);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
// resolve 'unresolvedPlugins'
try {
resolvePlugins();
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
/**
* 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<T>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getInstance());
}
return extensions;
}
/**
* Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'.
*/
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
for (PluginWrapper plugin : resolvedPlugins) {
if (plugin.getPluginClassLoader() == classLoader) {
return plugin;
}
}
return null;
}
/**
* Add the possibility to override the PluginDescriptorFinder.
*/
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new DefaultPluginDescriptorFinder(pluginClasspath);
}
/**
* Add the possibility to override the ExtensionFinder.
*/
protected ExtensionFinder createExtensionFinder() {
return new DefaultExtensionFinder(compoundClassLoader);
}
/**
* Add the possibility to override the PluginClassPath.
*/
protected PluginClasspath createPluginClasspath() {
return new PluginClasspath();
}
protected boolean isPluginDisabled(String pluginId) {
if (enabledPlugins.isEmpty()) {
return disabledPlugins.contains(pluginId);
}
return !enabledPlugins.contains(pluginId);
}
protected FileFilter createHiddenPluginFilter() {
return new HiddenFilter();
}
private void loadPlugin(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;
}
// 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);
// test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) {
log.info("Plugin '{}' is disabled", pluginPath);
return;
}
// load plugin
log.debug("Loading plugin '{}'", pluginPath);
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath);
pluginLoader.load();
log.debug("Loaded plugin '{}'", pluginPath);
// create the plugin wrapper
log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader());
log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
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
PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
pluginClassLoaders.put(pluginId, pluginClassLoader);
}
private void 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);
// create directory for plugin
pluginDirectory.mkdirs();
// expand '.zip' file
Unzip unzip = new Unzip();
unzip.setSource(pluginArchiveFile);
unzip.setDestination(pluginDirectory);
unzip.extract();
}
}
private void resolvePlugins() throws PluginException {
resolveDependencies();
}
private void resolveDependencies() throws PluginException {
DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins);
resolvedPlugins = dependencyResolver.getSortedPlugins();
for (PluginWrapper pluginWrapper : resolvedPlugins) {
unresolvedPlugins.remove(pluginWrapper);
compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader());
log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
}
}
}
/*
* 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.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.AndFileFilter;
import ro.fortsoft.pf4j.util.CompoundClassLoader;
import ro.fortsoft.pf4j.util.DirectoryFileFilter;
import ro.fortsoft.pf4j.util.FileUtils;
import ro.fortsoft.pf4j.util.HiddenFilter;
import ro.fortsoft.pf4j.util.NotFileFilter;
import ro.fortsoft.pf4j.util.Unzip;
import ro.fortsoft.pf4j.util.ZipFileFilter;
/**
* Default implementation of the PluginManager interface.
*
* @author Decebal Suiu
*/
public class DefaultPluginManager implements PluginManager {
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";
/**
* The plugins repository.
*/
private File pluginsDirectory;
private ExtensionFinder extensionFinder;
private PluginDescriptorFinder pluginDescriptorFinder;
private PluginClasspath pluginClasspath;
/**
* A map of plugins this manager is responsible for (the key is the 'pluginId').
*/
private Map<String, PluginWrapper> plugins;
/**
* A map of plugin class loaders (he key is the 'pluginId').
*/
private Map<String, PluginClassLoader> pluginClassLoaders;
/**
* A relation between 'pluginPath' and 'pluginId'
*/
private Map<String, String> pathToIdMap;
/**
* 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;
private List<String> enabledPlugins;
private List<String> disabledPlugins;
/**
* A compound class loader of resolved plugins.
*/
protected CompoundClassLoader compoundClassLoader;
/**
* Cache value for the runtime mode. No need to re-read it because it wont change at
* runtime.
*/
private RuntimeMode runtimeMode;
/**
* 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 List<PluginWrapper> getPlugins() {
return new ArrayList<PluginWrapper>(plugins.values());
}
@Override
public List<PluginWrapper> getResolvedPlugins() {
return resolvedPlugins;
}
public PluginWrapper getPlugin(String pluginId) {
return plugins.get(pluginId);
}
@Override
public List<PluginWrapper> getUnresolvedPlugins() {
return unresolvedPlugins;
}
@Override
public List<PluginWrapper> getStartedPlugins() {
return startedPlugins;
}
/**
* Start all active plugins.
*/
@Override
public void startPlugins() {
for (PluginWrapper pluginWrapper : resolvedPlugins) {
try {
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);
}
}
}
/**
* Stop all active plugins.
*/
@Override
public void stopPlugins() {
// stop started plugins in reverse order
Collections.reverse(startedPlugins);
for (PluginWrapper pluginWrapper : startedPlugins) {
try {
log.info("Stop plugin '{}'", pluginWrapper.getDescriptor().getPluginId());
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
}
/**
* 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
FileFilter zipFilter = new ZipFileFilter();
File[] zipFiles = pluginsDirectory.listFiles(zipFilter);
for (File zipFile : zipFiles) {
try {
expandPluginArchive(zipFile);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
// check for no plugins
List<FileFilter> filterList = new ArrayList<FileFilter>();
filterList.add(new DirectoryFileFilter());
filterList.add(new NotFileFilter(createHiddenPluginFilter()));
FileFilter pluginsFilter = new AndFileFilter(filterList);
File[] directories = pluginsDirectory.listFiles(pluginsFilter);
log.debug("Possible plugins: {}", Arrays.asList(directories));
if (directories.length == 0) {
log.info("No plugins");
return;
}
// load any plugin from plugins directory
for (File directory : directories) {
try {
loadPlugin(directory);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
// resolve 'unresolvedPlugins'
try {
resolvePlugins();
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
/**
* 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<T>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getInstance());
}
return extensions;
}
@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);
log.info("PF4J runtime mode: '" + runtimeMode + "'");
}
return runtimeMode;
}
/**
* Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'.
*/
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
for (PluginWrapper plugin : resolvedPlugins) {
if (plugin.getPluginClassLoader() == classLoader) {
return plugin;
}
}
return null;
}
/**
* 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() {
return new DefaultExtensionFinder(compoundClassLoader);
}
/**
* Add the possibility to override the PluginClassPath.
* By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
* DevelopmentPluginClasspath is returned else this method returns
* PluginClasspath.
*/
protected PluginClasspath createPluginClasspath() {
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
return new DevelopmentPluginClasspath();
}
return new PluginClasspath();
}
protected boolean isPluginDisabled(String pluginId) {
if (enabledPlugins.isEmpty()) {
return disabledPlugins.contains(pluginId);
}
return !enabledPlugins.contains(pluginId);
}
protected FileFilter createHiddenPluginFilter() {
return new HiddenFilter();
}
/**
* 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);
}
private void initialize() {
plugins = new HashMap<String, PluginWrapper>();
pluginClassLoaders = new HashMap<String, PluginClassLoader>();
pathToIdMap = new HashMap<String, String>();
unresolvedPlugins = new ArrayList<PluginWrapper>();
resolvedPlugins = new ArrayList<PluginWrapper>();
startedPlugins = new ArrayList<PluginWrapper>();
disabledPlugins = new ArrayList<String>();
compoundClassLoader = new CompoundClassLoader();
pluginClasspath = createPluginClasspath();
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());
}
private void loadPlugin(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;
}
// 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);
// test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) {
log.info("Plugin '{}' is disabled", pluginPath);
return;
}
// load plugin
log.debug("Loading plugin '{}'", pluginPath);
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath);
pluginLoader.load();
log.debug("Loaded plugin '{}'", pluginPath);
// create the plugin wrapper
log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader());
pluginWrapper.setRuntimeMode(getRuntimeMode());
log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
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
PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
pluginClassLoaders.put(pluginId, pluginClassLoader);
}
private void 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);
// create directory for plugin
pluginDirectory.mkdirs();
// expand '.zip' file
Unzip unzip = new Unzip();
unzip.setSource(pluginArchiveFile);
unzip.setDestination(pluginDirectory);
unzip.extract();
}
}
private void resolvePlugins() throws PluginException {
resolveDependencies();
}
private void resolveDependencies() throws PluginException {
DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins);
resolvedPlugins = dependencyResolver.getSortedPlugins();
for (PluginWrapper pluginWrapper : resolvedPlugins) {
unresolvedPlugins.remove(pluginWrapper);
compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader());
log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
}
}
}

36
pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java

@ -0,0 +1,36 @@
/*
* Copyright 2013 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;
/**
* Overwrite classes directories to "target/classes" and lib directories to "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);
}
}

13
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java

@ -28,16 +28,14 @@ public class PluginClasspath {
private static final String DEFAULT_CLASSES_DIRECTORY = "classes";
private static final String DEFAULT_LIB_DIRECTORY = "lib";
private List<String> classesDirectories;
private List<String> libDirectories;
protected List<String> classesDirectories;
protected List<String> libDirectories;
public PluginClasspath() {
classesDirectories = new ArrayList<String>();
libDirectories = new ArrayList<String>();
// add defaults
classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
libDirectories.add(DEFAULT_LIB_DIRECTORY);
addResources();
}
public List<String> getClassesDirectories() {
@ -56,4 +54,9 @@ public class PluginClasspath {
this.libDirectories = libDirectories;
}
protected void addResources() {
classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
libDirectories.add(DEFAULT_LIB_DIRECTORY);
}
}

5
pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java

@ -61,4 +61,9 @@ public interface PluginManager {
public <T> List<T> getExtensions(Class<T> type);
/**
* The runtime mode. Must currently be either DEVELOPMENT or DEPLOYMENT.
*/
public RuntimeMode getRuntimeMode();
}

9
pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java

@ -27,6 +27,7 @@ public class PluginWrapper {
PluginClassLoader pluginClassLoader;
Plugin plugin;
PluginState pluginState;
RuntimeMode runtimeMode;
public PluginWrapper(PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) {
this.descriptor = descriptor;
@ -74,6 +75,10 @@ public class PluginWrapper {
return pluginState;
}
public RuntimeMode getRuntimeMode() {
return runtimeMode;
}
@Override
public int hashCode() {
final int prime = 31;
@ -114,6 +119,10 @@ public class PluginWrapper {
void setPluginState(PluginState pluginState) {
this.pluginState = pluginState;
}
void setRuntimeMode(RuntimeMode runtimeMode) {
this.runtimeMode = runtimeMode;
}
private Plugin createPluginInstance() throws Exception {
String pluginClassName = descriptor.getPluginClass();

55
pf4j/src/main/java/ro/fortsoft/pf4j/RuntimeMode.java

@ -0,0 +1,55 @@
/*
* Copyright 2013 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.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* @author Decebal Suiu
*/
public enum RuntimeMode {
DEVELOPMENT("development"), // development
DEPLOYMENT("deployment"); // deployment
private final String name;
private static final Map<String, RuntimeMode> map = new HashMap<String, RuntimeMode>();
static {
for (RuntimeMode mode : RuntimeMode.values()) {
map.put(mode.name, mode);
}
}
private RuntimeMode(final String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
public static RuntimeMode byName(String name) {
if (map.containsKey(name)) {
return map.get(name);
}
throw new NoSuchElementException("Cannot found PF4J runtime mode with name '" + name +
"'. Must be 'development' or 'deployment'.");
}
}
Loading…
Cancel
Save