diff options
author | Decebal Suiu <decebal.suiu@gmail.com> | 2013-10-03 21:41:37 +0300 |
---|---|---|
committer | Decebal Suiu <decebal.suiu@gmail.com> | 2013-10-03 21:41:37 +0300 |
commit | c6a1bbda46fcd99ae48fbb8589e0138a67bf3e1d (patch) | |
tree | 742680ce2ddb1fb2a84060703814172de5f3a4d6 | |
parent | c18773b3ed9735f475844e2abf2a253412c46fc0 (diff) | |
parent | cd327605d0fa4a251a867413ffcd376e3823e872 (diff) | |
download | pf4j-c6a1bbda46fcd99ae48fbb8589e0138a67bf3e1d.tar.gz pf4j-c6a1bbda46fcd99ae48fbb8589e0138a67bf3e1d.zip |
Merge branch 'master' of https://github.com/decebals/pf4j
-rw-r--r-- | README.md | 36 | ||||
-rw-r--r-- | demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java | 212 | ||||
-rw-r--r-- | demo/plugins/plugin1/pom.xml | 254 | ||||
-rw-r--r-- | demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java | 10 | ||||
-rw-r--r-- | demo/plugins/plugin2/pom.xml | 246 | ||||
-rw-r--r-- | demo/plugins/plugin2/src/main/java/ro/fortsoft/pf4j/demo/hello/HelloPlugin.java | 3 | ||||
-rw-r--r-- | demo/plugins/pom.xml | 143 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java | 852 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java | 36 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java | 13 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java | 5 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java | 9 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/RuntimeMode.java | 55 |
13 files changed, 1025 insertions, 849 deletions
@@ -61,8 +61,8 @@ It's very simple to add pf4j in your application: In above code, I created a **DefaultPluginManager** (it's the default implementation for **PluginManager** interface) that loads and starts all active(resolved) plugins. -Each available plugin is loaded using a **PluginClassLoader**. -The **PluginClassLoader** contains only classes found in _classes_ and _lib_ folders of plugin and runtime classes and libraries of the required plugins. +Each available plugin is loaded using a different java class loader, **PluginClassLoader**. +The **PluginClassLoader** contains only classes found in **PluginClasspath** (default _classes_ and _lib_ folders) of plugin and runtime classes and libraries of the required/dependent plugins. The plugins are stored in a folder. You can specify the plugins folder in the constructor of DefaultPluginManager. If the plugins folder is not specified than the location is returned by `System.getProperty("pf4j.pluginsDir", "plugins")`. @@ -71,12 +71,12 @@ The structure of plugins folder is: * plugin2.zip (or plugin2 folder) In plugins folder you can put a plugin as folder or archive file (zip). -A plugin folder has this structure: +A plugin folder has this structure by default: * `classes` folder * `lib` folder (optional - if the plugin used third party libraries) The plugin manager searches plugins metadata using a **PluginDescriptorFinder**. -**DefaultPluginDescriptorFinder** lookups plugins descriptors in MANIFEST.MF file. +**DefaultPluginDescriptorFinder** is a "link" to **ManifestPluginDescriptorFinder** that lookups plugins descriptors in MANIFEST.MF file. In this case the `classes/META-INF/MANIFEST.MF` file looks like: Manifest-Version: 1.0 @@ -102,7 +102,7 @@ You can define an extension point in your application using **ExtensionPoint** i } Another important internal component is **ExtensionFinder** that describes how plugin manager discovers extensions for extensions points. -**DefaultExtensionFinder** looks up extensions using **Extension** annotation. +**DefaultExtensionFinder** is a "link" to **SezpozExtensionFinder** that looks up extensions using **Extension** annotation. public class WelcomePlugin extends Plugin { @@ -135,7 +135,7 @@ The output is: >>> Welcome >>> Hello -You can inject your custom component (for example PluginDescriptorFinder, ExtensionFinder) in DefaultPluginManager just override createXXX methods (factory method pattern). +You can inject your custom component (for example PluginDescriptorFinder, ExtensionFinder, PluginClasspath, ...) in DefaultPluginManager just override createXXX methods (factory method pattern). Example: @@ -154,6 +154,26 @@ and in plugin respository you must have a plugin.properties file with the below For more information please see the demo sources. +Development runtime mode +-------------------------- +PF4J can run in two modes: **DEVELOPMENT** and **DEPLOYMENT**. +The DEPLOYMENT(default) mode is the standard workflow for plugins creation: create a new maven module for each plugin, codding the plugin (declares new extension points and/or +add new extensions), pack the plugin in a zip file, deploy the zip file to plugins folder. These operations are time consuming and from this reason I introduced the DEVELOPMENT runtime mode. +The main advantage of DEVELOPMENT runtime mode for a plugin developer is that he/she is not enforced to pack and deploy the plugins. In DEVELOPMENT mode you can developing plugins in a simple and fast mode. + +Lets describe how DEVELOPMENT runtime mode works. + +First, you can change the runtime mode using the "pf4j.mode" system property or overriding `DefaultPluginManager.getRuntimeMode()`. +For example I run the pf4j demo in eclipse in DEVELOPMENT mode adding only `"-Dpf4j.mode=development"` to the pf4j demo launcher. +You can retrieve the current runtime mode using `PluginManager.getRuntimeMode()` or in your Plugin implementation with `getWrapper().getRuntimeMode()`(see [WelcomePlugin](https://github.com/decebals/pf4j/blob/master/demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java)). +The DefaultPluginManager determines automatically the correct runtime mode and for DEVELOPMENT mode overrides some components(pluginsDirectory is __"../plugins"__, __PropertiesPluginDescriptorFinder__ as PluginDescriptorFinder, __DevelopmentPluginClasspath__ as PluginClassPath). +Another advantage of DEVELOPMENT runtime mode is that you can execute some code lines only in this mode (for example more debug messages). + +If you use maven as build manger, after each dependency modification in you plugin (maven module) you must run Maven>Update Project... + + +For more details see the demo application. + Enable/Disable plugins ------------------- In theory, it's a relation **1:N** between an extension point and the extensions for this extension point. @@ -191,8 +211,8 @@ If a file with enabled.txt exists than disabled.txt is ignored. See enabled.txt Demo ------------------- I have a tiny demo application. The demo application is in demo folder. -In demo/api folder I declared an extension point (_Greeting_). -In demo/plugin* I implemented two plugins: plugin1, plugin2 (each plugin adds an extension for _Greeting_). +In demo/api folder I declared an extension point ( _Greeting_). +In demo/plugins I implemented two plugins: plugin1, plugin2 (each plugin adds an extension for _Greeting_). To run the demo application use: diff --git a/demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java b/demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java index 7681a75..8dde182 100644 --- a/demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java +++ b/demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java @@ -1,145 +1,67 @@ -/* - * Copyright 2012 Decebal Suiu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with - * the License. You may obtain a copy of the License in the LICENSE file, or at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package ro.fortsoft.pf4j.demo; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; - -import org.apache.commons.lang.StringUtils; - -import ro.fortsoft.pf4j.DefaultPluginManager; -import ro.fortsoft.pf4j.PluginClasspath; -import ro.fortsoft.pf4j.PluginDescriptorFinder; -import ro.fortsoft.pf4j.PluginManager; -import ro.fortsoft.pf4j.PropertiesPluginDescriptorFinder; -import ro.fortsoft.pf4j.demo.api.Greeting; - -/** - * A boot class that start the demo. - * - * @author Decebal Suiu - */ -public class Boot { - - public static void main(String[] args) { - // print logo - printLogo(); - - // create the plugin manager - final PluginManager pluginManager = createPluginManager(); - - // load and start (active/resolved) the plugins - pluginManager.loadPlugins(); - pluginManager.startPlugins(); - - // retrieves the extensions for Greeting extension point - List<Greeting> greetings = pluginManager.getExtensions(Greeting.class); - for (Greeting greeting : greetings) { - System.out.println(">>> " + greeting.getGreeting()); - } - - // stop the plugins - pluginManager.stopPlugins(); - /* - Runtime.getRuntime().addShutdownHook(new Thread() { - - @Override - public void run() { - pluginManager.stopPlugins(); - } - - }); - */ - } - - private static PluginManager createPluginManager() { - // retrieves the pf4j runtime mode - String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.PROD.toString()); - RuntimeMode mode = RuntimeMode.byName(modeAsString); - - System.out.println("PF4J runtime mode: '" + mode + "'"); - - // create the plugin manager depending on runtime mode - PluginManager pluginManager = null; - if (mode == RuntimeMode.PROD) { - pluginManager = new DefaultPluginManager(); - } else if (mode == RuntimeMode.DEV) { - // run from eclipse IDE (for example) - pluginManager = new DefaultPluginManager(new File("../plugins")) { - - @Override - protected PluginClasspath createPluginClasspath() { - PluginClasspath pluginClasspath = super.createPluginClasspath(); - // modify plugin classes - List<String> pluginClasses = pluginClasspath.getClassesDirectories(); - pluginClasses.clear(); - pluginClasses.add("target/classes"); - - return pluginClasspath; - } - - @Override - protected PluginDescriptorFinder createPluginDescriptorFinder() { - return new PropertiesPluginDescriptorFinder(); - } - - }; - } - - return pluginManager; - } - - private static void printLogo() { - System.out.println(StringUtils.repeat("#", 40)); - System.out.println(StringUtils.center("PF4J-DEMO", 40)); - System.out.println(StringUtils.repeat("#", 40)); - } - - public enum RuntimeMode { - - DEV("dev"), // development - PROD("prod"); // production - - private final String name; - - private static final Map<String, RuntimeMode> map = new HashMap<String, RuntimeMode>(); - - static { - for (RuntimeMode mode : RuntimeMode.values()) { - map.put(mode.name, mode); - } - } - - private RuntimeMode(final String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - - public static RuntimeMode byName(String name) { - if (map.containsKey(name)) { - return map.get(name); - } - - throw new NoSuchElementException("Cannot found PF4J runtime mode with name '" + name + "'"); - } - - } - -} +/*
+ * Copyright 2012 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
+ * the License. You may obtain a copy of the License in the LICENSE file, or at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package ro.fortsoft.pf4j.demo;
+
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+import ro.fortsoft.pf4j.DefaultPluginManager;
+import ro.fortsoft.pf4j.PluginManager;
+import ro.fortsoft.pf4j.demo.api.Greeting;
+
+/**
+ * A boot class that start the demo.
+ *
+ * @author Decebal Suiu
+ */
+public class Boot {
+
+ public static void main(String[] args) {
+ // print logo
+ printLogo();
+
+ // create the plugin manager
+ final PluginManager pluginManager = new DefaultPluginManager();
+
+ // load and start (active/resolved) the plugins
+ pluginManager.loadPlugins();
+ pluginManager.startPlugins();
+
+ // retrieves the extensions for Greeting extension point
+ List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
+ for (Greeting greeting : greetings) {
+ System.out.println(">>> " + greeting.getGreeting());
+ }
+
+ // stop the plugins
+ pluginManager.stopPlugins();
+ /*
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+
+ @Override
+ public void run() {
+ pluginManager.stopPlugins();
+ }
+
+ });
+ */
+ }
+
+ private static void printLogo() {
+ System.out.println(StringUtils.repeat("#", 40));
+ System.out.println(StringUtils.center("PF4J-DEMO", 40));
+ System.out.println(StringUtils.repeat("#", 40));
+ }
+
+}
diff --git a/demo/plugins/plugin1/pom.xml b/demo/plugins/plugin1/pom.xml index 8fe5f7b..bf8abe5 100644 --- a/demo/plugins/plugin1/pom.xml +++ b/demo/plugins/plugin1/pom.xml @@ -1,131 +1,123 @@ -<?xml version="1.0"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - - <parent> - <groupId>ro.fortsoft.pf4j.demo</groupId> - <artifactId>pf4j-demo-plugins</artifactId> - <version>0.6-SNAPSHOT</version> - </parent> - - <modelVersion>4.0.0</modelVersion> - <artifactId>pf4j-demo-plugin1</artifactId> - <version>0.6-SNAPSHOT</version> - <packaging>jar</packaging> - <name>Demo Plugin #1</name> - - <properties> - <plugin.id>welcome-plugin</plugin.id> - <plugin.class>ro.fortsoft.pf4j.demo.welcome.WelcomePlugin</plugin.class> - <plugin.version>0.0.1</plugin.version> - <plugin.provider>Decebal Suiu</plugin.provider> - <plugin.dependencies /> - </properties> - - <build> - <plugins> - <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section) - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>properties-maven-plugin</artifactId> - <version>1.0-alpha-2</version> - <executions> - <execution> - <phase>initialize</phase> - <goals> - <goal>read-project-properties</goal> - </goals> - <configuration> - <files> - <file>plugin.properties</file> - </files> - </configuration> - </execution> - </executions> - </plugin> - --> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-antrun-plugin</artifactId> - <version>1.6</version> - <executions> - <execution> - <id>unzip jar file</id> - <phase>package</phase> - <configuration> - <target> - <unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" /> - </target> - </configuration> - <goals> - <goal>run</goal> - </goals> - </execution> - </executions> - </plugin> - - <plugin> - <artifactId>maven-assembly-plugin</artifactId> - <version>2.3</version> - <configuration> - <descriptors> - <descriptor> - src/main/assembly/assembly.xml - </descriptor> - </descriptors> - <appendAssemblyId>false</appendAssemblyId> - </configuration> - <executions> - <execution> - <id>make-assembly</id> - <phase>package</phase> - <goals> - <goal>attached</goal> - </goals> - </execution> - </executions> - </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <version>2.4</version> - <configuration> - <archive> - <manifestEntries> - <Plugin-Id>${plugin.id}</Plugin-Id> - <Plugin-Class>${plugin.class}</Plugin-Class> - <Plugin-Version>${plugin.version}</Plugin-Version> - <Plugin-Provider>${plugin.provider}</Plugin-Provider> - <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> - </manifestEntries> - </archive> - </configuration> - </plugin> - - <plugin> - <artifactId>maven-deploy-plugin</artifactId> - <configuration> - <skip>true</skip> - </configuration> - </plugin> - </plugins> - </build> - - <dependencies> - <dependency> - <groupId>ro.fortsoft.pf4j</groupId> - <artifactId>pf4j</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> - - <dependency> - <groupId>ro.fortsoft.pf4j.demo</groupId> - <artifactId>pf4j-demo-api</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> - </dependencies> - -</project> +<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <groupId>ro.fortsoft.pf4j.demo</groupId>
+ <artifactId>pf4j-demo-plugins</artifactId>
+ <version>0.6-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>pf4j-demo-plugin1</artifactId>
+ <version>0.6-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <name>Demo Plugin #1</name>
+
+ <properties>
+ <plugin.id>welcome-plugin</plugin.id>
+ <plugin.class>ro.fortsoft.pf4j.demo.welcome.WelcomePlugin</plugin.class>
+ <plugin.version>0.0.1</plugin.version>
+ <plugin.provider>Decebal Suiu</plugin.provider>
+ <plugin.dependencies />
+ </properties>
+
+ <build>
+ <plugins>
+ <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>properties-maven-plugin</artifactId>
+ <version>1.0-alpha-2</version>
+ <executions>
+ <execution>
+ <phase>initialize</phase>
+ <goals>
+ <goal>read-project-properties</goal>
+ </goals>
+ <configuration>
+ <files>
+ <file>plugin.properties</file>
+ </files>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ -->
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <id>unzip jar file</id>
+ <phase>package</phase>
+ <configuration>
+ <target>
+ <unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" />
+ </target>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.3</version>
+ <configuration>
+ <descriptors>
+ <descriptor>
+ src/main/assembly/assembly.xml
+ </descriptor>
+ </descriptors>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attached</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Plugin-Id>${plugin.id}</Plugin-Id>
+ <Plugin-Class>${plugin.class}</Plugin-Class>
+ <Plugin-Version>${plugin.version}</Plugin-Version>
+ <Plugin-Provider>${plugin.provider}</Plugin-Provider>
+ <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.6</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java b/demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java index d10d3dd..1c35259 100644 --- a/demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java +++ b/demo/plugins/plugin1/src/main/java/ro/fortsoft/pf4j/demo/welcome/WelcomePlugin.java @@ -12,9 +12,12 @@ */ package ro.fortsoft.pf4j.demo.welcome; +import org.apache.commons.lang.StringUtils; + import ro.fortsoft.pf4j.Extension; import ro.fortsoft.pf4j.Plugin; import ro.fortsoft.pf4j.PluginWrapper; +import ro.fortsoft.pf4j.RuntimeMode; import ro.fortsoft.pf4j.demo.api.Greeting; /** @@ -26,10 +29,16 @@ public class WelcomePlugin extends Plugin { super(wrapper); } + @Override public void start() { System.out.println("WelcomePlugin.start()"); + // for testing the development mode + if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) { + System.out.println(StringUtils.upperCase("WelcomePlugin")); + } } + @Override public void stop() { System.out.println("WelcomePlugin.stop()"); } @@ -37,6 +46,7 @@ public class WelcomePlugin extends Plugin { @Extension public static class WelcomeGreeting implements Greeting { + @Override public String getGreeting() { return "Welcome"; } diff --git a/demo/plugins/plugin2/pom.xml b/demo/plugins/plugin2/pom.xml index 2bf0dd8..4b84e9f 100644 --- a/demo/plugins/plugin2/pom.xml +++ b/demo/plugins/plugin2/pom.xml @@ -1,131 +1,115 @@ -<?xml version="1.0"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - - <parent> - <groupId>ro.fortsoft.pf4j.demo</groupId> - <artifactId>pf4j-demo-plugins</artifactId> - <version>0.6-SNAPSHOT</version> - </parent> - - <modelVersion>4.0.0</modelVersion> - <artifactId>pf4j-demo-plugin2</artifactId> - <version>0.6-SNAPSHOT</version> - <packaging>jar</packaging> - <name>Demo Plugin #2</name> - - <properties> - <plugin.id>hello-plugin</plugin.id> - <plugin.class>ro.fortsoft.pf4j.demo.hello.HelloPlugin</plugin.class> - <plugin.version>0.0.1</plugin.version> - <plugin.provider>Decebal Suiu</plugin.provider> - <plugin.dependencies /> - </properties> - - <build> - <plugins> - <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section) - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>properties-maven-plugin</artifactId> - <version>1.0-alpha-2</version> - <executions> - <execution> - <phase>initialize</phase> - <goals> - <goal>read-project-properties</goal> - </goals> - <configuration> - <files> - <file>plugin.properties</file> - </files> - </configuration> - </execution> - </executions> - </plugin> - --> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-antrun-plugin</artifactId> - <version>1.6</version> - <executions> - <execution> - <id>unzip jar file</id> - <phase>package</phase> - <configuration> - <target> - <unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" /> - </target> - </configuration> - <goals> - <goal>run</goal> - </goals> - </execution> - </executions> - </plugin> - - <plugin> - <artifactId>maven-assembly-plugin</artifactId> - <version>2.3</version> - <configuration> - <descriptors> - <descriptor> - src/main/assembly/assembly.xml - </descriptor> - </descriptors> - <appendAssemblyId>false</appendAssemblyId> - </configuration> - <executions> - <execution> - <id>make-assembly</id> - <phase>package</phase> - <goals> - <goal>attached</goal> - </goals> - </execution> - </executions> - </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <version>2.4</version> - <configuration> - <archive> - <manifestEntries> - <Plugin-Id>${plugin.id}</Plugin-Id> - <Plugin-Class>${plugin.class}</Plugin-Class> - <Plugin-Version>${plugin.version}</Plugin-Version> - <Plugin-Provider>${plugin.provider}</Plugin-Provider> - <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> - </manifestEntries> - </archive> - </configuration> - </plugin> - - <plugin> - <artifactId>maven-deploy-plugin</artifactId> - <configuration> - <skip>true</skip> - </configuration> - </plugin> - </plugins> - </build> - - <dependencies> - <dependency> - <groupId>ro.fortsoft.pf4j</groupId> - <artifactId>pf4j</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> - - <dependency> - <groupId>ro.fortsoft.pf4j.demo</groupId> - <artifactId>pf4j-demo-api</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> - </dependencies> - -</project> +<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <groupId>ro.fortsoft.pf4j.demo</groupId>
+ <artifactId>pf4j-demo-plugins</artifactId>
+ <version>0.6-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>pf4j-demo-plugin2</artifactId>
+ <version>0.6-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <name>Demo Plugin #2</name>
+
+ <properties>
+ <plugin.id>hello-plugin</plugin.id>
+ <plugin.class>ro.fortsoft.pf4j.demo.hello.HelloPlugin</plugin.class>
+ <plugin.version>0.0.1</plugin.version>
+ <plugin.provider>Decebal Suiu</plugin.provider>
+ <plugin.dependencies />
+ </properties>
+
+ <build>
+ <plugins>
+ <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>properties-maven-plugin</artifactId>
+ <version>1.0-alpha-2</version>
+ <executions>
+ <execution>
+ <phase>initialize</phase>
+ <goals>
+ <goal>read-project-properties</goal>
+ </goals>
+ <configuration>
+ <files>
+ <file>plugin.properties</file>
+ </files>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ -->
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <id>unzip jar file</id>
+ <phase>package</phase>
+ <configuration>
+ <target>
+ <unzip src="target/${artifactId}-${version}.${packaging}" dest="target/plugin-classes" />
+ </target>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.3</version>
+ <configuration>
+ <descriptors>
+ <descriptor>
+ src/main/assembly/assembly.xml
+ </descriptor>
+ </descriptors>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attached</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Plugin-Id>${plugin.id}</Plugin-Id>
+ <Plugin-Class>${plugin.class}</Plugin-Class>
+ <Plugin-Version>${plugin.version}</Plugin-Version>
+ <Plugin-Provider>${plugin.provider}</Plugin-Provider>
+ <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/demo/plugins/plugin2/src/main/java/ro/fortsoft/pf4j/demo/hello/HelloPlugin.java b/demo/plugins/plugin2/src/main/java/ro/fortsoft/pf4j/demo/hello/HelloPlugin.java index 8f12e23..d8963fc 100644 --- a/demo/plugins/plugin2/src/main/java/ro/fortsoft/pf4j/demo/hello/HelloPlugin.java +++ b/demo/plugins/plugin2/src/main/java/ro/fortsoft/pf4j/demo/hello/HelloPlugin.java @@ -28,10 +28,12 @@ public class HelloPlugin extends Plugin { super(wrapper); } + @Override public void start() { System.out.println("HelloPlugin.start()"); } + @Override public void stop() { System.out.println("HelloPlugin.stop()"); } @@ -39,6 +41,7 @@ public class HelloPlugin extends Plugin { @Extension public static class HelloGreeting implements Greeting { + @Override public String getGreeting() { return "Hello"; } diff --git a/demo/plugins/pom.xml b/demo/plugins/pom.xml index d451b31..05f6c27 100644 --- a/demo/plugins/pom.xml +++ b/demo/plugins/pom.xml @@ -1,37 +1,106 @@ -<?xml version="1.0"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - - <parent> - <groupId>ro.fortsoft.pf4j.demo</groupId> - <artifactId>pf4j-demo-parent</artifactId> - <version>0.6-SNAPSHOT</version> - </parent> - - <modelVersion>4.0.0</modelVersion> - <groupId>ro.fortsoft.pf4j.demo</groupId> - <artifactId>pf4j-demo-plugins</artifactId> - <version>0.6-SNAPSHOT</version> - <packaging>pom</packaging> - <name>Demo Plugins Parent</name> - - <build> - <resources> - <resource> - <filtering>false</filtering> - <directory>src/main/java</directory> - <excludes> - <exclude>**/*.java</exclude> - </excludes> - </resource> - <resource> - <directory>src/main/resources</directory> - </resource> - </resources> - </build> - - <modules> - <module>plugin1</module> - <module>plugin2</module> - </modules> - -</project> +<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <groupId>ro.fortsoft.pf4j.demo</groupId>
+ <artifactId>pf4j-demo-parent</artifactId>
+ <version>0.6-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>ro.fortsoft.pf4j.demo</groupId>
+ <artifactId>pf4j-demo-plugins</artifactId>
+ <version>0.6-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Demo Plugins Parent</name>
+
+ <build>
+ <resources>
+ <resource>
+ <filtering>false</filtering>
+ <directory>src/main/java</directory>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </resource>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ </resources>
+
+ <plugins>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>process-sources</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/lib</outputDirectory>
+ <excludeScope>provided</excludeScope>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <versionRange>[2.0,)</versionRange>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <!--
+ <execute/>
+ -->
+ <execute>
+ <runOnIncremental>true</runOnIncremental>
+ <runOnConfiguration>true</runOnConfiguration>
+ </execute>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <modules>
+ <module>plugin1</module>
+ <module>plugin2</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>ro.fortsoft.pf4j</groupId>
+ <artifactId>pf4j</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>ro.fortsoft.pf4j.demo</groupId>
+ <artifactId>pf4j-demo-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java index 0c2628d..30658cd 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java @@ -1,392 +1,460 @@ -/* - * Copyright 2012 Decebal Suiu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with - * the License. You may obtain a copy of the License in the LICENSE file, or at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package ro.fortsoft.pf4j; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ro.fortsoft.pf4j.util.AndFileFilter; -import ro.fortsoft.pf4j.util.CompoundClassLoader; -import ro.fortsoft.pf4j.util.DirectoryFileFilter; -import ro.fortsoft.pf4j.util.FileUtils; -import ro.fortsoft.pf4j.util.HiddenFilter; -import ro.fortsoft.pf4j.util.NotFileFilter; -import ro.fortsoft.pf4j.util.Unzip; -import ro.fortsoft.pf4j.util.ZipFileFilter; - -/** - * Default implementation of the PluginManager interface. - * - * @author Decebal Suiu - */ -public class DefaultPluginManager implements PluginManager { - - private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class); - - /** - * The plugins repository. - */ - private File pluginsDirectory; - - private final ExtensionFinder extensionFinder; - - private final PluginDescriptorFinder pluginDescriptorFinder; - - private final PluginClasspath pluginClasspath; - - /** - * A map of plugins this manager is responsible for (the key is the 'pluginId'). - */ - private Map<String, PluginWrapper> plugins; - - /** - * A map of plugin class loaders (he key is the 'pluginId'). - */ - private Map<String, PluginClassLoader> pluginClassLoaders; - - /** - * A relation between 'pluginPath' and 'pluginId' - */ - private Map<String, String> pathToIdMap; - - /** - * A list with unresolved plugins (unresolved dependency). - */ - private List<PluginWrapper> unresolvedPlugins; - - /** - * A list with resolved plugins (resolved dependency). - */ - private List<PluginWrapper> resolvedPlugins; - - /** - * A list with started plugins. - */ - private List<PluginWrapper> startedPlugins; - - private List<String> enabledPlugins; - private List<String> disabledPlugins; - - /** - * A compound class loader of resolved plugins. - */ - protected CompoundClassLoader compoundClassLoader; - - /** - * Th plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). - */ - public DefaultPluginManager() { - this(new File(System.getProperty("pf4j.pluginsDir", "plugins"))); - } - - /** - * Constructs DefaultPluginManager which the given plugins directory. - * - * @param pluginsDirectory - * the directory to search for plugins - */ - public DefaultPluginManager(File pluginsDirectory) { - this.pluginsDirectory = pluginsDirectory; - - plugins = new HashMap<String, PluginWrapper>(); - pluginClassLoaders = new HashMap<String, PluginClassLoader>(); - pathToIdMap = new HashMap<String, String>(); - unresolvedPlugins = new ArrayList<PluginWrapper>(); - resolvedPlugins = new ArrayList<PluginWrapper>(); - startedPlugins = new ArrayList<PluginWrapper>(); - disabledPlugins = new ArrayList<String>(); - compoundClassLoader = new CompoundClassLoader(); - - pluginClasspath = createPluginClasspath(); - pluginDescriptorFinder = createPluginDescriptorFinder(); - extensionFinder = createExtensionFinder(); - - try { - // create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file) - enabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "enabled.txt"), true); - log.info("Enabled plugins: {}", enabledPlugins); - - // create a list with plugin identifiers that should not be accepted by this manager (blacklist from plugins/disabled.txt file) - disabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "disabled.txt"), true); - log.info("Disabled plugins: {}", disabledPlugins); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - - System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath()); - } - - @Override - public List<PluginWrapper> getPlugins() { - return new ArrayList<PluginWrapper>(plugins.values()); - } - - @Override - public List<PluginWrapper> getResolvedPlugins() { - return resolvedPlugins; - } - - public PluginWrapper getPlugin(String pluginId) { - return plugins.get(pluginId); - } - - @Override - public List<PluginWrapper> getUnresolvedPlugins() { - return unresolvedPlugins; - } - - @Override - public List<PluginWrapper> getStartedPlugins() { - return startedPlugins; - } - - /** - * Start all active plugins. - */ - @Override - public void startPlugins() { - for (PluginWrapper pluginWrapper : resolvedPlugins) { - try { - log.info("Start plugin '{}'", pluginWrapper.getDescriptor().getPluginId()); - pluginWrapper.getPlugin().start(); - pluginWrapper.setPluginState(PluginState.STARTED); - startedPlugins.add(pluginWrapper); - } catch (PluginException e) { - log.error(e.getMessage(), e); - } - } - } - - /** - * Stop all active plugins. - */ - @Override - public void stopPlugins() { - // stop started plugins in reverse order - Collections.reverse(startedPlugins); - for (PluginWrapper pluginWrapper : startedPlugins) { - try { - log.info("Stop plugin '{}'", pluginWrapper.getDescriptor().getPluginId()); - pluginWrapper.getPlugin().stop(); - pluginWrapper.setPluginState(PluginState.STOPPED); - } catch (PluginException e) { - log.error(e.getMessage(), e); - } - } - } - - /** - * Load plugins. - */ - @Override - public void loadPlugins() { - log.debug("Lookup plugins in '{}'", pluginsDirectory.getAbsolutePath()); - // check for plugins directory - if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) { - log.error("No '{}' directory", pluginsDirectory.getAbsolutePath()); - return; - } - - // expand all plugin archives - FileFilter zipFilter = new ZipFileFilter(); - File[] zipFiles = pluginsDirectory.listFiles(zipFilter); - for (File zipFile : zipFiles) { - try { - expandPluginArchive(zipFile); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - - // check for no plugins - List<FileFilter> filterList = new ArrayList<FileFilter>(); - filterList.add(new DirectoryFileFilter()); - filterList.add(new NotFileFilter(createHiddenPluginFilter())); - FileFilter pluginsFilter = new AndFileFilter(filterList); - File[] directories = pluginsDirectory.listFiles(pluginsFilter); - if (directories.length == 0) { - log.info("No plugins"); - return; - } - - // load any plugin from plugins directory - for (File directory : directories) { - try { - loadPlugin(directory); - } catch (PluginException e) { - log.error(e.getMessage(), e); - } - } - - // resolve 'unresolvedPlugins' - try { - resolvePlugins(); - } catch (PluginException e) { - log.error(e.getMessage(), e); - } - } - - /** - * Get plugin class loader for this path. - */ - @Override - public PluginClassLoader getPluginClassLoader(String pluginId) { - return pluginClassLoaders.get(pluginId); - } - - @Override - public <T> List<T> getExtensions(Class<T> type) { - List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type); - List<T> extensions = new ArrayList<T>(extensionsWrapper.size()); - for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) { - extensions.add(extensionWrapper.getInstance()); - } - - return extensions; - } - - /** - * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'. - */ - public PluginWrapper whichPlugin(Class<?> clazz) { - ClassLoader classLoader = clazz.getClassLoader(); - for (PluginWrapper plugin : resolvedPlugins) { - if (plugin.getPluginClassLoader() == classLoader) { - return plugin; - } - } - - return null; - } - - /** - * Add the possibility to override the PluginDescriptorFinder. - */ - protected PluginDescriptorFinder createPluginDescriptorFinder() { - return new DefaultPluginDescriptorFinder(pluginClasspath); - } - - /** - * Add the possibility to override the ExtensionFinder. - */ - protected ExtensionFinder createExtensionFinder() { - return new DefaultExtensionFinder(compoundClassLoader); - } - - /** - * Add the possibility to override the PluginClassPath. - */ - protected PluginClasspath createPluginClasspath() { - return new PluginClasspath(); - } - - protected boolean isPluginDisabled(String pluginId) { - if (enabledPlugins.isEmpty()) { - return disabledPlugins.contains(pluginId); - } - - return !enabledPlugins.contains(pluginId); - } - - protected FileFilter createHiddenPluginFilter() { - return new HiddenFilter(); - } - - private void loadPlugin(File pluginDirectory) throws PluginException { - // try to load the plugin - String pluginName = pluginDirectory.getName(); - String pluginPath = "/".concat(pluginName); - - // test for plugin duplication - if (plugins.get(pathToIdMap.get(pluginPath)) != null) { - return; - } - - // retrieves the plugin descriptor - log.debug("Find plugin descriptor '{}'", pluginPath); - PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory); - log.debug("Descriptor " + pluginDescriptor); - String pluginClassName = pluginDescriptor.getPluginClass(); - log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath); - - // test for disabled plugin - if (isPluginDisabled(pluginDescriptor.getPluginId())) { - log.info("Plugin '{}' is disabled", pluginPath); - return; - } - - // load plugin - log.debug("Loading plugin '{}'", pluginPath); - PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath); - pluginLoader.load(); - log.debug("Loaded plugin '{}'", pluginPath); - - // create the plugin wrapper - log.debug("Creating wrapper for plugin '{}'", pluginPath); - PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader()); - log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath); - - String pluginId = pluginDescriptor.getPluginId(); - - // add plugin to the list with plugins - plugins.put(pluginId, pluginWrapper); - unresolvedPlugins.add(pluginWrapper); - - // add plugin class loader to the list with class loaders - PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader(); - pluginClassLoaders.put(pluginId, pluginClassLoader); - } - - private void expandPluginArchive(File pluginArchiveFile) throws IOException { - String fileName = pluginArchiveFile.getName(); - long pluginArchiveDate = pluginArchiveFile.lastModified(); - String pluginName = fileName.substring(0, fileName.length() - 4); - File pluginDirectory = new File(pluginsDirectory, pluginName); - // check if exists directory or the '.zip' file is "newer" than directory - if (!pluginDirectory.exists() || (pluginArchiveDate > pluginDirectory.lastModified())) { - log.debug("Expand plugin archive '{}' in '{}'", pluginArchiveFile, pluginDirectory); - // create directory for plugin - pluginDirectory.mkdirs(); - - // expand '.zip' file - Unzip unzip = new Unzip(); - unzip.setSource(pluginArchiveFile); - unzip.setDestination(pluginDirectory); - unzip.extract(); - } - } - - private void resolvePlugins() throws PluginException { - resolveDependencies(); - } - - private void resolveDependencies() throws PluginException { - DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins); - resolvedPlugins = dependencyResolver.getSortedPlugins(); - for (PluginWrapper pluginWrapper : resolvedPlugins) { - unresolvedPlugins.remove(pluginWrapper); - compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader()); - log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId()); - } - } - -} +/*
+ * Copyright 2012 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
+ * the License. You may obtain a copy of the License in the LICENSE file, or at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package ro.fortsoft.pf4j;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ro.fortsoft.pf4j.util.AndFileFilter;
+import ro.fortsoft.pf4j.util.CompoundClassLoader;
+import ro.fortsoft.pf4j.util.DirectoryFileFilter;
+import ro.fortsoft.pf4j.util.FileUtils;
+import ro.fortsoft.pf4j.util.HiddenFilter;
+import ro.fortsoft.pf4j.util.NotFileFilter;
+import ro.fortsoft.pf4j.util.Unzip;
+import ro.fortsoft.pf4j.util.ZipFileFilter;
+
+/**
+ * Default implementation of the PluginManager interface.
+ *
+ * @author Decebal Suiu
+ */
+public class DefaultPluginManager implements PluginManager {
+
+ private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
+
+ public static final String DEFAULT_PLUGINS_DIRECTORY = "plugins";
+ public static final String DEVELOPMENT_PLUGINS_DIRECTORY = "../plugins";
+
+ /**
+ * The plugins repository.
+ */
+ private File pluginsDirectory;
+
+ private ExtensionFinder extensionFinder;
+
+ private PluginDescriptorFinder pluginDescriptorFinder;
+
+ private PluginClasspath pluginClasspath;
+
+ /**
+ * A map of plugins this manager is responsible for (the key is the 'pluginId').
+ */
+ private Map<String, PluginWrapper> plugins;
+
+ /**
+ * A map of plugin class loaders (he key is the 'pluginId').
+ */
+ private Map<String, PluginClassLoader> pluginClassLoaders;
+
+ /**
+ * A relation between 'pluginPath' and 'pluginId'
+ */
+ private Map<String, String> pathToIdMap;
+
+ /**
+ * A list with unresolved plugins (unresolved dependency).
+ */
+ private List<PluginWrapper> unresolvedPlugins;
+
+ /**
+ * A list with resolved plugins (resolved dependency).
+ */
+ private List<PluginWrapper> resolvedPlugins;
+
+ /**
+ * A list with started plugins.
+ */
+ private List<PluginWrapper> startedPlugins;
+
+ private List<String> enabledPlugins;
+ private List<String> disabledPlugins;
+
+ /**
+ * A compound class loader of resolved plugins.
+ */
+ protected CompoundClassLoader compoundClassLoader;
+
+ /**
+ * Cache value for the runtime mode. No need to re-read it because it wont change at
+ * runtime.
+ */
+ private RuntimeMode runtimeMode;
+
+ /**
+ * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
+ */
+ public DefaultPluginManager() {
+ this.pluginsDirectory = createPluginsDirectory();
+
+ initialize();
+ }
+
+ /**
+ * Constructs DefaultPluginManager which the given plugins directory.
+ *
+ * @param pluginsDirectory
+ * the directory to search for plugins
+ */
+ public DefaultPluginManager(File pluginsDirectory) {
+ this.pluginsDirectory = pluginsDirectory;
+
+ initialize();
+ }
+
+ @Override
+ public List<PluginWrapper> getPlugins() {
+ return new ArrayList<PluginWrapper>(plugins.values());
+ }
+
+ @Override
+ public List<PluginWrapper> getResolvedPlugins() {
+ return resolvedPlugins;
+ }
+
+ public PluginWrapper getPlugin(String pluginId) {
+ return plugins.get(pluginId);
+ }
+
+ @Override
+ public List<PluginWrapper> getUnresolvedPlugins() {
+ return unresolvedPlugins;
+ }
+
+ @Override
+ public List<PluginWrapper> getStartedPlugins() {
+ return startedPlugins;
+ }
+
+ /**
+ * Start all active plugins.
+ */
+ @Override
+ public void startPlugins() {
+ for (PluginWrapper pluginWrapper : resolvedPlugins) {
+ try {
+ log.info("Start plugin '{}'", pluginWrapper.getDescriptor().getPluginId());
+ pluginWrapper.getPlugin().start();
+ pluginWrapper.setPluginState(PluginState.STARTED);
+ startedPlugins.add(pluginWrapper);
+ } catch (PluginException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Stop all active plugins.
+ */
+ @Override
+ public void stopPlugins() {
+ // stop started plugins in reverse order
+ Collections.reverse(startedPlugins);
+ for (PluginWrapper pluginWrapper : startedPlugins) {
+ try {
+ log.info("Stop plugin '{}'", pluginWrapper.getDescriptor().getPluginId());
+ pluginWrapper.getPlugin().stop();
+ pluginWrapper.setPluginState(PluginState.STOPPED);
+ } catch (PluginException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Load plugins.
+ */
+ @Override
+ public void loadPlugins() {
+ log.debug("Lookup plugins in '{}'", pluginsDirectory.getAbsolutePath());
+ // check for plugins directory
+ if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
+ log.error("No '{}' directory", pluginsDirectory.getAbsolutePath());
+ return;
+ }
+
+ // expand all plugin archives
+ FileFilter zipFilter = new ZipFileFilter();
+ File[] zipFiles = pluginsDirectory.listFiles(zipFilter);
+ for (File zipFile : zipFiles) {
+ try {
+ expandPluginArchive(zipFile);
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ // check for no plugins
+ List<FileFilter> filterList = new ArrayList<FileFilter>();
+ filterList.add(new DirectoryFileFilter());
+ filterList.add(new NotFileFilter(createHiddenPluginFilter()));
+ FileFilter pluginsFilter = new AndFileFilter(filterList);
+ File[] directories = pluginsDirectory.listFiles(pluginsFilter);
+ log.debug("Possible plugins: {}", Arrays.asList(directories));
+ if (directories.length == 0) {
+ log.info("No plugins");
+ return;
+ }
+
+ // load any plugin from plugins directory
+ for (File directory : directories) {
+ try {
+ loadPlugin(directory);
+ } catch (PluginException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ // resolve 'unresolvedPlugins'
+ try {
+ resolvePlugins();
+ } catch (PluginException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Get plugin class loader for this path.
+ */
+ @Override
+ public PluginClassLoader getPluginClassLoader(String pluginId) {
+ return pluginClassLoaders.get(pluginId);
+ }
+
+ @Override
+ public <T> List<T> getExtensions(Class<T> type) {
+ List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
+ List<T> extensions = new ArrayList<T>(extensionsWrapper.size());
+ for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
+ extensions.add(extensionWrapper.getInstance());
+ }
+
+ return extensions;
+ }
+
+ @Override
+ public RuntimeMode getRuntimeMode() {
+ if (runtimeMode == null) {
+ // retrieves the runtime mode from system
+ String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
+ runtimeMode = RuntimeMode.byName(modeAsString);
+
+ log.info("PF4J runtime mode: '" + runtimeMode + "'");
+
+ }
+
+ return runtimeMode;
+ }
+
+ /**
+ * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'.
+ */
+ public PluginWrapper whichPlugin(Class<?> clazz) {
+ ClassLoader classLoader = clazz.getClassLoader();
+ for (PluginWrapper plugin : resolvedPlugins) {
+ if (plugin.getPluginClassLoader() == classLoader) {
+ return plugin;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Add the possibility to override the PluginDescriptorFinder.
+ * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
+ * PropertiesPluginDescriptorFinder is returned else this method returns
+ * DefaultPluginDescriptorFinder.
+ */
+ protected PluginDescriptorFinder createPluginDescriptorFinder() {
+ if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
+ return new PropertiesPluginDescriptorFinder();
+ }
+
+ return new DefaultPluginDescriptorFinder(pluginClasspath);
+ }
+
+ /**
+ * Add the possibility to override the ExtensionFinder.
+ */
+ protected ExtensionFinder createExtensionFinder() {
+ return new DefaultExtensionFinder(compoundClassLoader);
+ }
+
+ /**
+ * Add the possibility to override the PluginClassPath.
+ * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
+ * DevelopmentPluginClasspath is returned else this method returns
+ * PluginClasspath.
+ */
+ protected PluginClasspath createPluginClasspath() {
+ if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
+ return new DevelopmentPluginClasspath();
+ }
+
+ return new PluginClasspath();
+ }
+
+ protected boolean isPluginDisabled(String pluginId) {
+ if (enabledPlugins.isEmpty()) {
+ return disabledPlugins.contains(pluginId);
+ }
+
+ return !enabledPlugins.contains(pluginId);
+ }
+
+ protected FileFilter createHiddenPluginFilter() {
+ return new HiddenFilter();
+ }
+
+ /**
+ * Add the possibility to override the plugins directory.
+ * If a "pf4j.pluginsDir" system property is defined than this method returns
+ * that directory.
+ * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
+ * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns
+ * DEFAULT_PLUGINS_DIRECTORY ("plugins").
+ * @return
+ */
+ protected File createPluginsDirectory() {
+ String pluginsDir = System.getProperty("pf4j.pluginsDir");
+ if (pluginsDir == null) {
+ if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
+ pluginsDir = DEVELOPMENT_PLUGINS_DIRECTORY;
+ } else {
+ pluginsDir = DEFAULT_PLUGINS_DIRECTORY;
+ }
+ }
+
+ return new File(pluginsDir);
+ }
+
+ private void initialize() {
+ plugins = new HashMap<String, PluginWrapper>();
+ pluginClassLoaders = new HashMap<String, PluginClassLoader>();
+ pathToIdMap = new HashMap<String, String>();
+ unresolvedPlugins = new ArrayList<PluginWrapper>();
+ resolvedPlugins = new ArrayList<PluginWrapper>();
+ startedPlugins = new ArrayList<PluginWrapper>();
+ disabledPlugins = new ArrayList<String>();
+ compoundClassLoader = new CompoundClassLoader();
+
+ pluginClasspath = createPluginClasspath();
+ pluginDescriptorFinder = createPluginDescriptorFinder();
+ extensionFinder = createExtensionFinder();
+
+ try {
+ // create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file)
+ enabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "enabled.txt"), true);
+ log.info("Enabled plugins: {}", enabledPlugins);
+
+ // create a list with plugin identifiers that should not be accepted by this manager (blacklist from plugins/disabled.txt file)
+ disabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "disabled.txt"), true);
+ log.info("Disabled plugins: {}", disabledPlugins);
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+
+ System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath());
+ }
+
+ private void loadPlugin(File pluginDirectory) throws PluginException {
+ // try to load the plugin
+ String pluginName = pluginDirectory.getName();
+ String pluginPath = "/".concat(pluginName);
+
+ // test for plugin duplication
+ if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
+ return;
+ }
+
+ // retrieves the plugin descriptor
+ log.debug("Find plugin descriptor '{}'", pluginPath);
+ PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory);
+ log.debug("Descriptor " + pluginDescriptor);
+ String pluginClassName = pluginDescriptor.getPluginClass();
+ log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath);
+
+ // test for disabled plugin
+ if (isPluginDisabled(pluginDescriptor.getPluginId())) {
+ log.info("Plugin '{}' is disabled", pluginPath);
+ return;
+ }
+
+ // load plugin
+ log.debug("Loading plugin '{}'", pluginPath);
+ PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath);
+ pluginLoader.load();
+ log.debug("Loaded plugin '{}'", pluginPath);
+
+ // create the plugin wrapper
+ log.debug("Creating wrapper for plugin '{}'", pluginPath);
+ PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader());
+ pluginWrapper.setRuntimeMode(getRuntimeMode());
+ log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
+
+ String pluginId = pluginDescriptor.getPluginId();
+
+ // add plugin to the list with plugins
+ plugins.put(pluginId, pluginWrapper);
+ unresolvedPlugins.add(pluginWrapper);
+
+ // add plugin class loader to the list with class loaders
+ PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
+ pluginClassLoaders.put(pluginId, pluginClassLoader);
+ }
+
+ private void expandPluginArchive(File pluginArchiveFile) throws IOException {
+ String fileName = pluginArchiveFile.getName();
+ long pluginArchiveDate = pluginArchiveFile.lastModified();
+ String pluginName = fileName.substring(0, fileName.length() - 4);
+ File pluginDirectory = new File(pluginsDirectory, pluginName);
+ // check if exists directory or the '.zip' file is "newer" than directory
+ if (!pluginDirectory.exists() || (pluginArchiveDate > pluginDirectory.lastModified())) {
+ log.debug("Expand plugin archive '{}' in '{}'", pluginArchiveFile, pluginDirectory);
+ // create directory for plugin
+ pluginDirectory.mkdirs();
+
+ // expand '.zip' file
+ Unzip unzip = new Unzip();
+ unzip.setSource(pluginArchiveFile);
+ unzip.setDestination(pluginDirectory);
+ unzip.extract();
+ }
+ }
+
+ private void resolvePlugins() throws PluginException {
+ resolveDependencies();
+ }
+
+ private void resolveDependencies() throws PluginException {
+ DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins);
+ resolvedPlugins = dependencyResolver.getSortedPlugins();
+ for (PluginWrapper pluginWrapper : resolvedPlugins) {
+ unresolvedPlugins.remove(pluginWrapper);
+ compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader());
+ log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
+ }
+ }
+
+}
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java new file mode 100644 index 0000000..e14722c --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package ro.fortsoft.pf4j; + +/** + * Overwrite classes directories to "target/classes" and lib directories to "target/lib". + * + * @author Decebal Suiu + */ +public class DevelopmentPluginClasspath extends PluginClasspath { + + private static final String DEVELOPMENT_CLASSES_DIRECTORY = "target/classes"; + private static final String DEVELOPMENT_LIB_DIRECTORY = "target/lib"; + + public DevelopmentPluginClasspath() { + super(); + } + + @Override + protected void addResources() { + classesDirectories.add(DEVELOPMENT_CLASSES_DIRECTORY); + libDirectories.add(DEVELOPMENT_LIB_DIRECTORY); + } + + +} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java index 23cd8f6..5fbe975 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java @@ -28,16 +28,14 @@ public class PluginClasspath { private static final String DEFAULT_CLASSES_DIRECTORY = "classes"; private static final String DEFAULT_LIB_DIRECTORY = "lib"; - private List<String> classesDirectories; - private List<String> libDirectories; + protected List<String> classesDirectories; + protected List<String> libDirectories; public PluginClasspath() { classesDirectories = new ArrayList<String>(); libDirectories = new ArrayList<String>(); - // add defaults - classesDirectories.add(DEFAULT_CLASSES_DIRECTORY); - libDirectories.add(DEFAULT_LIB_DIRECTORY); + addResources(); } public List<String> getClassesDirectories() { @@ -56,4 +54,9 @@ public class PluginClasspath { this.libDirectories = libDirectories; } + protected void addResources() { + classesDirectories.add(DEFAULT_CLASSES_DIRECTORY); + libDirectories.add(DEFAULT_LIB_DIRECTORY); + } + } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java index 5e89b6b..7c78b89 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java @@ -61,4 +61,9 @@ public interface PluginManager { public <T> List<T> getExtensions(Class<T> type); + /** + * The runtime mode. Must currently be either DEVELOPMENT or DEPLOYMENT. + */ + public RuntimeMode getRuntimeMode(); + } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java index 4dd18d1..d307cf0 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java @@ -27,6 +27,7 @@ public class PluginWrapper { PluginClassLoader pluginClassLoader; Plugin plugin; PluginState pluginState; + RuntimeMode runtimeMode; public PluginWrapper(PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) { this.descriptor = descriptor; @@ -74,6 +75,10 @@ public class PluginWrapper { return pluginState; } + public RuntimeMode getRuntimeMode() { + return runtimeMode; + } + @Override public int hashCode() { final int prime = 31; @@ -114,6 +119,10 @@ public class PluginWrapper { void setPluginState(PluginState pluginState) { this.pluginState = pluginState; } + + void setRuntimeMode(RuntimeMode runtimeMode) { + this.runtimeMode = runtimeMode; + } private Plugin createPluginInstance() throws Exception { String pluginClassName = descriptor.getPluginClass(); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/RuntimeMode.java b/pf4j/src/main/java/ro/fortsoft/pf4j/RuntimeMode.java new file mode 100644 index 0000000..0e7cd3a --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/RuntimeMode.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package ro.fortsoft.pf4j; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * @author Decebal Suiu + */ +public enum RuntimeMode { + + DEVELOPMENT("development"), // development + DEPLOYMENT("deployment"); // deployment + + private final String name; + + private static final Map<String, RuntimeMode> map = new HashMap<String, RuntimeMode>(); + + static { + for (RuntimeMode mode : RuntimeMode.values()) { + map.put(mode.name, mode); + } + } + + private RuntimeMode(final String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public static RuntimeMode byName(String name) { + if (map.containsKey(name)) { + return map.get(name); + } + + throw new NoSuchElementException("Cannot found PF4J runtime mode with name '" + name + + "'. Must be 'development' or 'deployment'."); + } + +}
\ No newline at end of file |