Browse Source

first commit

pull/3/head
Decebal Suiu 12 years ago
commit
2aeb77b8c8
  1. 151
      README.md
  2. 44
      demo/api/pom.xml
  3. 24
      demo/api/src/main/java/org/pf4j/demo/api/Greeting.java
  4. 89
      demo/app/pom.xml
  5. 32
      demo/app/src/main/assembly/assembly.xml
  6. 63
      demo/app/src/main/java/org/pf4j/demo/Boot.java
  7. 10
      demo/app/src/main/resources/log4j.properties
  8. 8
      demo/app/src/main/resources/logging.properties
  9. 5
      demo/plugin1/plugin.properties
  10. 127
      demo/plugin1/pom.xml
  11. 37
      demo/plugin1/src/main/assembly/assembly.xml
  12. 46
      demo/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java
  13. 5
      demo/plugin2/plugin.properties
  14. 136
      demo/plugin2/pom.xml
  15. 37
      demo/plugin2/src/main/assembly/assembly.xml
  16. 48
      demo/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java
  17. 60
      demo/pom.xml
  18. 122
      pf4j/pom.xml
  19. 80
      pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java
  20. 90
      pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java
  21. 327
      pf4j/src/main/java/org/pf4j/DefaultPluginManager.java
  22. 81
      pf4j/src/main/java/org/pf4j/DependencyResolver.java
  23. 35
      pf4j/src/main/java/org/pf4j/Extension.java
  24. 24
      pf4j/src/main/java/org/pf4j/ExtensionFinder.java
  25. 20
      pf4j/src/main/java/org/pf4j/ExtensionPoint.java
  26. 41
      pf4j/src/main/java/org/pf4j/ExtensionWrapper.java
  27. 68
      pf4j/src/main/java/org/pf4j/Plugin.java
  28. 91
      pf4j/src/main/java/org/pf4j/PluginClassLoader.java
  29. 140
      pf4j/src/main/java/org/pf4j/PluginDescriptor.java
  30. 28
      pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java
  31. 40
      pf4j/src/main/java/org/pf4j/PluginException.java
  32. 142
      pf4j/src/main/java/org/pf4j/PluginLoader.java
  33. 49
      pf4j/src/main/java/org/pf4j/PluginManager.java
  34. 191
      pf4j/src/main/java/org/pf4j/PluginVersion.java
  35. 61
      pf4j/src/main/java/org/pf4j/PluginWrapper.java
  36. 171
      pf4j/src/main/java/org/pf4j/util/DirectedGraph.java
  37. 35
      pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java
  38. 41
      pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java
  39. 32
      pf4j/src/main/java/org/pf4j/util/JarFilter.java
  40. 72
      pf4j/src/main/java/org/pf4j/util/UberClassLoader.java
  41. 122
      pf4j/src/main/java/org/pf4j/util/Unzip.java
  42. 32
      pf4j/src/main/java/org/pf4j/util/ZipFilter.java
  43. 72
      pom.xml
  44. 24
      run-demo.sh

151
README.md

@ -0,0 +1,151 @@
Plugin Framework for Java (PF4J)
=====================
A plugin is a way for a third party to extend the functionality of an application. A plugin implements extensions points
declared by application or another plugins. Also a plugin can defines extension points.
Components
-------------------
- **Plugin** is the base class for all plugins types. Each plugin is loaded into a separate class loader to avoid conflicts.
- **PluginManager** is used for all aspects of plugins management (loading, starting, stopping).
- **ExtensionPoint** is a point in the application where custom code can be invoked.
- **Extension** is an implementation of extension point.
Artifacts
-------------------
- PF4J `pf4j` (jar)
- PF4J Demo `pf4j-demo` (executable jar)
Using Maven
-------------------
First you must install the pf4j artifacts in your local maven repository with:
mvn clean install
I will upload these artifacts in maven central repository as soon as possible.
In your pom.xml you must define the dependencies to PF4J artifacts with:
```xml
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${pf4j.version}</version>
</dependency>
```
where ${pf4j.version} is the last pf4j version.
How to use
-------------------
It's very simple to add pf4j in your application:
public static void main(String[] args) {
...
PluginManager pluginManager = new DefaultPluginManager();
pluginManager.loadPlugins();
pluginManager.startPlugins();
...
}
In above code, I created a DefaultPluginManager (it's the default implementation for
PluginManager interface) that load and start all active(resolved) plugins.
The plugins are stored in a folder. You can specify the plugins folder in constructor of DefaultPluginManager
or using the constructor without parameters (in this case plugins folder is returned by System.getProperty("pf4j.pluginsDir", "plugins")).
The structure of plugins folder is:
- plugin1.zip (or plugin1 folder)
- plugin2.zip (or plugin2 folder)
...
- pluginN.zip (or pluginN folder)
In plugins folder you can put a plugin as folder or archive file (zip).
A plugin folder has this structure:
- `classes` folder
- `lib` folder (optional - if the plugin used third party libraries)
The plugin manager discovers plugins metadata using a PluginDescriptorFinder. DefaultPluginDescriptorFinder lookup plugin descriptor in MANIFEST.MF file.
In this case the classes/META-INF/MANIFEST.MF looks like:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: decebal
Build-Jdk: 1.6.0_17
Plugin-Class: org.pf4j.demo.welcome.WelcomePlugin
Plugin-Dependencies: x, y z
Plugin-Id: welcome-plugin
Plugin-Provider: Decebal Suiu
Plugin-Version: 0.0.1
In above manifest I described a plugin with id `welcome-plugin`, with class `org.pf4j.demo.welcome.WelcomePlugin`, with version `0.0.1` and with dependencies
to plugins `x, y, z`.
You can define an extension point in your application using ExtensionPoint interface marker.
public interface Greeting extends ExtensionPoint {
public String getGreeting();
}
Another important internal component is ExtensionFinder that describes how plugin manager discovers extensions for extensions points. DefaultExtensionFinder look up extensions using Extension annotation.
public class WelcomePlugin extends Plugin {
public WelcomePlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Extension
public static class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}
}
In above code I supply an extension for the Greeting extension point.
You can retrieves all extensions for an extension point with:
List<ExtensionWrapper<Greeting>> greetings = pluginManager.getExtensions(Greeting.class);
for (ExtensionWrapper<Greeting> greeting : greetings) {
System.out.println(">>> " + greeting.getInstance().getGreeting());
}
For more information please see the demo sources.
Demo
-------------------
I have a tiny demo application. In this demo I have implemented two widgets types:
a chart widget (using open flash chart) and a text widget (display a Lorem Ipsum).
You can drag and drop widgets, perform some actions on each widget, add or remove new
widgets, change widget settings, collapse widgets.
The demo application is in demo folder.
To run the demo application use:
./run-demo.sh
License
--------------
Copyright 2012 Decebal Suiu
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
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.

44
demo/api/pom.xml

@ -0,0 +1,44 @@
<?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>org.pf4j.demo</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-api</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<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>
<plugins>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

24
demo/api/src/main/java/org/pf4j/demo/api/Greeting.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 org.pf4j.demo.api;
import org.pf4j.ExtensionPoint;
/**
* @author Decebal Suiu
*/
public interface Greeting extends ExtensionPoint {
public String getGreeting();
}

89
demo/app/pom.xml

@ -0,0 +1,89 @@
<?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>org.pf4j.demo</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-app</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<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>
<main.class>org.pf4j.demo.Boot</main.class>
</properties>
<build>
<plugins>
<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.3.1</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

32
demo/app/src/main/assembly/assembly.xml

@ -0,0 +1,32 @@
<!--
Describes the dist
@author Decebal Suiu
@version 1.0
-->
<assembly>
<id>plugin</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*:jar:*</include>
</includes>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

63
demo/app/src/main/java/org/pf4j/demo/Boot.java

@ -0,0 +1,63 @@
/*
* 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 org.pf4j.demo;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.pf4j.DefaultPluginManager;
import org.pf4j.ExtensionWrapper;
import org.pf4j.PluginManager;
import org.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();
// load and start (active/resolved) plugins
final PluginManager pluginManager = new DefaultPluginManager();
pluginManager.loadPlugins();
pluginManager.startPlugins();
List<ExtensionWrapper<Greeting>> greetings = pluginManager.getExtensions(Greeting.class);
for (ExtensionWrapper<Greeting> greeting : greetings) {
System.out.println(">>> " + greeting.getInstance().getGreeting());
}
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));
}
}

10
demo/app/src/main/resources/log4j.properties

@ -0,0 +1,10 @@
log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
log4j.logger.org.apache.wicket=INFO
log4j.logger.org.apache.wicket.protocol.http.HttpSessionStore=INFO
log4j.logger.org.apache.wicket.version=INFO
log4j.logger.org.apache.wicket.RequestCycle=INFO

8
demo/app/src/main/resources/logging.properties

@ -0,0 +1,8 @@
#handlers = java.util.logging.ConsoleHandler
handlers = org.slf4j.bridge.SLF4JBridgeHandler
# Set the default logging level for the root logger
.level = ALL
# Set the default formatter for new ConsoleHandler instances
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

5
demo/plugin1/plugin.properties

@ -0,0 +1,5 @@
plugin.id=welcome-plugin
plugin.class=org.pf4j.demo.welcome.WelcomePlugin
plugin.version=0.0.1
plugin.provider=Decebal Suiu
plugin.dependencies=

127
demo/plugin1/pom.xml

@ -0,0 +1,127 @@
<?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>org.pf4j.demo</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin1</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<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>
<build>
<plugins>
<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>
<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>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

37
demo/plugin1/src/main/assembly/assembly.xml

@ -0,0 +1,37 @@
<!--
Describes the plugin archive
@author Decebal Suiu
@version 1.0
-->
<assembly>
<id>plugin</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<scope>runtime</scope>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*:jar:*</include>
</includes>
</dependencySet>
</dependencySets>
<!--
<fileSets>
<fileSet>
<directory>target/classes</directory>
<outputDirectory>classes</outputDirectory>
</fileSet>
</fileSets>
-->
<fileSets>
<fileSet>
<directory>target/plugin-classes</directory>
<outputDirectory>classes</outputDirectory>
</fileSet>
</fileSets>
</assembly>

46
demo/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java

@ -0,0 +1,46 @@
/*
* 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 org.pf4j.demo.welcome;
import org.pf4j.Extension;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import org.pf4j.demo.api.Greeting;
/**
* @author Decebal Suiu
*/
public class WelcomePlugin extends Plugin {
public WelcomePlugin(PluginWrapper wrapper) {
super(wrapper);
}
public void start() {
System.out.println("WelcomePlugin.start()");
}
public void stop() {
System.out.println("WelcomePlugin.stop()");
}
@Extension
public static class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}
}

5
demo/plugin2/plugin.properties

@ -0,0 +1,5 @@
plugin.id=hello-plugin
plugin.class=org.pf4j.demo.hello.HelloPlugin
plugin.version=0.0.1
plugin.provider=Decebal Suiu
plugin.dependencies=

136
demo/plugin2/pom.xml

@ -0,0 +1,136 @@
<?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>org.pf4j.demo</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin2</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<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>
<build>
<plugins>
<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>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>
src/main/assembly/assembly.xml
</descriptor>
</descriptors>
<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>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<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>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

37
demo/plugin2/src/main/assembly/assembly.xml

@ -0,0 +1,37 @@
<!--
Describes the plugin archive
@author Decebal Suiu
@version 1.0
-->
<assembly>
<id>plugin</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<scope>runtime</scope>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*:jar:*</include>
</includes>
</dependencySet>
</dependencySets>
<!--
<fileSets>
<fileSet>
<directory>target/classes</directory>
<outputDirectory>classes</outputDirectory>
</fileSet>
</fileSets>
-->
<fileSets>
<fileSet>
<directory>target/plugin-classes</directory>
<outputDirectory>classes</outputDirectory>
</fileSet>
</fileSets>
</assembly>

48
demo/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java

@ -0,0 +1,48 @@
/*
* 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 org.pf4j.demo.hello;
import org.pf4j.Extension;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import org.pf4j.demo.api.Greeting;
/**
* A very simple plugin.
*
* @author Decebal Suiu
*/
public class HelloPlugin extends Plugin {
public HelloPlugin(PluginWrapper wrapper) {
super(wrapper);
}
public void start() {
System.out.println("HelloPlugin.start()");
}
public void stop() {
System.out.println("HelloPlugin.stop()");
}
@Extension
public static class HelloGreeting implements Greeting {
public String getGreeting() {
return "Hello";
}
}
}

60
demo/pom.xml

@ -0,0 +1,60 @@
<?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>org.pf4j</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.pf4j.demo</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>PF4J Demo</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>
<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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<optimize>true</optimize>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<module>app</module>
<module>api</module>
<module>plugin1</module>
<module>plugin2</module>
</modules>
</project>

122
pf4j/pom.xml

@ -0,0 +1,122 @@
<?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>org.pf4j</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>PF4J Library</name>
<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>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>1.6.4</slf4j.version>
</properties>
<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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<optimize>true</optimize>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<warName>root</warName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.3</version>
<configuration>
<goals>deploy</goals>
<autoVersionSubmodules>true</autoVersionSubmodules>
<tagNameFormat>release-@{project.version}</tagNameFormat>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>net.java.sezpoz</groupId>
<artifactId>sezpoz</artifactId>
<version>1.9</version>
</dependency>
<!-- Logs -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
</project>

80
pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java

@ -0,0 +1,80 @@
/*
* 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 org.pf4j;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.java.sezpoz.Index;
import net.java.sezpoz.IndexItem;
/**
* Using Sezpoz(http://sezpoz.java.net/) for extensions discovery.
*
* @author Decebal Suiu
*/
public class DefaultExtensionFinder implements ExtensionFinder {
private static final Logger LOG = LoggerFactory.getLogger(DefaultExtensionFinder.class);
private volatile List<IndexItem<Extension, Object>> indices;
private ClassLoader classLoader;
public DefaultExtensionFinder(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
LOG.debug("Find extensions for " + type);
List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>();
getIndices();
// System.out.println("indices = "+ indices);
for (IndexItem<Extension, Object> item : indices) {
try {
AnnotatedElement element = item.element();
Class<?> extensionType = (Class<?>) element;
LOG.debug("Checking extension type " + extensionType);
if (type.isAssignableFrom(extensionType)) {
Object instance = item.instance();
if (instance != null) {
LOG.debug("Added extension " + extensionType);
result.add(new ExtensionWrapper<T>(type.cast(instance), item.annotation().ordinal()));
}
}
} catch (InstantiationException e) {
LOG.error(e.getMessage(), e);
}
}
return result;
}
private List<IndexItem<Extension, Object>> getIndices() {
if (indices == null) {
indices = new ArrayList<IndexItem<Extension, Object>>();
Iterator<IndexItem<Extension, Object>> it = Index.load(Extension.class, Object.class, classLoader).iterator();
while (it.hasNext()) {
indices.add(it.next());
}
}
return indices;
}
}

90
pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java

@ -0,0 +1,90 @@
/*
* 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 org.pf4j;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.apache.commons.lang.StringUtils;
/**
* Read the plugin descriptor from the manifest file.
*
* @author Decebal Suiu
*/
public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder {
@Override
public PluginDescriptor find(File pluginRepository) throws PluginException {
// TODO it's ok with classes/ ?
File manifestFile = new File(pluginRepository, "classes/META-INF/MANIFEST.MF");
if (!manifestFile.exists()) {
// not found a 'plugin.xml' file for this plugin
throw new PluginException("Cannot find '" + manifestFile + "' file");
}
FileInputStream input = null;
try {
input = new FileInputStream(manifestFile);
} catch (FileNotFoundException e) {
// not happening
}
Manifest manifest = null;
try {
manifest = new Manifest(input);
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
} finally {
try {
input.close();
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
}
}
PluginDescriptor pluginDescriptor = new PluginDescriptor();
// TODO validate !!!
Attributes attrs = manifest.getMainAttributes();
String id = attrs.getValue("Plugin-Id");
if (StringUtils.isEmpty(id)) {
throw new PluginException("Plugin-Id cannot be empty");
}
pluginDescriptor.setPluginId(id);
String clazz = attrs.getValue("Plugin-Class");
if (StringUtils.isEmpty(clazz)) {
throw new PluginException("Plugin-Class cannot be empty");
}
pluginDescriptor.setPluginClass(clazz);
String version = attrs.getValue("Plugin-Version");
if (StringUtils.isEmpty(version)) {
throw new PluginException("Plugin-Version cannot be empty");
}
pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version));
String provider = attrs.getValue("Plugin-Provider");
pluginDescriptor.setProvider(provider);
String dependencies = attrs.getValue("Plugin-Dependencies");
pluginDescriptor.setDependencies(dependencies);
return pluginDescriptor;
}
}

327
pf4j/src/main/java/org/pf4j/DefaultPluginManager.java

@ -0,0 +1,327 @@
/*
* 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 org.pf4j;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.pf4j.util.DirectoryFilter;
import org.pf4j.util.UberClassLoader;
import org.pf4j.util.Unzip;
import org.pf4j.util.ZipFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 ExtensionFinder extensionFinder;
private PluginDescriptorFinder pluginDescriptorFinder;
/**
* A map of plugins this manager is responsible for (the key is the 'pluginId').
*/
private Map<String, Plugin> 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<Plugin> unresolvedPlugins;
/**
* A list with resolved plugins (resolved dependency).
*/
private List<Plugin> resolvedPlugins;
/**
* A list with disabled plugins.
*/
private List<Plugin> disabledPlugins;
private UberClassLoader uberClassLoader;
/**
* 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, Plugin>();
pluginClassLoaders = new HashMap<String, PluginClassLoader>();
pathToIdMap = new HashMap<String, String>();
unresolvedPlugins = new ArrayList<Plugin>();
resolvedPlugins = new ArrayList<Plugin>();
disabledPlugins = new ArrayList<Plugin>();
pluginDescriptorFinder = new DefaultPluginDescriptorFinder();
uberClassLoader = new UberClassLoader();
extensionFinder = new DefaultExtensionFinder(uberClassLoader);
}
/**
* Retrieves all active plugins.
*/
public List<Plugin> getPlugins() {
return new ArrayList<Plugin>(plugins.values());
}
public List<Plugin> getResolvedPlugins() {
return resolvedPlugins;
}
public Plugin getPlugin(String pluginId) {
return plugins.get(pluginId);
}
public List<Plugin> getUnresolvedPlugins() {
return unresolvedPlugins;
}
public List<Plugin> getDisabledPlugins() {
return disabledPlugins;
}
/**
* Start all active plugins.
*/
public void startPlugins() {
List<Plugin> resolvedPlugins = getResolvedPlugins();
for (Plugin plugin : resolvedPlugins) {
try {
plugin.start();
} catch (PluginException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* Stop all active plugins.
*/
public void stopPlugins() {
List<Plugin> resolvedPlugins = getResolvedPlugins();
for (Plugin plugin : resolvedPlugins) {
try {
plugin.stop();
} catch (PluginException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* Load plugins.
*/
public void loadPlugins() {
// check for plugins directory
if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
LOG.error("No '" + pluginsDirectory + "' directory");
return;
}
// expand all plugin archives
FilenameFilter zipFilter = new ZipFilter();
String[] zipFiles = pluginsDirectory.list(zipFilter);
for (String zipFile : zipFiles) {
try {
expandPluginArchive(zipFile);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
e.printStackTrace();
}
}
// load any plugin from plugins directory
FilenameFilter directoryFilter = new DirectoryFilter();
String[] directories = pluginsDirectory.list(directoryFilter);
for (String directory : directories) {
try {
loadPlugin(directory);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
e.printStackTrace();
}
}
// check for no plugins
if (directories.length == 0) {
LOG.info("No plugins");
return;
}
// resolve 'unresolvedPlugins'
resolvePlugins();
}
/**
* Get plugin class loader for this path.
*/
public PluginClassLoader getPluginClassLoader(String pluginId) {
return pluginClassLoaders.get(pluginId);
}
public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type) {
return extensionFinder.find(type);
}
private void loadPlugin(String fileName) throws Exception {
// test for plugin directory
File pluginDirectory = new File(pluginsDirectory, fileName);
if (!pluginDirectory.isDirectory()) {
return;
}
// try to load the plugin
String pluginPath = "/".concat(fileName);
// test for disabled plugin
if (disabledPlugins.contains(pluginPath)) {
return;
}
// it's a new plugin
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 '" + pluginClassName + "'" + " for plugin '" + pluginPath + "'");
// load plugin
LOG.debug("Loading plugin '" + pluginPath + "'");
PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor);
PluginLoader pluginLoader = new PluginLoader(this, pluginWrapper, pluginDirectory);
pluginLoader.load();
LOG.debug("Loaded plugin '" + pluginPath + "'");
// set some variables in plugin wrapper
pluginWrapper.setPluginPath(pluginPath);
pluginWrapper.setPluginClassLoader(pluginLoader.getPluginClassLoader());
// create the plugin instance
LOG.debug("Creating instance for plugin '" + pluginPath + "'");
Plugin plugin = getPluginInstance(pluginWrapper, pluginLoader);
LOG.debug("Created instance '" + plugin + "' for plugin '" + pluginPath + "'");
String pluginId = pluginDescriptor.getPluginId();
// add plugin to the list with plugins
plugins.put(pluginId, plugin);
unresolvedPlugins.add(plugin);
// add plugin class loader to the list with class loaders
PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
pluginDescriptor.setPluginClassLoader(pluginClassLoader);
pluginClassLoaders.put(pluginId, pluginClassLoader);
}
private void expandPluginArchive(String fileName) throws IOException {
File pluginArchiveFile = new File(pluginsDirectory, fileName);
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 '" + pluginArchiveFile + "' in '" + pluginDirectory + "'");
// create directorie for plugin
pluginDirectory.mkdirs();
// expand '.zip' file
Unzip unzip = new Unzip();
unzip.setSource(pluginArchiveFile);
unzip.setDestination(pluginDirectory);
unzip.extract();
}
}
private Plugin getPluginInstance(PluginWrapper pluginWrapper, PluginLoader pluginLoader)
throws Exception {
String pluginClassName = pluginWrapper.getDescriptor().getPluginClass();
ClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
Class<?> pluginClass = pluginClassLoader.loadClass(pluginClassName);
// once we have the class, we can do some checks on it to ensure
// that it is a valid implementation of a plugin.
int modifiers = pluginClass.getModifiers();
if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)
|| (!Plugin.class.isAssignableFrom(pluginClass))) {
throw new PluginException("The plugin class '" + pluginClassName
+ "' is not compatible.");
}
// create the plugin instance
Constructor<?> constructor = pluginClass.getConstructor(new Class[] { PluginWrapper.class });
Plugin plugin = (Plugin) constructor.newInstance(new Object[] { pluginWrapper });
return plugin;
}
private void resolvePlugins() {
resolveDependencies();
}
private void resolveDependencies() {
DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins);
resolvedPlugins = dependencyResolver.getSortedDependencies();
for (Plugin plugin : resolvedPlugins) {
unresolvedPlugins.remove(plugin);
uberClassLoader.addLoader(plugin.getWrapper().getPluginClassLoader());
}
}
}

81
pf4j/src/main/java/org/pf4j/DependencyResolver.java

@ -0,0 +1,81 @@
/*
* 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 org.pf4j;
import java.util.ArrayList;
import java.util.List;
import org.pf4j.util.DirectedGraph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Decebal Suiu
*/
class DependencyResolver {
private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class);
private List<Plugin> plugins;
public DependencyResolver(List<Plugin> plugins) {
this.plugins = plugins;
}
/**
* Get the list of plugins in dependency sorted order.
*/
public List<Plugin> getSortedDependencies() {
DirectedGraph<String> graph = new DirectedGraph<String>();
for (Plugin plugin : plugins) {
PluginDescriptor descriptor = plugin.getWrapper().getDescriptor();
String pluginId = descriptor.getPluginId();
List<String> dependencies = descriptor.getDependencies();
if (!dependencies.isEmpty()) {
for (String dependency : dependencies) {
graph.addEdge(pluginId, dependency);
}
} else {
graph.addVertex(pluginId);
}
}
LOG.debug("Graph: " + graph);
List<String> pluginsId = graph.reverseTopologicalSort();
if (pluginsId == null) {
LOG.error("Cyclic dependences !!!");
return null;
}
LOG.debug("Plugins order: " + pluginsId);
List<Plugin> sortedPlugins = new ArrayList<Plugin>();
for (String pluginId : pluginsId) {
sortedPlugins.add(getPlugin(pluginId));
}
return sortedPlugins;
}
private Plugin getPlugin(String pluginId) {
for (Plugin plugin : plugins) {
if (pluginId.equals(plugin.getWrapper().getDescriptor().getPluginId())) {
return plugin;
}
}
return null;
}
}

35
pf4j/src/main/java/org/pf4j/Extension.java

@ -0,0 +1,35 @@
/*
* 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 org.pf4j;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import net.java.sezpoz.Indexable;
/**
* @author Decebal Suiu
*/
@Indexable
@Retention(RUNTIME)
@Target(TYPE)
@Documented
public @interface Extension {
int ordinal() default 0;
}

24
pf4j/src/main/java/org/pf4j/ExtensionFinder.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 org.pf4j;
import java.util.List;
/**
* @author Decebal Suiu
*/
public interface ExtensionFinder {
public <T> List<ExtensionWrapper<T>> find(Class<T> type);
}

20
pf4j/src/main/java/org/pf4j/ExtensionPoint.java

@ -0,0 +1,20 @@
/*
* 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 org.pf4j;
/**
* @author Decebal Suiu
*/
public interface ExtensionPoint {
}

41
pf4j/src/main/java/org/pf4j/ExtensionWrapper.java

@ -0,0 +1,41 @@
/*
* 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 org.pf4j;
/**
* @author Decebal Suiu
*/
public class ExtensionWrapper<T> implements Comparable<ExtensionWrapper<T>> {
private final T instance;
private final int ordinal;
public ExtensionWrapper(T instance, int ordinal) {
this.instance = instance;
this.ordinal = ordinal;
}
public T getInstance() {
return instance;
}
public int getOrdinal() {
return ordinal;
}
@Override
public int compareTo(ExtensionWrapper<T> o) {
return (ordinal - o.getOrdinal());
}
}

68
pf4j/src/main/java/org/pf4j/Plugin.java

@ -0,0 +1,68 @@
/*
* 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 org.pf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class will be extended by all plugins and
* serve as the common class between a plugin and the application.
*
* @author Decebal Suiu
*/
public abstract class Plugin {
/**
* Makes logging service available for descending classes.
*/
protected final Logger log = LoggerFactory.getLogger(getClass());
/**
* Wrapper of the plugin.
*/
PluginWrapper wrapper;
/**
* Constructor to be used by plugin manager for plugin instantiation.
* Your plugins have to provide constructor with this exact signature to
* be successfully loaded by manager.
*/
public Plugin(final PluginWrapper wrapper) {
if (wrapper == null) {
throw new IllegalArgumentException("Wrapper cannot be null");
}
this.wrapper = wrapper;
}
/**
* Retrieves the wrapper of this plug-in.
*/
public final PluginWrapper getWrapper() {
return wrapper;
}
/**
* Start method is called by the application when the plugin is loaded.
*/
public void start() throws PluginException {
}
/**
* Stop method is called by the application when the plugin is unloaded.
*/
public void stop() throws PluginException {
}
}

91
pf4j/src/main/java/org/pf4j/PluginClassLoader.java

@ -0,0 +1,91 @@
/*
* 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 org.pf4j;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
/**
* One instance of this class should be created by plugin manager for every available plug-in.
*
* @author Decebal Suiu
*/
class PluginClassLoader extends URLClassLoader {
private static final String JAVA_PACKAGE_PREFIX = "java.";
private static final String JAVAX_PACKAGE_PREFIX = "javax.";
private static final String PLUGIN_PACKAGE_PREFIX = "org.pf4j.";
private PluginManager pluginManager;
private PluginWrapper pluginWrapper;
public PluginClassLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, ClassLoader parent) {
super(new URL[0], parent);
this.pluginManager = pluginManager;
this.pluginWrapper = pluginWrapper;
}
@Override
public void addURL(URL url) {
super.addURL(url);
}
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
// System.out.println(">>>" + className);
// first check whether it's a system class, delegate to the system loader
if (className.startsWith(JAVA_PACKAGE_PREFIX) || className.startsWith(JAVAX_PACKAGE_PREFIX)) {
return findSystemClass(className);
}
// second check whether it's already been loaded
Class<?> loadedClass = findLoadedClass(className);
if (loadedClass != null) {
return loadedClass;
}
// nope, try to load locally
try {
return findClass(className);
} catch (ClassNotFoundException e) {
// try next step
}
// if the class it's a part of the plugin engine use parent class loader
if (className.startsWith(PLUGIN_PACKAGE_PREFIX)) {
try {
return PluginClassLoader.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
// try next step
}
}
// look in dependencies
List<String> dependencies = pluginWrapper.getDescriptor().getDependencies();
for (String dependency : dependencies) {
PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency);
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
// try next dependency
}
}
// use the standard URLClassLoader (which follows normal parent delegation)
return super.loadClass(className);
}
}

140
pf4j/src/main/java/org/pf4j/PluginDescriptor.java

@ -0,0 +1,140 @@
/*
* 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 org.pf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* A plugin descriptor contains information about a plug-in obtained
* from the manifest (META-INF) file.
*
* @author Decebal Suiu
*/
class PluginDescriptor {
private String pluginId;
private String pluginClass;
private PluginVersion version;
private String provider;
private String pluginPath;
private List<String> dependencies;
private PluginClassLoader pluginClassLoader;
public PluginDescriptor() {
dependencies = new ArrayList<String>();
}
/**
* Returns the unique identifier of this plugin.
*/
public String getPluginId() {
return pluginId;
}
/**
* Returns the name of the class that implements Plugin interface.
*/
public String getPluginClass() {
return pluginClass;
}
/**
* Returns the version of this plugin.
*/
public PluginVersion getVersion() {
return version;
}
/**
* Returns the provider name of this plugin.
*/
public String getProvider() {
return provider;
}
/**
* Returns the path of this plugin relative to plugins directory.
*/
public String getPluginPath() {
return pluginPath;
}
/**
* Returns all dependencies declared by this plugin.
* Returns an empty array if this plugin does not declare any require.
*/
public List<String> getDependencies() {
return dependencies;
}
/**
* Returns the plugin class loader used to load classes and resources
* for this plug-in. The class loader can be used to directly access
* plug-in resources and classes.
*/
public PluginClassLoader getPluginClassLoader() {
return pluginClassLoader;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("pluginId", pluginId)
.append("pluginClass", pluginClass)
.append("version", version)
.append("provider", provider)
.append("pluginPath", pluginPath)
.append("dependencies", dependencies)
.toString();
}
void setPluginId(String pluginId) {
this.pluginId = pluginId;
}
void setPluginClass(String pluginClassName) {
this.pluginClass = pluginClassName;
}
void setPluginVersion(PluginVersion version) {
this.version = version;
}
void setProvider(String provider) {
this.provider = provider;
}
void setPluginPath(String pluginPath) {
this.pluginPath = pluginPath;
}
void setDependencies(String dependencies) {
if (dependencies != null) {
this.dependencies = Arrays.asList(StringUtils.split(dependencies, ','));
} else {
this.dependencies = Collections.emptyList();
}
}
void setPluginClassLoader(PluginClassLoader pluginClassLoader) {
this.pluginClassLoader = pluginClassLoader;
}
}

28
pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java

@ -0,0 +1,28 @@
/*
* 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 org.pf4j;
import java.io.File;
/**
* Find a plugin descriptor in a directory (plugin repository).
* You can find in manifest file @see DefaultPluginDescriptorFinder,
* xml file, properties file, java services (with ServiceLoader), etc.
*
* @author Decebal Suiu
*/
public interface PluginDescriptorFinder {
public PluginDescriptor find(File pluginRepository) throws PluginException;
}

40
pf4j/src/main/java/org/pf4j/PluginException.java

@ -0,0 +1,40 @@
/*
* 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 org.pf4j;
/**
* An exception used to indicate that a plugin problem occurred.
*
* @author Decebal Suiu
*/
class PluginException extends Exception {
private static final long serialVersionUID = 1L;
public PluginException() {
super();
}
public PluginException(String message) {
super(message);
}
public PluginException(Throwable cause) {
super(cause);
}
public PluginException(String message, Throwable cause) {
super(message, cause);
}
}

142
pf4j/src/main/java/org/pf4j/PluginLoader.java

@ -0,0 +1,142 @@
/*
* 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 org.pf4j;
import java.io.File;
import java.io.FilenameFilter;
import java.net.MalformedURLException;
import java.util.Vector;
import org.pf4j.util.DirectoryFilter;
import org.pf4j.util.JarFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Load all informations needed by a plugin.
* This means add all jar files from 'lib' directory, 'classes'
* to classpath.
* It's a class for only the internal use.
*
* @author Decebal Suiu
*/
class PluginLoader {
private static final Logger LOG = LoggerFactory.getLogger(PluginLoader.class);
/*
* The plugin repository.
*/
private File pluginRepository;
/*
* The directory with '.class' files.
*/
private File classesDirectory;
/*
* The directory with '.jar' files.
*/
private File libDirectory;
private PluginClassLoader pluginClassLoader;
public PluginLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, File pluginRepository) {
this.pluginRepository = pluginRepository;
classesDirectory = new File(pluginRepository, "classes");
libDirectory = new File(pluginRepository, "lib");
ClassLoader parent = getClass().getClassLoader();
pluginClassLoader = new PluginClassLoader(pluginManager, pluginWrapper, parent);
LOG.debug("Created class loader " + pluginClassLoader);
}
public File getPluginRepository() {
return pluginRepository;
}
public boolean load() {
return loadClassesAndJars();
}
public PluginClassLoader getPluginClassLoader() {
return pluginClassLoader;
}
private boolean loadClassesAndJars() {
return loadClasses() && loadJars();
}
private void getJars(Vector<String> v, File file) {
FilenameFilter jarFilter = new JarFilter();
FilenameFilter directoryFilter = new DirectoryFilter();
if (file.exists() && file.isDirectory() && file.isAbsolute()) {
String[] jars = file.list(jarFilter);
for (int i = 0; (jars != null) && (i < jars.length); ++i) {
v.addElement(jars[i]);
}
String[] directoryList = file.list(directoryFilter);
for (int i = 0; (directoryList != null) && (i < directoryList.length); ++i) {
File directory = new File(file, directoryList[i]);
getJars(v, directory);
}
}
}
private boolean loadClasses() {
// make 'classesDirectory' absolute
classesDirectory = classesDirectory.getAbsoluteFile();
if (classesDirectory.exists() && classesDirectory.isDirectory()) {
LOG.debug("Found '" + classesDirectory.getPath() + "' directory");
try {
pluginClassLoader.addURL(classesDirectory.toURI().toURL());
LOG.debug("Added '" + classesDirectory + "' to the class loader path");
} catch (MalformedURLException e) {
e.printStackTrace();
LOG.error(e.getMessage(), e);
return false;
}
}
return true;
}
/**
* Add all *.jar files from '/lib' directory.
*/
private boolean loadJars() {
// make 'jarDirectory' absolute
libDirectory = libDirectory.getAbsoluteFile();
Vector<String> jars = new Vector<String>();
getJars(jars, libDirectory);
for (String jar : jars) {
File jarFile = new File(libDirectory, jar);
try {
pluginClassLoader.addURL(jarFile.toURI().toURL());
LOG.debug("Added '" + jarFile + "' to the class loader path");
} catch (MalformedURLException e) {
e.printStackTrace();
LOG.error(e.getMessage(), e);
return false;
}
}
return true;
}
}

49
pf4j/src/main/java/org/pf4j/PluginManager.java

@ -0,0 +1,49 @@
/*
* 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 org.pf4j;
import java.util.List;
/**
* Provides the functionality for plugin management such as load,
* start and stop the plugins.
*
* @author Decebal Suiu
*/
public interface PluginManager {
/**
* Retrieves all plugins.
*/
public List<Plugin> getPlugins();
/**
* Load plugins.
*/
public void loadPlugins();
/**
* Start all active plugins.
*/
public void startPlugins();
/**
* Stop all active plugins.
*/
public void stopPlugins();
public PluginClassLoader getPluginClassLoader(String pluginId);
public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type);
}

191
pf4j/src/main/java/org/pf4j/PluginVersion.java

@ -0,0 +1,191 @@
/*
* 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 org.pf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
/**
* Represents the version of a Plugin and allows versions to be compared.
* Version identifiers have five components.
*
* 1. Major version. A non-negative integer.
* 2. Minor version. A non-negative integer.
* 3. Release version. A non-negative integer.
* 4. Build version. A non-negative integer.
* 5. Qualifier. A text string.
*
* This class is immutable.
*
* @author Decebal Suiu
*/
public class PluginVersion implements Comparable<PluginVersion> {
private int major;
private int minor;
private int release;
private int build;
private String qualifier;
private PluginVersion() {
}
public PluginVersion(int major, int minor, int release) {
this.major = major;
this.minor = minor;
this.release = release;
}
public PluginVersion(int major, int minor, int release, int build) {
this.major = major;
this.minor = minor;
this.release = release;
this.build = build;
}
public PluginVersion(int major, int minor, int release, int build, String qualifier) {
this.major = major;
this.minor = minor;
this.release = release;
this.build = build;
this.qualifier = qualifier;
}
public static PluginVersion createVersion(String version) {
if (version == null) {
return new PluginVersion();
}
PluginVersion v = new PluginVersion();
StringTokenizer st = new StringTokenizer(version, ".");
List<String> tmp = new ArrayList<String>();
for (int i = 0; st.hasMoreTokens() && i < 4; i++) {
tmp.add(st.nextToken());
}
int n = tmp.size();
switch (n) {
case 0 :
break;
case 1 :
v.major = Integer.parseInt(tmp.get(0));
break;
case 2 :
v.major = Integer.parseInt(tmp.get(0));
v.minor = Integer.parseInt(tmp.get(1));
break;
case 3 :
v.major = Integer.parseInt(tmp.get(0));
v.minor = Integer.parseInt(tmp.get(1));
v.release = Integer.parseInt(tmp.get(2));
break;
case 4 :
v.major = Integer.parseInt(tmp.get(0));
v.minor = Integer.parseInt(tmp.get(1));
v.release = Integer.parseInt(tmp.get(2));
v.build = Integer.parseInt(tmp.get(3));
break;
}
return v;
}
public int getMajor() {
return this.major;
}
public int getMinor() {
return this.minor;
}
public int getRelease() {
return this.release;
}
public int getBuild() {
return this.build;
}
public String getQualifier() {
return qualifier;
}
public String toString() {
StringBuffer sb = new StringBuffer(50);
sb.append(major);
sb.append('.');
sb.append(minor);
sb.append('.');
sb.append(release);
sb.append('.');
sb.append(build);
if (qualifier != null) {
sb.append(qualifier);
}
return sb.toString();
}
public int compareTo(PluginVersion version) {
if (version.major > major) {
return 1;
} else if (version.major < major) {
return -1;
}
if (version.minor > minor) {
return 1;
} else if (version.minor < minor) {
return -1;
}
if (version.release > release) {
return 1;
} else if (version.release < release) {
return -1;
}
if (version.build > build) {
return 1;
} else if (version.build < build) {
return -1;
}
return 0;
}
/*
private String extractQualifier(String token) {
StringTokenizer st = new StringTokenizer(token, "-");
if (st.countTokens() == 2) {
return st.
}
}
*/
// for test only
public static void main(String[] args) {
PluginVersion v = PluginVersion.createVersion("4.0.0.123");
System.out.println(v.toString());
// v = PluginVersion.createVersion("4.0.0.123-alpha");
// System.out.println(v.toString());
PluginVersion v1 = PluginVersion.createVersion("4.1.0");
System.out.println(v1.toString());
PluginVersion v2 = PluginVersion.createVersion("4.0.32");
System.out.println(v2.toString());
System.out.println(v1.compareTo(v2));
}
}

61
pf4j/src/main/java/org/pf4j/PluginWrapper.java

@ -0,0 +1,61 @@
/*
* 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 org.pf4j;
/**
* A wrapper over plugin instance.
*
* @author Decebal Suiu
*/
public class PluginWrapper {
PluginDescriptor descriptor;
String pluginPath;
PluginClassLoader pluginClassLoader;
public PluginWrapper(PluginDescriptor descriptor) {
this.descriptor = descriptor;
}
/**
* Returns the plugin descriptor.
*/
public PluginDescriptor getDescriptor() {
return descriptor;
}
/**
* Returns the path of this plugin relative to plugins directory.
*/
public String getPluginPath() {
return pluginPath;
}
/**
* Returns the plugin class loader used to load classes and resources
* for this plug-in. The class loader can be used to directly access
* plug-in resources and classes.
*/
public PluginClassLoader getPluginClassLoader() {
return pluginClassLoader;
}
void setPluginPath(String pluginPath) {
this.pluginPath = pluginPath;
}
void setPluginClassLoader(PluginClassLoader pluginClassLoader) {
this.pluginClassLoader = pluginClassLoader;
}
}

171
pf4j/src/main/java/org/pf4j/util/DirectedGraph.java

@ -0,0 +1,171 @@
/*
* 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 org.pf4j.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
/**
* @author Decebal Suiu
*/
public class DirectedGraph<V> {
/**
* The implementation here is basically an adjacency list, but instead
* of an array of lists, a Map is used to map each vertex to its list of
* adjacent vertices.
*/
private Map<V, List<V>> neighbors = new HashMap<V, List<V>>();
/**
* Add a vertex to the graph. Nothing happens if vertex is already in graph.
*/
public void addVertex(V vertex) {
if (neighbors.containsKey(vertex)) {
return;
}
neighbors.put(vertex, new ArrayList<V>());
}
/**
* True if graph contains vertex.
*/
public boolean containsVertex(V vertex) {
return neighbors.containsKey(vertex);
}
/**
* Add an edge to the graph; if either vertex does not exist, it's added.
* This implementation allows the creation of multi-edges and self-loops.
*/
public void addEdge(V from, V to) {
this.addVertex(from);
this.addVertex(to);
neighbors.get(from).add(to);
}
/**
* Remove an edge from the graph. Nothing happens if no such edge.
* @throws IllegalArgumentException if either vertex doesn't exist.
*/
public void remove(V from, V to) {
if (!(this.containsVertex(from) && this.containsVertex(to))) {
throw new IllegalArgumentException("Nonexistent vertex");
}
neighbors.get(from).remove(to);
}
/**
* Report (as a Map) the out-degree of each vertex.
*/
public Map<V, Integer> outDegree() {
Map<V, Integer> result = new HashMap<V, Integer>();
for (V vertex : neighbors.keySet()) {
result.put(vertex, neighbors.get(vertex).size());
}
return result;
}
/**
* Report (as a Map) the in-degree of each vertex.
*/
public Map<V,Integer> inDegree() {
Map<V, Integer> result = new HashMap<V, Integer>();
for (V vertex : neighbors.keySet()) {
result.put(vertex, 0); // all in-degrees are 0
}
for (V from : neighbors.keySet()) {
for (V to : neighbors.get(from)) {
result.put(to, result.get(to) + 1); // increment in-degree
}
}
return result;
}
/**
* Report (as a List) the topological sort of the vertices; null for no such sort.
*/
public List<V> topologicalSort() {
Map<V, Integer> degree = inDegree();
// determine all vertices with zero in-degree
Stack<V> zeroVertices = new Stack<V>(); // stack as good as any here
for (V v : degree.keySet()) {
if (degree.get(v) == 0) {
zeroVertices.push(v);
}
}
// determine the topological order
List<V> result = new ArrayList<V>();
while (!zeroVertices.isEmpty()) {
V vertex = zeroVertices.pop(); // choose a vertex with zero in-degree
result.add(vertex); // vertex 'v' is next in topological order
// "remove" vertex 'v' by updating its neighbors
for (V neighbor : neighbors.get(vertex)) {
degree.put(neighbor, degree.get(neighbor) - 1);
// remember any vertices that now have zero in-degree
if (degree.get(neighbor) == 0) {
zeroVertices.push(neighbor);
}
}
}
// check that we have used the entire graph (if not, there was a cycle)
if (result.size() != neighbors.size()) {
return null;
}
return result;
}
/**
* Report (as a List) the reverse topological sort of the vertices; null for no such sort.
*/
public List<V> reverseTopologicalSort() {
List<V> list = topologicalSort();
if (list == null) {
return null;
}
Collections.reverse(list);
return list;
}
/**
* True if graph is a dag (directed acyclic graph).
*/
public boolean isDag () {
return topologicalSort() != null;
}
/**
* String representation of graph.
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
for (V vertex : neighbors.keySet()) {
sb.append("\n " + vertex + " -> " + neighbors.get(vertex));
}
return sb.toString();
}
}

35
pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java

@ -0,0 +1,35 @@
/*
* 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 org.pf4j.util;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
/**
* @author Decebal Suiu
*/
public class DirectoryFilter implements FileFilter, FilenameFilter {
/**
* Accepts any file ending in .jar. The case of the filename is ignored.
*/
public boolean accept(File file) {
return file.isDirectory();
}
public boolean accept(File dir, String name) {
return accept(new File(dir, name));
}
}

41
pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java

@ -0,0 +1,41 @@
/*
* 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 org.pf4j.util;
import java.io.File;
import java.io.FilenameFilter;
/**
* @author Decebal Suiu
*/
public class ExtensionFilter implements FilenameFilter {
private String extension;
public ExtensionFilter(String extension) {
this.extension = extension;
}
/**
* Accepts any file ending in extension. The case of the filename is ignored.
*/
public boolean accept(File file) {
// perform a case insensitive check.
return file.getName().toUpperCase().endsWith(extension.toUpperCase());
}
public boolean accept(File dir, String name) {
return accept(new File(dir, name));
}
}

32
pf4j/src/main/java/org/pf4j/util/JarFilter.java

@ -0,0 +1,32 @@
/*
* 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 org.pf4j.util;
/**
* File filter that accepts all files ending with .JAR.
* This filter is case insensitive.
*
* @author Decebal Suiu
*/
public class JarFilter extends ExtensionFilter {
/**
* The extension that this filter will search for.
*/
private static final String JAR_EXTENSION = ".JAR";
public JarFilter() {
super(JAR_EXTENSION);
}
}

72
pf4j/src/main/java/org/pf4j/util/UberClassLoader.java

@ -0,0 +1,72 @@
/*
* 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 org.pf4j.util;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A class loader that has multiple loaders and uses them for loading classes and resources.
*
* @author Decebal Suiu
*/
public class UberClassLoader extends ClassLoader {
private Set<ClassLoader> loaders = new HashSet<ClassLoader>();
public void addLoader(ClassLoader loader) {
loaders.add(loader);
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
for (ClassLoader loader : loaders) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException e) {
// try next
}
}
throw new ClassNotFoundException(name);
}
@Override
public URL findResource(String name) {
for (ClassLoader loader : loaders) {
URL url = loader.getResource(name);
if (url != null) {
return url;
}
}
return null;
}
@Override
protected Enumeration<URL> findResources(String name) throws IOException {
List<URL> resources = new ArrayList<URL>();
for (ClassLoader loader : loaders) {
resources.addAll(Collections.list(loader.getResources(name)));
}
return Collections.enumeration(resources);
}
}

122
pf4j/src/main/java/org/pf4j/util/Unzip.java

@ -0,0 +1,122 @@
/*
* 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 org.pf4j.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class extracts the containt of the plugin archive into a directory.
* It's a class for only the internal use.
*
* @author Decebal Suiu
*/
public class Unzip {
private static final Logger LOG = LoggerFactory.getLogger(Unzip.class);
/**
* Holds the destination directory.
* File will be unzipped into the destination directory.
*/
private File destination;
/**
* Holds path to zip file.
*/
private File source;
public Unzip() {
}
public Unzip(File source, File destination) {
this.source = source;
this.destination = destination;
}
public void setSource(File source) {
this.source = source;
}
public void setDestination(File destination) {
this.destination = destination;
}
public void extract() throws IOException {
LOG.debug("Extract content of " + source + " to " + destination);
// delete destination file if exists
removeDirectory(destination);
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source));
ZipEntry zipEntry = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
try {
File file = new File(destination, zipEntry.getName());
// create intermediary directories - sometimes zip don't add them
File dir = new File(file.getParent());
dir.mkdirs();
if (zipEntry.isDirectory()) {
file.mkdirs();
} else {
byte[] buffer = new byte[1024];
int length = 0;
FileOutputStream fos = new FileOutputStream(file);
while ((length = zipInputStream.read(buffer)) >= 0) {
fos.write(buffer, 0, length);
}
fos.close();
}
} catch (FileNotFoundException e) {
LOG.error("File '" + zipEntry.getName() + "' not found");
}
}
zipInputStream.close();
}
private boolean removeDirectory(File directory) {
if (!directory.exists()) {
return true;
}
if (!directory.isDirectory()) {
return false;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
removeDirectory(file);
} else {
file.delete();
}
}
return directory.delete();
}
}

32
pf4j/src/main/java/org/pf4j/util/ZipFilter.java

@ -0,0 +1,32 @@
/*
* 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 org.pf4j.util;
/**
* File filter that accepts all files ending with .ZIP.
* This filter is case insensitive.
*
* @author Decebal Suiu
*/
public class ZipFilter extends ExtensionFilter {
/**
* The extension that this filter will search for.
*/
private static final String ZIP_EXTENSION = ".ZIP";
public ZipFilter() {
super(ZIP_EXTENSION);
}
}

72
pom.xml

@ -0,0 +1,72 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.pf4j</groupId>
<artifactId>pom</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>PF4J</name>
<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>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>1.6.4</slf4j.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<optimize>true</optimize>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<goals>deploy</goals>
<autoVersionSubmodules>true</autoVersionSubmodules>
<tagNameFormat>release-@{project.version}</tagNameFormat>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
</plugin>
</plugins>
</build>
<modules>
<module>pf4j</module>
<module>demo</module>
</modules>
</project>

24
run-demo.sh

@ -0,0 +1,24 @@
#!/bin/sh
#
# This script creates and run the pf4j demo.
#
# create artifacts using maven
mvn clean package
# create demo-dist folder
rm -fr demo-dist
mkdir demo-dist
mkdir demo-dist/plugins
# copy artifacts to demo-dist folder
cp -r demo/app/target/pf4j-demo-*/* demo-dist/
cp demo/plugin1/target/pf4j-demo-plugin1-*.zip demo-dist/plugins/
cp demo/plugin2/target/pf4j-demo-plugin2-*.zip demo-dist/plugins/
# run demo
cd demo-dist
java -jar pf4j-demo-app-*.jar
cd -
Loading…
Cancel
Save