diff options
author | Decebal Suiu <decebal.suiu@gmail.com> | 2012-10-11 13:29:43 +0300 |
---|---|---|
committer | Decebal Suiu <decebal.suiu@gmail.com> | 2012-10-11 13:29:43 +0300 |
commit | 2aeb77b8c8e6023651d1fc7b9ef31736b855cafa (patch) | |
tree | 00b9bf8944c517fe991dbf83da672c002f9f12f0 | |
download | pf4j-2aeb77b8c8e6023651d1fc7b9ef31736b855cafa.tar.gz pf4j-2aeb77b8c8e6023651d1fc7b9ef31736b855cafa.zip |
first commit
44 files changed, 3153 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa038a2 --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +Plugin Framework for Java (PF4J) +===================== + +A plugin is a way for a third party to extend the functionality of an application. A plugin implements extensions points +declared by application or another plugins. Also a plugin can defines extension points. + +Components +------------------- +- **Plugin** is the base class for all plugins types. Each plugin is loaded into a separate class loader to avoid conflicts. +- **PluginManager** is used for all aspects of plugins management (loading, starting, stopping). +- **ExtensionPoint** is a point in the application where custom code can be invoked. +- **Extension** is an implementation of extension point. + +Artifacts +------------------- +- PF4J `pf4j` (jar) +- PF4J Demo `pf4j-demo` (executable jar) + +Using Maven +------------------- + +First you must install the pf4j artifacts in your local maven repository with: + + mvn clean install + +I will upload these artifacts in maven central repository as soon as possible. + +In your pom.xml you must define the dependencies to PF4J artifacts with: + +```xml +<dependency> + <groupId>org.pf4j</groupId> + <artifactId>pf4j</artifactId> + <version>${pf4j.version}</version> +</dependency> +``` + +where ${pf4j.version} is the last pf4j version. + +How to use +------------------- +It's very simple to add pf4j in your application: + + public static void main(String[] args) { + ... + + PluginManager pluginManager = new DefaultPluginManager(); + pluginManager.loadPlugins(); + pluginManager.startPlugins(); + + ... + } + +In above code, I created a DefaultPluginManager (it's the default implementation for +PluginManager interface) that load and start all active(resolved) plugins. +The plugins are stored in a folder. You can specify the plugins folder in constructor of DefaultPluginManager +or using the constructor without parameters (in this case plugins folder is returned by System.getProperty("pf4j.pluginsDir", "plugins")). + +The structure of plugins folder is: +- plugin1.zip (or plugin1 folder) +- plugin2.zip (or plugin2 folder) +... +- pluginN.zip (or pluginN folder) + +In plugins folder you can put a plugin as folder or archive file (zip). +A plugin folder has this structure: +- `classes` folder +- `lib` folder (optional - if the plugin used third party libraries) + +The plugin manager discovers plugins metadata using a PluginDescriptorFinder. DefaultPluginDescriptorFinder lookup plugin descriptor in MANIFEST.MF file. +In this case the classes/META-INF/MANIFEST.MF looks like: + + Manifest-Version: 1.0 + Archiver-Version: Plexus Archiver + Created-By: Apache Maven + Built-By: decebal + Build-Jdk: 1.6.0_17 + Plugin-Class: org.pf4j.demo.welcome.WelcomePlugin + Plugin-Dependencies: x, y z + Plugin-Id: welcome-plugin + Plugin-Provider: Decebal Suiu + Plugin-Version: 0.0.1 + +In above manifest I described a plugin with id `welcome-plugin`, with class `org.pf4j.demo.welcome.WelcomePlugin`, with version `0.0.1` and with dependencies +to plugins `x, y, z`. + +You can define an extension point in your application using ExtensionPoint interface marker. + + public interface Greeting extends ExtensionPoint { + + public String getGreeting(); + + } + +Another important internal component is ExtensionFinder that describes how plugin manager discovers extensions for extensions points. DefaultExtensionFinder look up extensions using Extension annotation. + + public class WelcomePlugin extends Plugin { + + public WelcomePlugin(PluginWrapper wrapper) { + super(wrapper); + } + + @Extension + public static class WelcomeGreeting implements Greeting { + + public String getGreeting() { + return "Welcome"; + } + + } + + } + +In above code I supply an extension for the Greeting extension point. + +You can retrieves all extensions for an extension point with: + + List<ExtensionWrapper<Greeting>> greetings = pluginManager.getExtensions(Greeting.class); + for (ExtensionWrapper<Greeting> greeting : greetings) { + System.out.println(">>> " + greeting.getInstance().getGreeting()); + } + + +For more information please see the demo sources. + +Demo +------------------- + +I have a tiny demo application. In this demo I have implemented two widgets types: +a chart widget (using open flash chart) and a text widget (display a Lorem Ipsum). +You can drag and drop widgets, perform some actions on each widget, add or remove new +widgets, change widget settings, collapse widgets. + +The demo application is in demo folder. +To run the demo application use: + + ./run-demo.sh + +License +-------------- + +Copyright 2012 Decebal Suiu + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with +the License. You may obtain a copy of the License in the LICENSE file, or at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/demo/api/pom.xml b/demo/api/pom.xml new file mode 100644 index 0000000..1514476 --- /dev/null +++ b/demo/api/pom.xml @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.pf4j.demo</groupId> + <artifactId>pom</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + <artifactId>pf4j-demo-api</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Demo Api</name> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + + <build> + <plugins> + <plugin> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.pf4j</groupId> + <artifactId>pf4j</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> + +</project> diff --git a/demo/api/src/main/java/org/pf4j/demo/api/Greeting.java b/demo/api/src/main/java/org/pf4j/demo/api/Greeting.java new file mode 100644 index 0000000..b21f80c --- /dev/null +++ b/demo/api/src/main/java/org/pf4j/demo/api/Greeting.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.demo.api; + +import org.pf4j.ExtensionPoint; + +/** + * @author Decebal Suiu + */ +public interface Greeting extends ExtensionPoint { + + public String getGreeting(); + +} diff --git a/demo/app/pom.xml b/demo/app/pom.xml new file mode 100644 index 0000000..d9084b1 --- /dev/null +++ b/demo/app/pom.xml @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.pf4j.demo</groupId> + <artifactId>pom</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + <artifactId>pf4j-demo-app</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Demo App</name> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + + <properties> + <main.class>org.pf4j.demo.Boot</main.class> + </properties> + + <build> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <version>2.3</version> + <configuration> + <descriptors> + <descriptor> + src/main/assembly/assembly.xml + </descriptor> + </descriptors> + <appendAssemblyId>false</appendAssemblyId> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <phase>package</phase> + <goals> + <goal>attached</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>2.3.1</version> + <configuration> + <archive> + <manifest> + <addClasspath>true</addClasspath> + <classpathPrefix>lib/</classpathPrefix> + <mainClass>${main.class}</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + + <plugin> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.pf4j</groupId> + <artifactId>pf4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.pf4j.demo</groupId> + <artifactId>pf4j-demo-api</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + +</project> diff --git a/demo/app/src/main/assembly/assembly.xml b/demo/app/src/main/assembly/assembly.xml new file mode 100644 index 0000000..1d9e8e4 --- /dev/null +++ b/demo/app/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ +<!-- + Describes the dist + + @author Decebal Suiu + @version 1.0 +--> +<assembly> + <id>plugin</id> + <formats> + <format>dir</format> + </formats> + <includeBaseDirectory>false</includeBaseDirectory> + <dependencySets> + <dependencySet> + <useProjectArtifact>false</useProjectArtifact> + <outputDirectory>lib</outputDirectory> + <includes> + <include>*:jar:*</include> + </includes> + </dependencySet> + </dependencySets> + <fileSets> + <fileSet> + <directory>${project.build.directory}</directory> + <outputDirectory></outputDirectory> + <includes> + <include>*.jar</include> + </includes> + </fileSet> + </fileSets> +</assembly> + diff --git a/demo/app/src/main/java/org/pf4j/demo/Boot.java b/demo/app/src/main/java/org/pf4j/demo/Boot.java new file mode 100644 index 0000000..b34af53 --- /dev/null +++ b/demo/app/src/main/java/org/pf4j/demo/Boot.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.demo; + +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.pf4j.DefaultPluginManager; +import org.pf4j.ExtensionWrapper; +import org.pf4j.PluginManager; +import org.pf4j.demo.api.Greeting; + +/** + * A boot class that start the demo. + * + * @author Decebal Suiu + */ +public class Boot { + + public static void main(String[] args) { + // print logo + printLogo(); + + // load and start (active/resolved) plugins + final PluginManager pluginManager = new DefaultPluginManager(); + pluginManager.loadPlugins(); + pluginManager.startPlugins(); + + List<ExtensionWrapper<Greeting>> greetings = pluginManager.getExtensions(Greeting.class); + for (ExtensionWrapper<Greeting> greeting : greetings) { + System.out.println(">>> " + greeting.getInstance().getGreeting()); + } + + pluginManager.stopPlugins(); + /* + Runtime.getRuntime().addShutdownHook(new Thread() { + + @Override + public void run() { + pluginManager.stopPlugins(); + } + + }); + */ + } + + private static void printLogo() { + System.out.println(StringUtils.repeat("#", 40)); + System.out.println(StringUtils.center("PF4J-DEMO", 40)); + System.out.println(StringUtils.repeat("#", 40)); + } + +} diff --git a/demo/app/src/main/resources/log4j.properties b/demo/app/src/main/resources/log4j.properties new file mode 100644 index 0000000..b3e5aa8 --- /dev/null +++ b/demo/app/src/main/resources/log4j.properties @@ -0,0 +1,10 @@ +log4j.rootLogger=DEBUG,Console + +log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.layout=org.apache.log4j.PatternLayout +log4j.appender.Console.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n + +log4j.logger.org.apache.wicket=INFO +log4j.logger.org.apache.wicket.protocol.http.HttpSessionStore=INFO +log4j.logger.org.apache.wicket.version=INFO +log4j.logger.org.apache.wicket.RequestCycle=INFO diff --git a/demo/app/src/main/resources/logging.properties b/demo/app/src/main/resources/logging.properties new file mode 100644 index 0000000..6fe7cef --- /dev/null +++ b/demo/app/src/main/resources/logging.properties @@ -0,0 +1,8 @@ +#handlers = java.util.logging.ConsoleHandler +handlers = org.slf4j.bridge.SLF4JBridgeHandler + +# Set the default logging level for the root logger +.level = ALL + +# Set the default formatter for new ConsoleHandler instances +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter diff --git a/demo/plugin1/plugin.properties b/demo/plugin1/plugin.properties new file mode 100644 index 0000000..9da9bcc --- /dev/null +++ b/demo/plugin1/plugin.properties @@ -0,0 +1,5 @@ +plugin.id=welcome-plugin +plugin.class=org.pf4j.demo.welcome.WelcomePlugin +plugin.version=0.0.1 +plugin.provider=Decebal Suiu +plugin.dependencies= diff --git a/demo/plugin1/pom.xml b/demo/plugin1/pom.xml new file mode 100644 index 0000000..10c1163 --- /dev/null +++ b/demo/plugin1/pom.xml @@ -0,0 +1,127 @@ +<?xml version="1.0"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.pf4j.demo</groupId> + <artifactId>pom</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + <artifactId>pf4j-demo-plugin1</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Demo Plugin #1</name> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>properties-maven-plugin</artifactId> + <version>1.0-alpha-2</version> + <executions> + <execution> + <phase>initialize</phase> + <goals> + <goal>read-project-properties</goal> + </goals> + <configuration> + <files> + <file>plugin.properties</file> + </files> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <version>1.6</version> + <executions> + <execution> + <id>unzip jar file</id> + <phase>package</phase> + <configuration> + <target> + <unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" /> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <version>2.3</version> + <configuration> + <descriptors> + <descriptor> + src/main/assembly/assembly.xml + </descriptor> + </descriptors> + <appendAssemblyId>false</appendAssemblyId> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <phase>package</phase> + <goals> + <goal>attached</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestEntries> + <Plugin-Id>${plugin.id}</Plugin-Id> + <Plugin-Class>${plugin.class}</Plugin-Class> + <Plugin-Version>${plugin.version}</Plugin-Version> + <Plugin-Provider>${plugin.provider}</Plugin-Provider> + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> + </manifestEntries> + </archive> + </configuration> + </plugin> + + <plugin> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.pf4j</groupId> + <artifactId>pf4j</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.pf4j.demo</groupId> + <artifactId>pf4j-demo-api</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> + +</project> diff --git a/demo/plugin1/src/main/assembly/assembly.xml b/demo/plugin1/src/main/assembly/assembly.xml new file mode 100644 index 0000000..3fdc464 --- /dev/null +++ b/demo/plugin1/src/main/assembly/assembly.xml @@ -0,0 +1,37 @@ +<!-- + Describes the plugin archive + + @author Decebal Suiu + @version 1.0 +--> +<assembly> + <id>plugin</id> + <formats> + <format>zip</format> + </formats> + <includeBaseDirectory>false</includeBaseDirectory> + <dependencySets> + <dependencySet> + <useProjectArtifact>false</useProjectArtifact> + <scope>runtime</scope> + <outputDirectory>lib</outputDirectory> + <includes> + <include>*:jar:*</include> + </includes> + </dependencySet> + </dependencySets> + <!-- + <fileSets> + <fileSet> + <directory>target/classes</directory> + <outputDirectory>classes</outputDirectory> + </fileSet> + </fileSets> + --> + <fileSets> + <fileSet> + <directory>target/plugin-classes</directory> + <outputDirectory>classes</outputDirectory> + </fileSet> + </fileSets> +</assembly> diff --git a/demo/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java b/demo/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java new file mode 100644 index 0000000..22ebf7d --- /dev/null +++ b/demo/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.demo.welcome; + +import org.pf4j.Extension; +import org.pf4j.Plugin; +import org.pf4j.PluginWrapper; +import org.pf4j.demo.api.Greeting; + +/** + * @author Decebal Suiu + */ +public class WelcomePlugin extends Plugin { + + public WelcomePlugin(PluginWrapper wrapper) { + super(wrapper); + } + + public void start() { + System.out.println("WelcomePlugin.start()"); + } + + public void stop() { + System.out.println("WelcomePlugin.stop()"); + } + + @Extension + public static class WelcomeGreeting implements Greeting { + + public String getGreeting() { + return "Welcome"; + } + + } + +} diff --git a/demo/plugin2/plugin.properties b/demo/plugin2/plugin.properties new file mode 100644 index 0000000..60b6f33 --- /dev/null +++ b/demo/plugin2/plugin.properties @@ -0,0 +1,5 @@ +plugin.id=hello-plugin +plugin.class=org.pf4j.demo.hello.HelloPlugin +plugin.version=0.0.1 +plugin.provider=Decebal Suiu +plugin.dependencies= diff --git a/demo/plugin2/pom.xml b/demo/plugin2/pom.xml new file mode 100644 index 0000000..7f4f617 --- /dev/null +++ b/demo/plugin2/pom.xml @@ -0,0 +1,136 @@ +<?xml version="1.0"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.pf4j.demo</groupId> + <artifactId>pom</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + <artifactId>pf4j-demo-plugin2</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Demo Plugin #2</name> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>properties-maven-plugin</artifactId> + <version>1.0-alpha-2</version> + <executions> + <execution> + <phase>initialize</phase> + <goals> + <goal>read-project-properties</goal> + </goals> + <configuration> + <files> + <file>plugin.properties</file> + </files> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <version>1.6</version> + <executions> + <execution> + <id>unzip jar file</id> + <phase>package</phase> + <configuration> + <target> + <unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" /> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <version>2.3</version> + <configuration> + <appendAssemblyId>false</appendAssemblyId> + <descriptors> + <descriptor> + src/main/assembly/assembly.xml + </descriptor> + </descriptors> + <archive> + <manifestEntries> + <Plugin-Id>${plugin.id}</Plugin-Id> + <Plugin-Class>${plugin.class}</Plugin-Class> + <Plugin-Version>${plugin.version}</Plugin-Version> + <Plugin-Provider>${plugin.provider}</Plugin-Provider> + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> + </manifestEntries> + </archive> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestEntries> + <Plugin-Id>${plugin.id}</Plugin-Id> + <Plugin-Class>${plugin.class}</Plugin-Class> + <Plugin-Version>${plugin.version}</Plugin-Version> + <Plugin-Provider>${plugin.provider}</Plugin-Provider> + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> + </manifestEntries> + </archive> + </configuration> + </plugin> + + <plugin> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.pf4j</groupId> + <artifactId>pf4j</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.pf4j.demo</groupId> + <artifactId>pf4j-demo-api</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> + +</project> diff --git a/demo/plugin2/src/main/assembly/assembly.xml b/demo/plugin2/src/main/assembly/assembly.xml new file mode 100644 index 0000000..5cefe0d --- /dev/null +++ b/demo/plugin2/src/main/assembly/assembly.xml @@ -0,0 +1,37 @@ +<!-- + Describes the plugin archive + + @author Decebal Suiu + @version 1.0 +--> +<assembly> + <id>plugin</id> + <formats> + <format>zip</format> + </formats> + <includeBaseDirectory>false</includeBaseDirectory> + <dependencySets> + <dependencySet> + <useProjectArtifact>false</useProjectArtifact> + <scope>runtime</scope> + <outputDirectory>lib</outputDirectory> + <includes> + <include>*:jar:*</include> + </includes> + </dependencySet> + </dependencySets> + <!-- + <fileSets> + <fileSet> + <directory>target/classes</directory> + <outputDirectory>classes</outputDirectory> + </fileSet> + </fileSets> + --> + <fileSets> + <fileSet> + <directory>target/plugin-classes</directory> + <outputDirectory>classes</outputDirectory> + </fileSet> + </fileSets> +</assembly> diff --git a/demo/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java b/demo/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java new file mode 100644 index 0000000..ad7fd0c --- /dev/null +++ b/demo/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.demo.hello; + +import org.pf4j.Extension; +import org.pf4j.Plugin; +import org.pf4j.PluginWrapper; +import org.pf4j.demo.api.Greeting; + +/** + * A very simple plugin. + * + * @author Decebal Suiu + */ +public class HelloPlugin extends Plugin { + + public HelloPlugin(PluginWrapper wrapper) { + super(wrapper); + } + + public void start() { + System.out.println("HelloPlugin.start()"); + } + + public void stop() { + System.out.println("HelloPlugin.stop()"); + } + + @Extension + public static class HelloGreeting implements Greeting { + + public String getGreeting() { + return "Hello"; + } + + } + +} diff --git a/demo/pom.xml b/demo/pom.xml new file mode 100644 index 0000000..dbb029a --- /dev/null +++ b/demo/pom.xml @@ -0,0 +1,60 @@ +<?xml version="1.0"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.pf4j</groupId> + <artifactId>pom</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + <groupId>org.pf4j.demo</groupId> + <artifactId>pom</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>pom</packaging> + <name>PF4J Demo</name> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + + <build> + <resources> + <resource> + <filtering>false</filtering> + <directory>src/main/java</directory> + <excludes> + <exclude>**/*.java</exclude> + </excludes> + </resource> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <source>1.6</source> + <target>1.6</target> + <optimize>true</optimize> + </configuration> + </plugin> + </plugins> + </build> + + <modules> + <module>app</module> + <module>api</module> + <module>plugin1</module> + <module>plugin2</module> + </modules> + +</project> diff --git a/pf4j/pom.xml b/pf4j/pom.xml new file mode 100644 index 0000000..920a870 --- /dev/null +++ b/pf4j/pom.xml @@ -0,0 +1,122 @@ +<?xml version="1.0"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.pf4j</groupId> + <artifactId>pom</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + <artifactId>pf4j</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>PF4J Library</name> + <description>Plugin Framework for Java</description> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + + <scm> + <connection>scm:git:https://github.com/decebals/pf4j.git</connection> + <developerConnection>scm:git:https://github.com/decebals/pf4j.git</developerConnection> + <url>git@github.com/decebals/pf4j.git</url> + </scm> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <slf4j.version>1.6.4</slf4j.version> + </properties> + + <build> + <resources> + <resource> + <filtering>false</filtering> + <directory>src/main/java</directory> + <excludes> + <exclude>**/*.java</exclude> + </excludes> + </resource> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <source>1.6</source> + <target>1.6</target> + <optimize>true</optimize> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>2.1.1</version> + <configuration> + <warName>root</warName> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-release-plugin</artifactId> + <version>2.3</version> + <configuration> + <goals>deploy</goals> + <autoVersionSubmodules>true</autoVersionSubmodules> + <tagNameFormat>release-@{project.version}</tagNameFormat> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>2.4</version> + </dependency> + + <dependency> + <groupId>net.java.sezpoz</groupId> + <artifactId>sezpoz</artifactId> + <version>1.9</version> + </dependency> + + <!-- Logs --> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.16</version> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j.version}</version> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jul-to-slf4j</artifactId> + <version>${slf4j.version}</version> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>${slf4j.version}</version> + </dependency> + </dependencies> +</project> diff --git a/pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java b/pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java new file mode 100644 index 0000000..e7a6dc9 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.java.sezpoz.Index; +import net.java.sezpoz.IndexItem; + +/** + * Using Sezpoz(http://sezpoz.java.net/) for extensions discovery. + * + * @author Decebal Suiu + */ +public class DefaultExtensionFinder implements ExtensionFinder { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultExtensionFinder.class); + + private volatile List<IndexItem<Extension, Object>> indices; + private ClassLoader classLoader; + + public DefaultExtensionFinder(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public <T> List<ExtensionWrapper<T>> find(Class<T> type) { + LOG.debug("Find extensions for " + type); + List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>(); + getIndices(); +// System.out.println("indices = "+ indices); + for (IndexItem<Extension, Object> item : indices) { + try { + AnnotatedElement element = item.element(); + Class<?> extensionType = (Class<?>) element; + LOG.debug("Checking extension type " + extensionType); + if (type.isAssignableFrom(extensionType)) { + Object instance = item.instance(); + if (instance != null) { + LOG.debug("Added extension " + extensionType); + result.add(new ExtensionWrapper<T>(type.cast(instance), item.annotation().ordinal())); + } + } + } catch (InstantiationException e) { + LOG.error(e.getMessage(), e); + } + } + + return result; + } + + private List<IndexItem<Extension, Object>> getIndices() { + if (indices == null) { + indices = new ArrayList<IndexItem<Extension, Object>>(); + Iterator<IndexItem<Extension, Object>> it = Index.load(Extension.class, Object.class, classLoader).iterator(); + while (it.hasNext()) { + indices.add(it.next()); + } + } + + return indices; + } + +} diff --git a/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java new file mode 100644 index 0000000..0055e57 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import org.apache.commons.lang.StringUtils; + +/** + * Read the plugin descriptor from the manifest file. + * + * @author Decebal Suiu + */ +public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder { + + @Override + public PluginDescriptor find(File pluginRepository) throws PluginException { + // TODO it's ok with classes/ ? + File manifestFile = new File(pluginRepository, "classes/META-INF/MANIFEST.MF"); + if (!manifestFile.exists()) { + // not found a 'plugin.xml' file for this plugin + throw new PluginException("Cannot find '" + manifestFile + "' file"); + } + + FileInputStream input = null; + try { + input = new FileInputStream(manifestFile); + } catch (FileNotFoundException e) { + // not happening + } + + Manifest manifest = null; + try { + manifest = new Manifest(input); + } catch (IOException e) { + throw new PluginException(e.getMessage(), e); + } finally { + try { + input.close(); + } catch (IOException e) { + throw new PluginException(e.getMessage(), e); + } + } + + PluginDescriptor pluginDescriptor = new PluginDescriptor(); + + // TODO validate !!! + Attributes attrs = manifest.getMainAttributes(); + String id = attrs.getValue("Plugin-Id"); + if (StringUtils.isEmpty(id)) { + throw new PluginException("Plugin-Id cannot be empty"); + } + pluginDescriptor.setPluginId(id); + + String clazz = attrs.getValue("Plugin-Class"); + if (StringUtils.isEmpty(clazz)) { + throw new PluginException("Plugin-Class cannot be empty"); + } + pluginDescriptor.setPluginClass(clazz); + + String version = attrs.getValue("Plugin-Version"); + if (StringUtils.isEmpty(version)) { + throw new PluginException("Plugin-Version cannot be empty"); + } + pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version)); + + String provider = attrs.getValue("Plugin-Provider"); + pluginDescriptor.setProvider(provider); + String dependencies = attrs.getValue("Plugin-Dependencies"); + pluginDescriptor.setDependencies(dependencies); + + return pluginDescriptor; + } + +} diff --git a/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java new file mode 100644 index 0000000..9cb7ccd --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java @@ -0,0 +1,327 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.pf4j.util.DirectoryFilter; +import org.pf4j.util.UberClassLoader; +import org.pf4j.util.Unzip; +import org.pf4j.util.ZipFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Default implementation of the PluginManager interface. + * + * @author Decebal Suiu + */ +public class DefaultPluginManager implements PluginManager { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginManager.class); + + /** + * The plugins repository. + */ + private File pluginsDirectory; + + private ExtensionFinder extensionFinder; + + private PluginDescriptorFinder pluginDescriptorFinder; + + /** + * A map of plugins this manager is responsible for (the key is the 'pluginId'). + */ + private Map<String, Plugin> plugins; + + /** + * A map of plugin class loaders (he key is the 'pluginId'). + */ + private Map<String, PluginClassLoader> pluginClassLoaders; + + /** + * A relation between 'pluginPath' and 'pluginId' + */ + private Map<String, String> pathToIdMap; + + /** + * A list with unresolved plugins (unresolved dependency). + */ + private List<Plugin> unresolvedPlugins; + + /** + * A list with resolved plugins (resolved dependency). + */ + private List<Plugin> resolvedPlugins; + + /** + * A list with disabled plugins. + */ + private List<Plugin> disabledPlugins; + + private UberClassLoader uberClassLoader; + + /** + * Th plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). + */ + public DefaultPluginManager() { + this(new File(System.getProperty("pf4j.pluginsDir", "plugins"))); + } + + /** + * Constructs DefaultPluginManager which the given plugins directory. + * + * @param pluginsDirectory + * the directory to search for plugins + */ + public DefaultPluginManager(File pluginsDirectory) { + this.pluginsDirectory = pluginsDirectory; + plugins = new HashMap<String, Plugin>(); + pluginClassLoaders = new HashMap<String, PluginClassLoader>(); + pathToIdMap = new HashMap<String, String>(); + unresolvedPlugins = new ArrayList<Plugin>(); + resolvedPlugins = new ArrayList<Plugin>(); + disabledPlugins = new ArrayList<Plugin>(); + pluginDescriptorFinder = new DefaultPluginDescriptorFinder(); + uberClassLoader = new UberClassLoader(); + extensionFinder = new DefaultExtensionFinder(uberClassLoader); + } + + /** + * Retrieves all active plugins. + */ + public List<Plugin> getPlugins() { + return new ArrayList<Plugin>(plugins.values()); + } + + public List<Plugin> getResolvedPlugins() { + return resolvedPlugins; + } + + public Plugin getPlugin(String pluginId) { + return plugins.get(pluginId); + } + + public List<Plugin> getUnresolvedPlugins() { + return unresolvedPlugins; + } + + public List<Plugin> getDisabledPlugins() { + return disabledPlugins; + } + + /** + * Start all active plugins. + */ + public void startPlugins() { + List<Plugin> resolvedPlugins = getResolvedPlugins(); + for (Plugin plugin : resolvedPlugins) { + try { + plugin.start(); + } catch (PluginException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + /** + * Stop all active plugins. + */ + public void stopPlugins() { + List<Plugin> resolvedPlugins = getResolvedPlugins(); + for (Plugin plugin : resolvedPlugins) { + try { + plugin.stop(); + } catch (PluginException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + /** + * Load plugins. + */ + public void loadPlugins() { + // check for plugins directory + if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) { + LOG.error("No '" + pluginsDirectory + "' directory"); + return; + } + + // expand all plugin archives + FilenameFilter zipFilter = new ZipFilter(); + String[] zipFiles = pluginsDirectory.list(zipFilter); + for (String zipFile : zipFiles) { + try { + expandPluginArchive(zipFile); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + e.printStackTrace(); + } + } + + // load any plugin from plugins directory + FilenameFilter directoryFilter = new DirectoryFilter(); + String[] directories = pluginsDirectory.list(directoryFilter); + for (String directory : directories) { + try { + loadPlugin(directory); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + e.printStackTrace(); + } + } + + // check for no plugins + if (directories.length == 0) { + LOG.info("No plugins"); + return; + } + + // resolve 'unresolvedPlugins' + resolvePlugins(); + } + + /** + * Get plugin class loader for this path. + */ + public PluginClassLoader getPluginClassLoader(String pluginId) { + return pluginClassLoaders.get(pluginId); + } + + public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type) { + return extensionFinder.find(type); + } + + private void loadPlugin(String fileName) throws Exception { + // test for plugin directory + File pluginDirectory = new File(pluginsDirectory, fileName); + if (!pluginDirectory.isDirectory()) { + return; + } + + // try to load the plugin + String pluginPath = "/".concat(fileName); + + // test for disabled plugin + if (disabledPlugins.contains(pluginPath)) { + return; + } + + // it's a new plugin + if (plugins.get(pathToIdMap.get(pluginPath)) != null) { + return; + } + + // retrieves the plugin descriptor + LOG.debug("Find plugin descriptor '" + pluginPath + "'"); + PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory); + LOG.debug("Descriptor " + pluginDescriptor); + String pluginClassName = pluginDescriptor.getPluginClass(); + LOG.debug("Class '" + pluginClassName + "'" + " for plugin '" + pluginPath + "'"); + + // load plugin + LOG.debug("Loading plugin '" + pluginPath + "'"); + PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor); + PluginLoader pluginLoader = new PluginLoader(this, pluginWrapper, pluginDirectory); + pluginLoader.load(); + LOG.debug("Loaded plugin '" + pluginPath + "'"); + + // set some variables in plugin wrapper + pluginWrapper.setPluginPath(pluginPath); + pluginWrapper.setPluginClassLoader(pluginLoader.getPluginClassLoader()); + + // create the plugin instance + LOG.debug("Creating instance for plugin '" + pluginPath + "'"); + Plugin plugin = getPluginInstance(pluginWrapper, pluginLoader); + LOG.debug("Created instance '" + plugin + "' for plugin '" + pluginPath + "'"); + + String pluginId = pluginDescriptor.getPluginId(); + + // add plugin to the list with plugins + plugins.put(pluginId, plugin); + unresolvedPlugins.add(plugin); + + // add plugin class loader to the list with class loaders + PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader(); + pluginDescriptor.setPluginClassLoader(pluginClassLoader); + pluginClassLoaders.put(pluginId, pluginClassLoader); + } + + private void expandPluginArchive(String fileName) throws IOException { + File pluginArchiveFile = new File(pluginsDirectory, fileName); + long pluginArchiveDate = pluginArchiveFile.lastModified(); + String pluginName = fileName.substring(0, fileName.length() - 4); + File pluginDirectory = new File(pluginsDirectory, pluginName); + // check if exists directory or the '.zip' file is "newer" than directory + if (!pluginDirectory.exists() || pluginArchiveDate > pluginDirectory.lastModified()) { + LOG.debug("Expand plugin archive '" + pluginArchiveFile + "' in '" + pluginDirectory + "'"); + // create directorie for plugin + pluginDirectory.mkdirs(); + + // expand '.zip' file + Unzip unzip = new Unzip(); + unzip.setSource(pluginArchiveFile); + unzip.setDestination(pluginDirectory); + unzip.extract(); + } + } + + private Plugin getPluginInstance(PluginWrapper pluginWrapper, PluginLoader pluginLoader) + throws Exception { + String pluginClassName = pluginWrapper.getDescriptor().getPluginClass(); + + ClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader(); + Class<?> pluginClass = pluginClassLoader.loadClass(pluginClassName); + + // once we have the class, we can do some checks on it to ensure + // that it is a valid implementation of a plugin. + int modifiers = pluginClass.getModifiers(); + if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers) + || (!Plugin.class.isAssignableFrom(pluginClass))) { + throw new PluginException("The plugin class '" + pluginClassName + + "' is not compatible."); + } + + // create the plugin instance + Constructor<?> constructor = pluginClass.getConstructor(new Class[] { PluginWrapper.class }); + Plugin plugin = (Plugin) constructor.newInstance(new Object[] { pluginWrapper }); + + return plugin; + } + + private void resolvePlugins() { + resolveDependencies(); + } + + private void resolveDependencies() { + DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins); + resolvedPlugins = dependencyResolver.getSortedDependencies(); + for (Plugin plugin : resolvedPlugins) { + unresolvedPlugins.remove(plugin); + uberClassLoader.addLoader(plugin.getWrapper().getPluginClassLoader()); + } + } + +} diff --git a/pf4j/src/main/java/org/pf4j/DependencyResolver.java b/pf4j/src/main/java/org/pf4j/DependencyResolver.java new file mode 100644 index 0000000..2f5f601 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/DependencyResolver.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.util.ArrayList; +import java.util.List; + +import org.pf4j.util.DirectedGraph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * @author Decebal Suiu + */ +class DependencyResolver { + + private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class); + + private List<Plugin> plugins; + + public DependencyResolver(List<Plugin> plugins) { + this.plugins = plugins; + } + + /** + * Get the list of plugins in dependency sorted order. + */ + public List<Plugin> getSortedDependencies() { + DirectedGraph<String> graph = new DirectedGraph<String>(); + for (Plugin plugin : plugins) { + PluginDescriptor descriptor = plugin.getWrapper().getDescriptor(); + String pluginId = descriptor.getPluginId(); + List<String> dependencies = descriptor.getDependencies(); + if (!dependencies.isEmpty()) { + for (String dependency : dependencies) { + graph.addEdge(pluginId, dependency); + } + } else { + graph.addVertex(pluginId); + } + } + + LOG.debug("Graph: " + graph); + List<String> pluginsId = graph.reverseTopologicalSort(); + + if (pluginsId == null) { + LOG.error("Cyclic dependences !!!"); + return null; + } + + LOG.debug("Plugins order: " + pluginsId); + List<Plugin> sortedPlugins = new ArrayList<Plugin>(); + for (String pluginId : pluginsId) { + sortedPlugins.add(getPlugin(pluginId)); + } + + return sortedPlugins; + } + + private Plugin getPlugin(String pluginId) { + for (Plugin plugin : plugins) { + if (pluginId.equals(plugin.getWrapper().getDescriptor().getPluginId())) { + return plugin; + } + } + + return null; + } + +} diff --git a/pf4j/src/main/java/org/pf4j/Extension.java b/pf4j/src/main/java/org/pf4j/Extension.java new file mode 100644 index 0000000..6c499b6 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/Extension.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import net.java.sezpoz.Indexable; + +/** + * @author Decebal Suiu + */ +@Indexable +@Retention(RUNTIME) +@Target(TYPE) +@Documented +public @interface Extension { + + int ordinal() default 0; + +} diff --git a/pf4j/src/main/java/org/pf4j/ExtensionFinder.java b/pf4j/src/main/java/org/pf4j/ExtensionFinder.java new file mode 100644 index 0000000..5fc466d --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/ExtensionFinder.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.util.List; + +/** + * @author Decebal Suiu + */ +public interface ExtensionFinder { + + public <T> List<ExtensionWrapper<T>> find(Class<T> type); + +} diff --git a/pf4j/src/main/java/org/pf4j/ExtensionPoint.java b/pf4j/src/main/java/org/pf4j/ExtensionPoint.java new file mode 100644 index 0000000..9113c1d --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/ExtensionPoint.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +/** + * @author Decebal Suiu + */ +public interface ExtensionPoint { + +} diff --git a/pf4j/src/main/java/org/pf4j/ExtensionWrapper.java b/pf4j/src/main/java/org/pf4j/ExtensionWrapper.java new file mode 100644 index 0000000..9ba320e --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/ExtensionWrapper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +/** + * @author Decebal Suiu + */ +public class ExtensionWrapper<T> implements Comparable<ExtensionWrapper<T>> { + + private final T instance; + private final int ordinal; + + public ExtensionWrapper(T instance, int ordinal) { + this.instance = instance; + this.ordinal = ordinal; + } + + public T getInstance() { + return instance; + } + + public int getOrdinal() { + return ordinal; + } + + @Override + public int compareTo(ExtensionWrapper<T> o) { + return (ordinal - o.getOrdinal()); + } + +}
\ No newline at end of file diff --git a/pf4j/src/main/java/org/pf4j/Plugin.java b/pf4j/src/main/java/org/pf4j/Plugin.java new file mode 100644 index 0000000..bab55c1 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/Plugin.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class will be extended by all plugins and + * serve as the common class between a plugin and the application. + * + * @author Decebal Suiu + */ +public abstract class Plugin { + + /** + * Makes logging service available for descending classes. + */ + protected final Logger log = LoggerFactory.getLogger(getClass()); + + /** + * Wrapper of the plugin. + */ + PluginWrapper wrapper; + + /** + * Constructor to be used by plugin manager for plugin instantiation. + * Your plugins have to provide constructor with this exact signature to + * be successfully loaded by manager. + */ + public Plugin(final PluginWrapper wrapper) { + if (wrapper == null) { + throw new IllegalArgumentException("Wrapper cannot be null"); + } + + this.wrapper = wrapper; + } + + /** + * Retrieves the wrapper of this plug-in. + */ + public final PluginWrapper getWrapper() { + return wrapper; + } + + /** + * Start method is called by the application when the plugin is loaded. + */ + public void start() throws PluginException { + } + + /** + * Stop method is called by the application when the plugin is unloaded. + */ + public void stop() throws PluginException { + } + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java new file mode 100644 index 0000000..884642f --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; + +/** + * One instance of this class should be created by plugin manager for every available plug-in. + * + * @author Decebal Suiu + */ +class PluginClassLoader extends URLClassLoader { + + private static final String JAVA_PACKAGE_PREFIX = "java."; + private static final String JAVAX_PACKAGE_PREFIX = "javax."; + private static final String PLUGIN_PACKAGE_PREFIX = "org.pf4j."; + + private PluginManager pluginManager; + private PluginWrapper pluginWrapper; + + public PluginClassLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, ClassLoader parent) { + super(new URL[0], parent); + + this.pluginManager = pluginManager; + this.pluginWrapper = pluginWrapper; + } + + @Override + public void addURL(URL url) { + super.addURL(url); + } + + @Override + public Class<?> loadClass(String className) throws ClassNotFoundException { +// System.out.println(">>>" + className); + + // first check whether it's a system class, delegate to the system loader + if (className.startsWith(JAVA_PACKAGE_PREFIX) || className.startsWith(JAVAX_PACKAGE_PREFIX)) { + return findSystemClass(className); + } + + // second check whether it's already been loaded + Class<?> loadedClass = findLoadedClass(className); + if (loadedClass != null) { + return loadedClass; + } + + // nope, try to load locally + try { + return findClass(className); + } catch (ClassNotFoundException e) { + // try next step + } + + // if the class it's a part of the plugin engine use parent class loader + if (className.startsWith(PLUGIN_PACKAGE_PREFIX)) { + try { + return PluginClassLoader.class.getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + // try next step + } + } + + // look in dependencies + List<String> dependencies = pluginWrapper.getDescriptor().getDependencies(); + for (String dependency : dependencies) { + PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency); + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException e) { + // try next dependency + } + } + + // use the standard URLClassLoader (which follows normal parent delegation) + return super.loadClass(className); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginDescriptor.java b/pf4j/src/main/java/org/pf4j/PluginDescriptor.java new file mode 100644 index 0000000..8346e48 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginDescriptor.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; + +/** + * A plugin descriptor contains information about a plug-in obtained + * from the manifest (META-INF) file. + * + * @author Decebal Suiu + */ +class PluginDescriptor { + + private String pluginId; + private String pluginClass; + private PluginVersion version; + private String provider; + private String pluginPath; + private List<String> dependencies; + private PluginClassLoader pluginClassLoader; + + public PluginDescriptor() { + dependencies = new ArrayList<String>(); + } + + /** + * Returns the unique identifier of this plugin. + */ + public String getPluginId() { + return pluginId; + } + + /** + * Returns the name of the class that implements Plugin interface. + */ + public String getPluginClass() { + return pluginClass; + } + + /** + * Returns the version of this plugin. + */ + public PluginVersion getVersion() { + return version; + } + + /** + * Returns the provider name of this plugin. + */ + public String getProvider() { + return provider; + } + + /** + * Returns the path of this plugin relative to plugins directory. + */ + public String getPluginPath() { + return pluginPath; + } + + /** + * Returns all dependencies declared by this plugin. + * Returns an empty array if this plugin does not declare any require. + */ + public List<String> getDependencies() { + return dependencies; + } + + /** + * Returns the plugin class loader used to load classes and resources + * for this plug-in. The class loader can be used to directly access + * plug-in resources and classes. + */ + public PluginClassLoader getPluginClassLoader() { + return pluginClassLoader; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("pluginId", pluginId) + .append("pluginClass", pluginClass) + .append("version", version) + .append("provider", provider) + .append("pluginPath", pluginPath) + .append("dependencies", dependencies) + .toString(); + } + + void setPluginId(String pluginId) { + this.pluginId = pluginId; + } + + void setPluginClass(String pluginClassName) { + this.pluginClass = pluginClassName; + } + + void setPluginVersion(PluginVersion version) { + this.version = version; + } + + void setProvider(String provider) { + this.provider = provider; + } + + void setPluginPath(String pluginPath) { + this.pluginPath = pluginPath; + } + + void setDependencies(String dependencies) { + if (dependencies != null) { + this.dependencies = Arrays.asList(StringUtils.split(dependencies, ',')); + } else { + this.dependencies = Collections.emptyList(); + } + } + + void setPluginClassLoader(PluginClassLoader pluginClassLoader) { + this.pluginClassLoader = pluginClassLoader; + } + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java b/pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java new file mode 100644 index 0000000..c59ec65 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.io.File; + +/** + * Find a plugin descriptor in a directory (plugin repository). + * You can find in manifest file @see DefaultPluginDescriptorFinder, + * xml file, properties file, java services (with ServiceLoader), etc. + * + * @author Decebal Suiu + */ +public interface PluginDescriptorFinder { + + public PluginDescriptor find(File pluginRepository) throws PluginException; + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginException.java b/pf4j/src/main/java/org/pf4j/PluginException.java new file mode 100644 index 0000000..c341c70 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +/** + * An exception used to indicate that a plugin problem occurred. + * + * @author Decebal Suiu + */ +class PluginException extends Exception { + + private static final long serialVersionUID = 1L; + + public PluginException() { + super(); + } + + public PluginException(String message) { + super(message); + } + + public PluginException(Throwable cause) { + super(cause); + } + + public PluginException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginLoader.java b/pf4j/src/main/java/org/pf4j/PluginLoader.java new file mode 100644 index 0000000..bd64d59 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginLoader.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.MalformedURLException; +import java.util.Vector; + +import org.pf4j.util.DirectoryFilter; +import org.pf4j.util.JarFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Load all informations needed by a plugin. + * This means add all jar files from 'lib' directory, 'classes' + * to classpath. + * It's a class for only the internal use. + * + * @author Decebal Suiu + */ +class PluginLoader { + + private static final Logger LOG = LoggerFactory.getLogger(PluginLoader.class); + + /* + * The plugin repository. + */ + private File pluginRepository; + + /* + * The directory with '.class' files. + */ + private File classesDirectory; + + /* + * The directory with '.jar' files. + */ + private File libDirectory; + + private PluginClassLoader pluginClassLoader; + + public PluginLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, File pluginRepository) { + this.pluginRepository = pluginRepository; + classesDirectory = new File(pluginRepository, "classes"); + libDirectory = new File(pluginRepository, "lib"); + ClassLoader parent = getClass().getClassLoader(); + pluginClassLoader = new PluginClassLoader(pluginManager, pluginWrapper, parent); + LOG.debug("Created class loader " + pluginClassLoader); + } + + public File getPluginRepository() { + return pluginRepository; + } + + public boolean load() { + return loadClassesAndJars(); + } + + public PluginClassLoader getPluginClassLoader() { + return pluginClassLoader; + } + + private boolean loadClassesAndJars() { + return loadClasses() && loadJars(); + } + + private void getJars(Vector<String> v, File file) { + FilenameFilter jarFilter = new JarFilter(); + FilenameFilter directoryFilter = new DirectoryFilter(); + + if (file.exists() && file.isDirectory() && file.isAbsolute()) { + String[] jars = file.list(jarFilter); + for (int i = 0; (jars != null) && (i < jars.length); ++i) { + v.addElement(jars[i]); + } + + String[] directoryList = file.list(directoryFilter); + for (int i = 0; (directoryList != null) && (i < directoryList.length); ++i) { + File directory = new File(file, directoryList[i]); + getJars(v, directory); + } + } + } + + private boolean loadClasses() { + // make 'classesDirectory' absolute + classesDirectory = classesDirectory.getAbsoluteFile(); + + if (classesDirectory.exists() && classesDirectory.isDirectory()) { + LOG.debug("Found '" + classesDirectory.getPath() + "' directory"); + + try { + pluginClassLoader.addURL(classesDirectory.toURI().toURL()); + LOG.debug("Added '" + classesDirectory + "' to the class loader path"); + } catch (MalformedURLException e) { + e.printStackTrace(); + LOG.error(e.getMessage(), e); + return false; + } + } + + return true; + } + + /** + * Add all *.jar files from '/lib' directory. + */ + private boolean loadJars() { + // make 'jarDirectory' absolute + libDirectory = libDirectory.getAbsoluteFile(); + + Vector<String> jars = new Vector<String>(); + getJars(jars, libDirectory); + for (String jar : jars) { + File jarFile = new File(libDirectory, jar); + try { + pluginClassLoader.addURL(jarFile.toURI().toURL()); + LOG.debug("Added '" + jarFile + "' to the class loader path"); + } catch (MalformedURLException e) { + e.printStackTrace(); + LOG.error(e.getMessage(), e); + return false; + } + } + + return true; + } + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginManager.java b/pf4j/src/main/java/org/pf4j/PluginManager.java new file mode 100644 index 0000000..cf0cff1 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginManager.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.util.List; + +/** + * Provides the functionality for plugin management such as load, + * start and stop the plugins. + * + * @author Decebal Suiu + */ +public interface PluginManager { + + /** + * Retrieves all plugins. + */ + public List<Plugin> getPlugins(); + + /** + * Load plugins. + */ + public void loadPlugins(); + + /** + * Start all active plugins. + */ + public void startPlugins(); + + /** + * Stop all active plugins. + */ + public void stopPlugins(); + + public PluginClassLoader getPluginClassLoader(String pluginId); + + public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type); + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginVersion.java b/pf4j/src/main/java/org/pf4j/PluginVersion.java new file mode 100644 index 0000000..66267c9 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginVersion.java @@ -0,0 +1,191 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Represents the version of a Plugin and allows versions to be compared. + * Version identifiers have five components. + * + * 1. Major version. A non-negative integer. + * 2. Minor version. A non-negative integer. + * 3. Release version. A non-negative integer. + * 4. Build version. A non-negative integer. + * 5. Qualifier. A text string. + * + * This class is immutable. + * + * @author Decebal Suiu + */ +public class PluginVersion implements Comparable<PluginVersion> { + + private int major; + private int minor; + private int release; + private int build; + private String qualifier; + + private PluginVersion() { + } + + public PluginVersion(int major, int minor, int release) { + this.major = major; + this.minor = minor; + this.release = release; + } + + public PluginVersion(int major, int minor, int release, int build) { + this.major = major; + this.minor = minor; + this.release = release; + this.build = build; + } + + public PluginVersion(int major, int minor, int release, int build, String qualifier) { + this.major = major; + this.minor = minor; + this.release = release; + this.build = build; + this.qualifier = qualifier; + } + + public static PluginVersion createVersion(String version) { + if (version == null) { + return new PluginVersion(); + } + + PluginVersion v = new PluginVersion(); + + StringTokenizer st = new StringTokenizer(version, "."); + List<String> tmp = new ArrayList<String>(); + for (int i = 0; st.hasMoreTokens() && i < 4; i++) { + tmp.add(st.nextToken()); + } + + int n = tmp.size(); + switch (n) { + case 0 : + break; + case 1 : + v.major = Integer.parseInt(tmp.get(0)); + break; + case 2 : + v.major = Integer.parseInt(tmp.get(0)); + v.minor = Integer.parseInt(tmp.get(1)); + break; + case 3 : + v.major = Integer.parseInt(tmp.get(0)); + v.minor = Integer.parseInt(tmp.get(1)); + v.release = Integer.parseInt(tmp.get(2)); + break; + case 4 : + v.major = Integer.parseInt(tmp.get(0)); + v.minor = Integer.parseInt(tmp.get(1)); + v.release = Integer.parseInt(tmp.get(2)); + v.build = Integer.parseInt(tmp.get(3)); + break; + } + + return v; + } + + public int getMajor() { + return this.major; + } + + public int getMinor() { + return this.minor; + } + + public int getRelease() { + return this.release; + } + + public int getBuild() { + return this.build; + } + + public String getQualifier() { + return qualifier; + } + + public String toString() { + StringBuffer sb = new StringBuffer(50); + sb.append(major); + sb.append('.'); + sb.append(minor); + sb.append('.'); + sb.append(release); + sb.append('.'); + sb.append(build); + if (qualifier != null) { + sb.append(qualifier); + } + + return sb.toString(); + } + + public int compareTo(PluginVersion version) { + if (version.major > major) { + return 1; + } else if (version.major < major) { + return -1; + } + + if (version.minor > minor) { + return 1; + } else if (version.minor < minor) { + return -1; + } + + if (version.release > release) { + return 1; + } else if (version.release < release) { + return -1; + } + + if (version.build > build) { + return 1; + } else if (version.build < build) { + return -1; + } + + return 0; + } + + /* + private String extractQualifier(String token) { + StringTokenizer st = new StringTokenizer(token, "-"); + if (st.countTokens() == 2) { + return st. + } + } + */ + + // for test only + public static void main(String[] args) { + PluginVersion v = PluginVersion.createVersion("4.0.0.123"); + System.out.println(v.toString()); +// v = PluginVersion.createVersion("4.0.0.123-alpha"); +// System.out.println(v.toString()); + PluginVersion v1 = PluginVersion.createVersion("4.1.0"); + System.out.println(v1.toString()); + PluginVersion v2 = PluginVersion.createVersion("4.0.32"); + System.out.println(v2.toString()); + System.out.println(v1.compareTo(v2)); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/PluginWrapper.java b/pf4j/src/main/java/org/pf4j/PluginWrapper.java new file mode 100644 index 0000000..46a0da1 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/PluginWrapper.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j; + +/** + * A wrapper over plugin instance. + * + * @author Decebal Suiu + */ +public class PluginWrapper { + + PluginDescriptor descriptor; + String pluginPath; + PluginClassLoader pluginClassLoader; + + public PluginWrapper(PluginDescriptor descriptor) { + this.descriptor = descriptor; + } + + /** + * Returns the plugin descriptor. + */ + public PluginDescriptor getDescriptor() { + return descriptor; + } + + /** + * Returns the path of this plugin relative to plugins directory. + */ + public String getPluginPath() { + return pluginPath; + } + + /** + * Returns the plugin class loader used to load classes and resources + * for this plug-in. The class loader can be used to directly access + * plug-in resources and classes. + */ + public PluginClassLoader getPluginClassLoader() { + return pluginClassLoader; + } + + void setPluginPath(String pluginPath) { + this.pluginPath = pluginPath; + } + + void setPluginClassLoader(PluginClassLoader pluginClassLoader) { + this.pluginClassLoader = pluginClassLoader; + } + +} diff --git a/pf4j/src/main/java/org/pf4j/util/DirectedGraph.java b/pf4j/src/main/java/org/pf4j/util/DirectedGraph.java new file mode 100644 index 0000000..a5fa829 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/util/DirectedGraph.java @@ -0,0 +1,171 @@ +/*
+ * Copyright 2012 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
+ * the License. You may obtain a copy of the License in the LICENSE file, or at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.pf4j.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * @author Decebal Suiu
+ */
+public class DirectedGraph<V> {
+
+ /**
+ * The implementation here is basically an adjacency list, but instead
+ * of an array of lists, a Map is used to map each vertex to its list of
+ * adjacent vertices.
+ */
+ private Map<V, List<V>> neighbors = new HashMap<V, List<V>>();
+
+ /**
+ * Add a vertex to the graph. Nothing happens if vertex is already in graph.
+ */
+ public void addVertex(V vertex) {
+ if (neighbors.containsKey(vertex)) {
+ return;
+ }
+ neighbors.put(vertex, new ArrayList<V>());
+ }
+
+ /**
+ * True if graph contains vertex.
+ */
+ public boolean containsVertex(V vertex) {
+ return neighbors.containsKey(vertex);
+ }
+
+ /**
+ * Add an edge to the graph; if either vertex does not exist, it's added.
+ * This implementation allows the creation of multi-edges and self-loops.
+ */
+ public void addEdge(V from, V to) {
+ this.addVertex(from);
+ this.addVertex(to);
+ neighbors.get(from).add(to);
+ }
+
+ /**
+ * Remove an edge from the graph. Nothing happens if no such edge.
+ * @throws IllegalArgumentException if either vertex doesn't exist.
+ */
+ public void remove(V from, V to) {
+ if (!(this.containsVertex(from) && this.containsVertex(to))) {
+ throw new IllegalArgumentException("Nonexistent vertex");
+ }
+ neighbors.get(from).remove(to);
+ }
+
+ /**
+ * Report (as a Map) the out-degree of each vertex.
+ */
+ public Map<V, Integer> outDegree() {
+ Map<V, Integer> result = new HashMap<V, Integer>();
+ for (V vertex : neighbors.keySet()) {
+ result.put(vertex, neighbors.get(vertex).size());
+ }
+
+ return result;
+ }
+
+ /**
+ * Report (as a Map) the in-degree of each vertex.
+ */
+ public Map<V,Integer> inDegree() {
+ Map<V, Integer> result = new HashMap<V, Integer>();
+ for (V vertex : neighbors.keySet()) {
+ result.put(vertex, 0); // all in-degrees are 0
+ }
+ for (V from : neighbors.keySet()) {
+ for (V to : neighbors.get(from)) {
+ result.put(to, result.get(to) + 1); // increment in-degree
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Report (as a List) the topological sort of the vertices; null for no such sort.
+ */
+ public List<V> topologicalSort() {
+ Map<V, Integer> degree = inDegree();
+
+ // determine all vertices with zero in-degree
+ Stack<V> zeroVertices = new Stack<V>(); // stack as good as any here
+ for (V v : degree.keySet()) {
+ if (degree.get(v) == 0) {
+ zeroVertices.push(v);
+ }
+ }
+
+ // determine the topological order
+ List<V> result = new ArrayList<V>();
+ while (!zeroVertices.isEmpty()) {
+ V vertex = zeroVertices.pop(); // choose a vertex with zero in-degree
+ result.add(vertex); // vertex 'v' is next in topological order
+ // "remove" vertex 'v' by updating its neighbors
+ for (V neighbor : neighbors.get(vertex)) {
+ degree.put(neighbor, degree.get(neighbor) - 1);
+ // remember any vertices that now have zero in-degree
+ if (degree.get(neighbor) == 0) {
+ zeroVertices.push(neighbor);
+ }
+ }
+ }
+
+ // check that we have used the entire graph (if not, there was a cycle)
+ if (result.size() != neighbors.size()) {
+ return null;
+ }
+
+ return result;
+ }
+
+ /**
+ * Report (as a List) the reverse topological sort of the vertices; null for no such sort.
+ */
+ public List<V> reverseTopologicalSort() {
+ List<V> list = topologicalSort();
+ if (list == null) {
+ return null;
+ }
+ Collections.reverse(list);
+
+ return list;
+ }
+
+ /**
+ * True if graph is a dag (directed acyclic graph).
+ */
+ public boolean isDag () {
+ return topologicalSort() != null;
+ }
+
+ /**
+ * String representation of graph.
+ */
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ for (V vertex : neighbors.keySet()) {
+ sb.append("\n " + vertex + " -> " + neighbors.get(vertex));
+ }
+
+ return sb.toString();
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java b/pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java new file mode 100644 index 0000000..c6a236b --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.util; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; + +/** + * @author Decebal Suiu + */ +public class DirectoryFilter implements FileFilter, FilenameFilter { + + /** + * Accepts any file ending in .jar. The case of the filename is ignored. + */ + public boolean accept(File file) { + return file.isDirectory(); + } + + public boolean accept(File dir, String name) { + return accept(new File(dir, name)); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java b/pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java new file mode 100644 index 0000000..1252a4e --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.util; + +import java.io.File; +import java.io.FilenameFilter; + +/** + * @author Decebal Suiu + */ +public class ExtensionFilter implements FilenameFilter { + + private String extension; + + public ExtensionFilter(String extension) { + this.extension = extension; + } + + /** + * Accepts any file ending in extension. The case of the filename is ignored. + */ + public boolean accept(File file) { + // perform a case insensitive check. + return file.getName().toUpperCase().endsWith(extension.toUpperCase()); + } + + public boolean accept(File dir, String name) { + return accept(new File(dir, name)); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/util/JarFilter.java b/pf4j/src/main/java/org/pf4j/util/JarFilter.java new file mode 100644 index 0000000..09c322c --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/util/JarFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.util; + +/** + * File filter that accepts all files ending with .JAR. + * This filter is case insensitive. + * + * @author Decebal Suiu + */ +public class JarFilter extends ExtensionFilter { + + /** + * The extension that this filter will search for. + */ + private static final String JAR_EXTENSION = ".JAR"; + + public JarFilter() { + super(JAR_EXTENSION); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/util/UberClassLoader.java b/pf4j/src/main/java/org/pf4j/util/UberClassLoader.java new file mode 100644 index 0000000..4ecfe2c --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/util/UberClassLoader.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.util; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A class loader that has multiple loaders and uses them for loading classes and resources. + * + * @author Decebal Suiu + */ +public class UberClassLoader extends ClassLoader { + + private Set<ClassLoader> loaders = new HashSet<ClassLoader>(); + + public void addLoader(ClassLoader loader) { + loaders.add(loader); + } + + @Override + public Class<?> findClass(String name) throws ClassNotFoundException { + for (ClassLoader loader : loaders) { + try { + return loader.loadClass(name); + } catch (ClassNotFoundException e) { + // try next + } + } + + throw new ClassNotFoundException(name); + } + + @Override + public URL findResource(String name) { + for (ClassLoader loader : loaders) { + URL url = loader.getResource(name); + if (url != null) { + return url; + } + } + + return null; + } + + @Override + protected Enumeration<URL> findResources(String name) throws IOException { + List<URL> resources = new ArrayList<URL>(); + for (ClassLoader loader : loaders) { + resources.addAll(Collections.list(loader.getResources(name))); + } + + return Collections.enumeration(resources); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/util/Unzip.java b/pf4j/src/main/java/org/pf4j/util/Unzip.java new file mode 100644 index 0000000..d5e6a04 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/util/Unzip.java @@ -0,0 +1,122 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class extracts the containt of the plugin archive into a directory. + * It's a class for only the internal use. + * + * @author Decebal Suiu + */ +public class Unzip { + + private static final Logger LOG = LoggerFactory.getLogger(Unzip.class); + + /** + * Holds the destination directory. + * File will be unzipped into the destination directory. + */ + private File destination; + + /** + * Holds path to zip file. + */ + private File source; + + public Unzip() { + } + + public Unzip(File source, File destination) { + this.source = source; + this.destination = destination; + } + + public void setSource(File source) { + this.source = source; + } + + public void setDestination(File destination) { + this.destination = destination; + } + + public void extract() throws IOException { + LOG.debug("Extract content of " + source + " to " + destination); + + // delete destination file if exists + removeDirectory(destination); + + ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source)); + ZipEntry zipEntry = null; + + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + try { + File file = new File(destination, zipEntry.getName()); + + // create intermediary directories - sometimes zip don't add them + File dir = new File(file.getParent()); + dir.mkdirs(); + + if (zipEntry.isDirectory()) { + file.mkdirs(); + } else { + byte[] buffer = new byte[1024]; + int length = 0; + FileOutputStream fos = new FileOutputStream(file); + + while ((length = zipInputStream.read(buffer)) >= 0) { + fos.write(buffer, 0, length); + } + + fos.close(); + } + } catch (FileNotFoundException e) { + LOG.error("File '" + zipEntry.getName() + "' not found"); + } + } + + zipInputStream.close(); + } + + private boolean removeDirectory(File directory) { + if (!directory.exists()) { + return true; + } + + if (!directory.isDirectory()) { + return false; + } + + File[] files = directory.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + removeDirectory(file); + } else { + file.delete(); + } + } + + return directory.delete(); + } + +} diff --git a/pf4j/src/main/java/org/pf4j/util/ZipFilter.java b/pf4j/src/main/java/org/pf4j/util/ZipFilter.java new file mode 100644 index 0000000..4884e90 --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/util/ZipFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.pf4j.util; + +/** + * File filter that accepts all files ending with .ZIP. + * This filter is case insensitive. + * + * @author Decebal Suiu + */ +public class ZipFilter extends ExtensionFilter { + + /** + * The extension that this filter will search for. + */ + private static final String ZIP_EXTENSION = ".ZIP"; + + public ZipFilter() { + super(ZIP_EXTENSION); + } + +} @@ -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> diff --git a/run-demo.sh b/run-demo.sh new file mode 100755 index 0000000..bea8ba2 --- /dev/null +++ b/run-demo.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# +# This script creates and run the pf4j demo. +# + +# create artifacts using maven +mvn clean package + +# create demo-dist folder +rm -fr demo-dist +mkdir demo-dist +mkdir demo-dist/plugins + +# copy artifacts to demo-dist folder +cp -r demo/app/target/pf4j-demo-*/* demo-dist/ +cp demo/plugin1/target/pf4j-demo-plugin1-*.zip demo-dist/plugins/ +cp demo/plugin2/target/pf4j-demo-plugin2-*.zip demo-dist/plugins/ + +# run demo +cd demo-dist +java -jar pf4j-demo-app-*.jar +cd - + |