@@ -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. |
@@ -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> |
@@ -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(); | |||
} |
@@ -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> |
@@ -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> | |||
@@ -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)); | |||
} | |||
} |
@@ -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 |
@@ -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 |
@@ -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= |
@@ -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> |
@@ -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> |
@@ -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"; | |||
} | |||
} | |||
} |
@@ -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= |
@@ -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> |
@@ -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> |
@@ -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"; | |||
} | |||
} | |||
} |
@@ -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> |
@@ -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> |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} |
@@ -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 { | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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 { | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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> |
@@ -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 - | |||