]> source.dussan.org Git - pf4j.git/commitdiff
first commit
authorDecebal Suiu <decebal.suiu@gmail.com>
Thu, 11 Oct 2012 10:29:43 +0000 (13:29 +0300)
committerDecebal Suiu <decebal.suiu@gmail.com>
Thu, 11 Oct 2012 10:29:43 +0000 (13:29 +0300)
44 files changed:
README.md [new file with mode: 0644]
demo/api/pom.xml [new file with mode: 0644]
demo/api/src/main/java/org/pf4j/demo/api/Greeting.java [new file with mode: 0644]
demo/app/pom.xml [new file with mode: 0644]
demo/app/src/main/assembly/assembly.xml [new file with mode: 0644]
demo/app/src/main/java/org/pf4j/demo/Boot.java [new file with mode: 0644]
demo/app/src/main/resources/log4j.properties [new file with mode: 0644]
demo/app/src/main/resources/logging.properties [new file with mode: 0644]
demo/plugin1/plugin.properties [new file with mode: 0644]
demo/plugin1/pom.xml [new file with mode: 0644]
demo/plugin1/src/main/assembly/assembly.xml [new file with mode: 0644]
demo/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java [new file with mode: 0644]
demo/plugin2/plugin.properties [new file with mode: 0644]
demo/plugin2/pom.xml [new file with mode: 0644]
demo/plugin2/src/main/assembly/assembly.xml [new file with mode: 0644]
demo/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java [new file with mode: 0644]
demo/pom.xml [new file with mode: 0644]
pf4j/pom.xml [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/DefaultPluginManager.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/DependencyResolver.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/Extension.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/ExtensionFinder.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/ExtensionPoint.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/ExtensionWrapper.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/Plugin.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginClassLoader.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginDescriptor.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginException.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginLoader.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginManager.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginVersion.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/PluginWrapper.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/util/DirectedGraph.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/util/JarFilter.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/util/UberClassLoader.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/util/Unzip.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/util/ZipFilter.java [new file with mode: 0644]
pom.xml [new file with mode: 0644]
run-demo.sh [new file with mode: 0755]

diff --git a/README.md b/README.md
new file mode 100644 (file)
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 (file)
index 0000000..1514476
--- /dev/null
@@ -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 (file)
index 0000000..b21f80c
--- /dev/null
@@ -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 (file)
index 0000000..d9084b1
--- /dev/null
@@ -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 (file)
index 0000000..1d9e8e4
--- /dev/null
@@ -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 (file)
index 0000000..b34af53
--- /dev/null
@@ -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 (file)
index 0000000..b3e5aa8
--- /dev/null
@@ -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 (file)
index 0000000..6fe7cef
--- /dev/null
@@ -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 (file)
index 0000000..9da9bcc
--- /dev/null
@@ -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 (file)
index 0000000..10c1163
--- /dev/null
@@ -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 (file)
index 0000000..3fdc464
--- /dev/null
@@ -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 (file)
index 0000000..22ebf7d
--- /dev/null
@@ -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 (file)
index 0000000..60b6f33
--- /dev/null
@@ -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 (file)
index 0000000..7f4f617
--- /dev/null
@@ -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 (file)
index 0000000..5cefe0d
--- /dev/null
@@ -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 (file)
index 0000000..ad7fd0c
--- /dev/null
@@ -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 (file)
index 0000000..dbb029a
--- /dev/null
@@ -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 (file)
index 0000000..920a870
--- /dev/null
@@ -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 (file)
index 0000000..e7a6dc9
--- /dev/null
@@ -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 (file)
index 0000000..0055e57
--- /dev/null
@@ -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 (file)
index 0000000..9cb7ccd
--- /dev/null
@@ -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 (file)
index 0000000..2f5f601
--- /dev/null
@@ -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 (file)
index 0000000..6c499b6
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 Decebal Suiu
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
+ * the License. You may obtain a copy of the License in the LICENSE file, or at:
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.pf4j;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import net.java.sezpoz.Indexable;
+
+/**
+ * @author Decebal Suiu
+ */
+@Indexable
+@Retention(RUNTIME)
+@Target(TYPE)
+@Documented
+public @interface Extension {
+
+        int ordinal() default 0;
+        
+}
diff --git a/pf4j/src/main/java/org/pf4j/ExtensionFinder.java b/pf4j/src/main/java/org/pf4j/ExtensionFinder.java
new file mode 100644 (file)
index 0000000..5fc466d
--- /dev/null
@@ -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 (file)
index 0000000..9113c1d
--- /dev/null
@@ -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 (file)
index 0000000..9ba320e
--- /dev/null
@@ -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 (file)
index 0000000..bab55c1
--- /dev/null
@@ -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 (file)
index 0000000..884642f
--- /dev/null
@@ -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 (file)
index 0000000..8346e48
--- /dev/null
@@ -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 (file)
index 0000000..c59ec65
--- /dev/null
@@ -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 (file)
index 0000000..c341c70
--- /dev/null
@@ -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 (file)
index 0000000..bd64d59
--- /dev/null
@@ -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 (file)
index 0000000..cf0cff1
--- /dev/null
@@ -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 (file)
index 0000000..66267c9
--- /dev/null
@@ -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 (file)
index 0000000..46a0da1
--- /dev/null
@@ -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 (file)
index 0000000..a5fa829
--- /dev/null
@@ -0,0 +1,171 @@
+/*\r
+ * Copyright 2012 Decebal Suiu\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with\r
+ * the License. You may obtain a copy of the License in the LICENSE file, or at:\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\r
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\r
+ * specific language governing permissions and limitations under the License.\r
+ */\r
+package org.pf4j.util;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Stack;\r
+\r
+/**\r
+ * @author Decebal Suiu\r
+ */\r
+public class DirectedGraph<V> {\r
+\r
+    /**\r
+     * The implementation here is basically an adjacency list, but instead\r
+     * of an array of lists, a Map is used to map each vertex to its list of\r
+     * adjacent vertices.\r
+     */\r
+    private Map<V, List<V>> neighbors = new HashMap<V, List<V>>();\r
+\r
+    /**\r
+     * Add a vertex to the graph. Nothing happens if vertex is already in graph.\r
+     */\r
+    public void addVertex(V vertex) {\r
+        if (neighbors.containsKey(vertex)) {\r
+               return;\r
+        }\r
+        neighbors.put(vertex, new ArrayList<V>());\r
+    }\r
+\r
+    /**\r
+     * True if graph contains vertex.\r
+     */\r
+    public boolean containsVertex(V vertex) {\r
+        return neighbors.containsKey(vertex);\r
+    }\r
+\r
+    /**\r
+     * Add an edge to the graph; if either vertex does not exist, it's added.\r
+     * This implementation allows the creation of multi-edges and self-loops.\r
+     */\r
+    public void addEdge(V from, V to) {\r
+        this.addVertex(from);\r
+        this.addVertex(to);\r
+        neighbors.get(from).add(to);\r
+    }\r
+\r
+    /**\r
+     * Remove an edge from the graph. Nothing happens if no such edge.\r
+     * @throws IllegalArgumentException if either vertex doesn't exist.\r
+     */\r
+    public void remove(V from, V to) {\r
+        if (!(this.containsVertex(from) && this.containsVertex(to))) {\r
+            throw new IllegalArgumentException("Nonexistent vertex");\r
+        }\r
+        neighbors.get(from).remove(to);\r
+    }\r
+\r
+    /**\r
+     * Report (as a Map) the out-degree of each vertex.\r
+     */\r
+    public Map<V, Integer> outDegree() {\r
+        Map<V, Integer> result = new HashMap<V, Integer>();\r
+        for (V vertex : neighbors.keySet()) {\r
+               result.put(vertex, neighbors.get(vertex).size());\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * Report (as a Map) the in-degree of each vertex.\r
+     */\r
+    public Map<V,Integer> inDegree() {\r
+        Map<V, Integer> result = new HashMap<V, Integer>();\r
+        for (V vertex : neighbors.keySet()) {\r
+               result.put(vertex, 0); // all in-degrees are 0\r
+        }\r
+        for (V from : neighbors.keySet()) {\r
+            for (V to : neighbors.get(from)) {\r
+                result.put(to, result.get(to) + 1); // increment in-degree\r
+            }\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * Report (as a List) the topological sort of the vertices; null for no such sort.\r
+     */\r
+    public List<V> topologicalSort() {\r
+        Map<V, Integer> degree = inDegree();\r
+\r
+        // determine all vertices with zero in-degree\r
+        Stack<V> zeroVertices = new Stack<V>(); // stack as good as any here\r
+        for (V v : degree.keySet()) {\r
+            if (degree.get(v) == 0) {\r
+               zeroVertices.push(v);\r
+            }\r
+        }\r
+\r
+        // determine the topological order\r
+        List<V> result = new ArrayList<V>();\r
+        while (!zeroVertices.isEmpty()) {\r
+            V vertex = zeroVertices.pop(); // choose a vertex with zero in-degree\r
+            result.add(vertex); // vertex 'v' is next in topological order\r
+            // "remove" vertex 'v' by updating its neighbors\r
+            for (V neighbor : neighbors.get(vertex)) {\r
+                degree.put(neighbor, degree.get(neighbor) - 1);\r
+                // remember any vertices that now have zero in-degree\r
+                if (degree.get(neighbor) == 0) {\r
+                       zeroVertices.push(neighbor);\r
+                }\r
+            }\r
+        }\r
+\r
+        // check that we have used the entire graph (if not, there was a cycle)\r
+        if (result.size() != neighbors.size()) {\r
+               return null;\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * Report (as a List) the reverse topological sort of the vertices; null for no such sort.\r
+     */\r
+    public List<V> reverseTopologicalSort() {\r
+       List<V> list = topologicalSort();\r
+       if (list == null) {\r
+               return null;\r
+       }\r
+       Collections.reverse(list);\r
+\r
+       return list;\r
+    }\r
+\r
+    /**\r
+     * True if graph is a dag (directed acyclic graph).\r
+     */\r
+    public boolean isDag () {\r
+        return topologicalSort() != null;\r
+    }\r
+\r
+    /**\r
+     * String representation of graph.\r
+     */\r
+    @Override\r
+    public String toString() {\r
+        StringBuffer sb = new StringBuffer();\r
+        for (V vertex : neighbors.keySet()) {\r
+               sb.append("\n   " + vertex + " -> " + neighbors.get(vertex));\r
+        }\r
+\r
+        return sb.toString();\r
+    }\r
+\r
+}\r
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 (file)
index 0000000..c6a236b
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 Decebal Suiu
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
+ * the License. You may obtain a copy of the License in the LICENSE file, or at:
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.pf4j.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 (file)
index 0000000..1252a4e
--- /dev/null
@@ -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 (file)
index 0000000..09c322c
--- /dev/null
@@ -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 (file)
index 0000000..4ecfe2c
--- /dev/null
@@ -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 (file)
index 0000000..d5e6a04
--- /dev/null
@@ -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 (file)
index 0000000..4884e90
--- /dev/null
@@ -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);
+    }
+
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..a8d45f9
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.pf4j</groupId>
+    <artifactId>pom</artifactId>
+    <version>0.1-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>PF4J</name>
+    <description>Plugin Framework for Java</description>
+
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <scm>
+        <connection>scm:git:https://github.com/decebals/pf4j.git</connection>
+        <developerConnection>scm:git:https://github.com/decebals/pf4j.git</developerConnection>
+        <url>git@github.com/decebals/pf4j.git</url>
+    </scm>
+        
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <slf4j.version>1.6.4</slf4j.version>
+    </properties>   
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                    <optimize>true</optimize>
+                </configuration>
+            </plugin>
+            
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-release-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <goals>deploy</goals>
+                    <autoVersionSubmodules>true</autoVersionSubmodules>
+                    <tagNameFormat>release-@{project.version}</tagNameFormat>
+                </configuration>
+            </plugin>
+            
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>2.4.3</version>
+            </plugin>
+            
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.3.1</version>
+            </plugin>           
+        </plugins>
+    </build>
+
+    <modules>
+               <module>pf4j</module>
+               <module>demo</module>
+       </modules>
+
+</project>
diff --git a/run-demo.sh b/run-demo.sh
new file mode 100755 (executable)
index 0000000..bea8ba2
--- /dev/null
@@ -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 -
+