mirror of https://github.com/pf4j/pf4j.git
Decebal Suiu
12 years ago
commit
2aeb77b8c8
44 changed files with 3153 additions and 0 deletions
@ -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 - |
||||
|
Loading…
Reference in new issue