Browse Source

Merge pull request #324 from pf4j/pf4j_3

PF4J 3
pull/328/head
Decebal Suiu 6 years ago committed by GitHub
parent
commit
ee3b5f2276
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .travis.yml
  2. 4
      demo/api/pom.xml
  3. 4
      demo/app/pom.xml
  4. 4
      demo/app/src/main/java/org/pf4j/demo/Boot.java
  5. 4
      demo/plugins/plugin1/pom.xml
  6. 4
      demo/plugins/plugin2/pom.xml
  7. 13
      demo/plugins/pom.xml
  8. 4
      demo/pom.xml
  9. 68
      maven-archetypes/quickstart/pom.xml
  10. 86
      maven-archetypes/quickstart/src/main/resources/META-INF/maven/archetype-metadata.xml
  11. 86
      maven-archetypes/quickstart/src/main/resources/archetype-resources/app/pom.xml
  12. 30
      maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/assembly/assembly.xml
  13. 102
      maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/java/Boot.java
  14. 9
      maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/java/Greeting.java
  15. 13
      maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/java/WhazzupGreeting.java
  16. 20
      maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/resources/log4j.properties
  17. 6
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/disabled.txt
  18. 6
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/enabled.txt
  19. 5
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/hello/plugin.properties
  20. 24
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/hello/pom.xml
  21. 37
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/hello/src/main/java/hello/HelloPlugin.java
  22. 86
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/pom.xml
  23. 5
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/welcome/plugin.properties
  24. 32
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/welcome/pom.xml
  25. 41
      maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/welcome/src/main/java/welcome/WelcomePlugin.java
  26. 48
      maven-archetypes/quickstart/src/main/resources/archetype-resources/pom.xml
  27. 23
      maven-archetypes/quickstart/src/main/resources/archetype-resources/run.sh
  28. 43
      pf4j/pom.xml
  29. 47
      pf4j/src/main/java/module-info.java
  30. 163
      pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
  31. 88
      pf4j/src/main/java/org/pf4j/BasePluginLoader.java
  32. 19
      pf4j/src/main/java/org/pf4j/BasePluginRepository.java
  33. 4
      pf4j/src/main/java/org/pf4j/CompoundPluginDescriptorFinder.java
  34. 16
      pf4j/src/main/java/org/pf4j/CompoundPluginLoader.java
  35. 22
      pf4j/src/main/java/org/pf4j/CompoundPluginRepository.java
  36. 10
      pf4j/src/main/java/org/pf4j/DefaultExtensionFactory.java
  37. 9
      pf4j/src/main/java/org/pf4j/DefaultPluginClasspath.java
  38. 59
      pf4j/src/main/java/org/pf4j/DefaultPluginLoader.java
  39. 46
      pf4j/src/main/java/org/pf4j/DefaultPluginManager.java
  40. 44
      pf4j/src/main/java/org/pf4j/DefaultPluginRepository.java
  41. 20
      pf4j/src/main/java/org/pf4j/DefaultPluginStatusProvider.java
  42. 6
      pf4j/src/main/java/org/pf4j/DependencyResolver.java
  43. 8
      pf4j/src/main/java/org/pf4j/DevelopmentPluginClasspath.java
  44. 29
      pf4j/src/main/java/org/pf4j/DevelopmentPluginLoader.java
  45. 55
      pf4j/src/main/java/org/pf4j/DevelopmentPluginRepository.java
  46. 2
      pf4j/src/main/java/org/pf4j/ExtensionFactory.java
  47. 56
      pf4j/src/main/java/org/pf4j/JarPluginManager.java
  48. 20
      pf4j/src/main/java/org/pf4j/LegacyExtensionFinder.java
  49. 39
      pf4j/src/main/java/org/pf4j/ManifestPluginDescriptorFinder.java
  50. 6
      pf4j/src/main/java/org/pf4j/Plugin.java
  51. 2
      pf4j/src/main/java/org/pf4j/PluginAlreadyLoadedException.java
  52. 17
      pf4j/src/main/java/org/pf4j/PluginClasspath.java
  53. 5
      pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java
  54. 31
      pf4j/src/main/java/org/pf4j/PluginManager.java
  55. 5
      pf4j/src/main/java/org/pf4j/PluginRepository.java
  56. 16
      pf4j/src/main/java/org/pf4j/PluginRuntimeException.java
  57. 8
      pf4j/src/main/java/org/pf4j/PluginStatusProvider.java
  58. 11
      pf4j/src/main/java/org/pf4j/PluginWrapper.java
  59. 41
      pf4j/src/main/java/org/pf4j/PropertiesPluginDescriptorFinder.java
  60. 2
      pf4j/src/main/java/org/pf4j/RuntimeMode.java
  61. 7
      pf4j/src/main/java/org/pf4j/SingletonExtensionFactory.java
  62. 49
      pf4j/src/main/java/org/pf4j/ZipPluginManager.java
  63. 6
      pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java
  64. 12
      pf4j/src/main/java/org/pf4j/util/AndFileFilter.java
  65. 10
      pf4j/src/main/java/org/pf4j/util/DirectedGraph.java
  66. 11
      pf4j/src/main/java/org/pf4j/util/FileUtils.java
  67. 12
      pf4j/src/main/java/org/pf4j/util/OrFileFilter.java
  68. 6
      pf4j/src/main/java/org/pf4j/util/Unzip.java
  69. 12
      pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java
  70. 49
      pf4j/src/test/java/org/pf4j/AbstractPluginManagerTest.java
  71. 82
      pf4j/src/test/java/org/pf4j/CompoundPluginDescriptorFinderTest.java
  72. 30
      pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java
  73. 8
      pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java
  74. 46
      pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java
  75. 66
      pf4j/src/test/java/org/pf4j/DefaultPluginRepositoryTest.java
  76. 51
      pf4j/src/test/java/org/pf4j/DefaultPluginStatusProviderTest.java
  77. 18
      pf4j/src/test/java/org/pf4j/DefaultVersionManagerTest.java
  78. 10
      pf4j/src/test/java/org/pf4j/DependencyResolverTest.java
  79. 61
      pf4j/src/test/java/org/pf4j/DevelopmentPluginRepositoryTest.java
  80. 6
      pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java
  81. 96
      pf4j/src/test/java/org/pf4j/JarPluginManagerTest.java
  82. 58
      pf4j/src/test/java/org/pf4j/JarPluginRepositoryTest.java
  83. 73
      pf4j/src/test/java/org/pf4j/LegacyExtensionFinderTest.java
  84. 4
      pf4j/src/test/java/org/pf4j/LegacyExtensionStorageTest.java
  85. 83
      pf4j/src/test/java/org/pf4j/LoadPluginsTest.java
  86. 218
      pf4j/src/test/java/org/pf4j/ManifestPluginDescriptorFinderTest.java
  87. 19
      pf4j/src/test/java/org/pf4j/PluginDependencyTest.java
  88. 193
      pf4j/src/test/java/org/pf4j/PropertiesPluginDescriptorFinderTest.java
  89. 5
      pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java
  90. 33
      pf4j/src/test/java/org/pf4j/plugin/ClassDataProvider.java
  91. 55
      pf4j/src/test/java/org/pf4j/plugin/DefaultClassDataProvider.java
  92. 5
      pf4j/src/test/java/org/pf4j/plugin/FailTestExtension.java
  93. 200
      pf4j/src/test/java/org/pf4j/plugin/PluginJar.java
  94. 86
      pf4j/src/test/java/org/pf4j/plugin/PluginZip.java
  95. 5
      pf4j/src/test/java/org/pf4j/plugin/TestExtension.java
  96. 2
      pf4j/src/test/java/org/pf4j/plugin/TestExtensionPoint.java
  97. 15
      pf4j/src/test/java/org/pf4j/processor/ServiceProviderExtensionStorageTest.java
  98. 98
      pf4j/src/test/java/org/pf4j/util/DirectedGraphTest.java
  99. 18
      pf4j/src/test/java/org/pf4j/util/FileUtilsTest.java
  100. 10
      pom.xml

4
.travis.yml

@ -1,9 +1,5 @@
language: java language: java
jdk: jdk:
- openjdk7
# JDK7 is not supported anymore; https://github.com/travis-ci/travis-ci/issues/7884#issuecomment-308451879
# - oraclejdk7
- oraclejdk8
- openjdk11 - openjdk11
after_success: after_success:
- mvn clean cobertura:cobertura coveralls:report - mvn clean cobertura:cobertura coveralls:report

4
demo/api/pom.xml

@ -4,12 +4,12 @@
<parent> <parent>
<groupId>org.pf4j.demo</groupId> <groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-api</artifactId> <artifactId>pf4j-demo-api</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo Api</name> <name>Demo Api</name>

4
demo/app/pom.xml

@ -4,12 +4,12 @@
<parent> <parent>
<groupId>org.pf4j.demo</groupId> <groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-app</artifactId> <artifactId>pf4j-demo-app</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo App</name> <name>Demo App</name>

4
demo/app/src/main/java/org/pf4j/demo/Boot.java

@ -73,8 +73,8 @@ public class Boot {
} }
System.out.println("Extension classes by classpath:"); System.out.println("Extension classes by classpath:");
List<Class<Greeting>> greetingsClasses = pluginManager.getExtensionClasses(Greeting.class); List<Class<? extends Greeting>> greetingsClasses = pluginManager.getExtensionClasses(Greeting.class);
for (Class<Greeting> greeting : greetingsClasses) { for (Class<? extends Greeting> greeting : greetingsClasses) {
System.out.println(" Class: " + greeting.getCanonicalName()); System.out.println(" Class: " + greeting.getCanonicalName());
} }

4
demo/plugins/plugin1/pom.xml

@ -4,12 +4,12 @@
<parent> <parent>
<groupId>org.pf4j.demo</groupId> <groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId> <artifactId>pf4j-demo-plugins</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin1</artifactId> <artifactId>pf4j-demo-plugin1</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo Plugin #1</name> <name>Demo Plugin #1</name>

4
demo/plugins/plugin2/pom.xml

@ -4,12 +4,12 @@
<parent> <parent>
<groupId>org.pf4j.demo</groupId> <groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId> <artifactId>pf4j-demo-plugins</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin2</artifactId> <artifactId>pf4j-demo-plugin2</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Demo Plugin #2</name> <name>Demo Plugin #2</name>

13
demo/plugins/pom.xml

@ -4,19 +4,18 @@
<parent> <parent>
<groupId>org.pf4j.demo</groupId> <groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugins</artifactId> <artifactId>pf4j-demo-plugins</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Demo Plugins Parent</name> <name>Demo Plugins Parent</name>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
<!-- Override below properties in each plugin's pom.xml --> <!-- Override below properties in each plugin's pom.xml -->
<plugin.id /> <plugin.id />
@ -28,14 +27,6 @@
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>

4
demo/pom.xml

@ -4,13 +4,13 @@
<parent> <parent>
<groupId>org.pf4j</groupId> <groupId>org.pf4j</groupId>
<artifactId>pf4j-parent</artifactId> <artifactId>pf4j-parent</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.pf4j.demo</groupId> <groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId> <artifactId>pf4j-demo-parent</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Demo Parent</name> <name>Demo Parent</name>

68
maven-archetypes/quickstart/pom.xml

@ -0,0 +1,68 @@
<?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>pf4j-parent</artifactId>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-quickstart</artifactId>
<packaging>maven-archetype</packaging>
<name>Quickstart Archetype</name>
<build>
<!-- http://stackoverflow.com/questions/7223031/how-to-embed-archetype-project-version-in-maven-archetype -->
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>archetype-resources/**/pom.xml</include>
</includes>
</resource>
<resource>
<filtering>false</filtering>
<directory>src/main/resources</directory>
<excludes>
<exclude>archetype-resources/**/pom.xml</exclude>
</excludes>
</resource>
</resources>
<extensions>
<extension>
<groupId>org.apache.maven.archetype</groupId>
<artifactId>archetype-packaging</artifactId>
<version>2.3</version>
</extension>
</extensions>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-archetype-plugin</artifactId>
<version>2.3</version>
</plugin>
<!-- http://stackoverflow.com/questions/7223031/how-to-embed-archetype-project-version-in-maven-archetype -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<configuration>
<delimiters>
<delimiter>{{*}}</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

86
maven-archetypes/quickstart/src/main/resources/META-INF/maven/archetype-metadata.xml

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<archetype-descriptor name="quickstart">
<fileSets>
<fileSet encoding="UTF-8">
<directory></directory>
<includes>
<include>run.sh</include>
</includes>
</fileSet>
</fileSets>
<modules>
<module id="${rootArtifactId}-app" dir="app" name="${rootArtifactId}-app">
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/main/java</directory>
<includes>
<include>**/*.java</include>
</includes>
</fileSet>
<fileSet filtered="true" encoding="UTF-8">
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
</includes>
</fileSet>
<fileSet filtered="true" encoding="UTF-8">
<directory>src/main/assembly</directory>
<includes>
<include>**/*.xml</include>
</includes>
</fileSet>
</fileSets>
</module>
<module id="${rootArtifactId}-plugins" dir="plugins" name="${rootArtifactId}-plugins">
<fileSets>
<fileSet filtered="true" encoding="UTF-8">
<directory></directory>
<includes>
<include>enabled.txt</include>
<include>disabled.txt</include>
</includes>
</fileSet>
</fileSets>
<modules>
<module id="hello-plugin" dir="hello" name="hello-plugin">
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/main/java</directory>
<includes>
<include>**/*.java</include>
</includes>
</fileSet>
<fileSet filtered="true" encoding="UTF-8">
<directory></directory>
<includes>
<include>plugin.properties</include>
</includes>
</fileSet>
</fileSets>
</module>
<module id="welcome-plugin" dir="welcome" name="welcome-plugin">
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/main/java</directory>
<includes>
<include>**/*.java</include>
</includes>
</fileSet>
<fileSet filtered="true" encoding="UTF-8">
<directory></directory>
<includes>
<include>plugin.properties</include>
</includes>
</fileSet>
</fileSets>
</module>
</modules>
</module>
</modules>
</archetype-descriptor>

86
maven-archetypes/quickstart/src/main/resources/archetype-resources/app/pom.xml

@ -0,0 +1,86 @@
<?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>${groupId}</groupId>
<artifactId>${rootArtifactId}</artifactId>
<version>${version}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<packaging>jar</packaging>
<name>App</name>
<properties>
<main.class>${package}.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>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${pf4j.version}</version>
</dependency>
<!-- Logs -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</project>

30
maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/assembly/assembly.xml

@ -0,0 +1,30 @@
<assembly>
<id>app</id>
<formats>
<format>dir</format>
<format>zip</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>
<excludes>
<exclude>*-javadoc.jar</exclude>
<exclude>*-sources.jar</exclude>
</excludes>
</fileSet>
</fileSets>
</assembly>

102
maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/java/Boot.java

@ -0,0 +1,102 @@
package ${package};
import org.apache.commons.lang.StringUtils;
import org.pf4j.DefaultPluginManager;
import org.pf4j.ExtensionFinder;
import org.pf4j.PluginManager;
import org.pf4j.PluginWrapper;
import java.util.List;
import java.util.Set;
/**
* A boot class that start the application.
*/
public class Boot {
public static void main(String[] args) {
// create the plugin manager
PluginManager pluginManager = new DefaultPluginManager();
// load the plugins
pluginManager.loadPlugins();
// enable a disabled plugin
// pluginManager.enablePlugin("welcome-plugin");
// start (active/resolved) the plugins
pluginManager.startPlugins();
// retrieves the extensions for Greeting extension point
List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
System.out.println(String.format("Found %d extensions for extension point '%s'", greetings.size(), Greeting.class.getName()));
for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.getGreeting());
}
// print extensions from classpath (non plugin)
System.out.println("Extensions added by classpath:");
Set<String> extensionClassNames = pluginManager.getExtensionClassNames(null);
for (String extension : extensionClassNames) {
System.out.println(" " + extension);
}
System.out.println("Extension classes by classpath:");
List<Class<? extends Greeting>> greetingsClasses = pluginManager.getExtensionClasses(Greeting.class);
for (Class<? extends Greeting> greeting : greetingsClasses) {
System.out.println(" Class: " + greeting.getCanonicalName());
}
// print extensions ids for each started plugin
List<PluginWrapper> startedPlugins = pluginManager.getStartedPlugins();
for (PluginWrapper plugin : startedPlugins) {
String pluginId = plugin.getDescriptor().getPluginId();
System.out.println(String.format("Extensions added by plugin '%s':", pluginId));
extensionClassNames = pluginManager.getExtensionClassNames(pluginId);
for (String extension : extensionClassNames) {
System.out.println(" " + extension);
}
}
// print extensions instances for Greeting extension point for each started plugin
for (PluginWrapper plugin : startedPlugins) {
String pluginId = plugin.getDescriptor().getPluginId();
System.out.println(String.format("Extensions instances added by plugin '%s' for extension point '%s':", pluginId, Greeting.class.getName()));
List<Greeting> extensions = pluginManager.getExtensions(Greeting.class, pluginId);
for (Object extension : extensions) {
System.out.println(" " + extension);
}
}
// print extensions instances from classpath (non plugin)
System.out.println("Extensions instances added by classpath:");
List extensions = pluginManager.getExtensions((String) null);
for (Object extension : extensions) {
System.out.println(" " + extension);
}
// print extensions instances for each started plugin
for (PluginWrapper plugin : startedPlugins) {
String pluginId = plugin.getDescriptor().getPluginId();
System.out.println(String.format("Extensions instances added by plugin '%s':", pluginId));
extensions = pluginManager.getExtensions(pluginId);
for (Object extension : extensions) {
System.out.println(" " + extension);
}
}
// stop the plugins
pluginManager.stopPlugins();
/*
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
pluginManager.stopPlugins();
}
});
*/
}
}

9
maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/java/Greeting.java

@ -0,0 +1,9 @@
package ${package};
import org.pf4j.ExtensionPoint;
public interface Greeting extends ExtensionPoint {
String getGreeting();
}

13
maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/java/WhazzupGreeting.java

@ -0,0 +1,13 @@
package ${package};
import org.pf4j.Extension;
@Extension
public class WhazzupGreeting implements Greeting {
@Override
public String getGreeting() {
return "Whazzup";
}
}

20
maven-archetypes/quickstart/src/main/resources/archetype-resources/app/src/main/resources/log4j.properties

@ -0,0 +1,20 @@
log4j.rootLogger=DEBUG, Console
#
# PF4J log
#
log4j.logger.org.pf4j=DEBUG, Console
# !!! Put the bellow classes on level TRACE when you are in trouble
log4j.logger.org.pf4j.PluginClassLoader=DEBUG, Console
log4j.logger.org.pf4j.AbstractExtensionFinder=DEBUG, Console
log4j.additivity.org.pf4j=false
log4j.additivity.org.pf4j.PluginClassLoader=false
log4j.additivity.org.pf4j.AbstractExtensionFinder=false
#
# Appenders
#
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
#log4j.appender.Console.layout.conversionPattern=%-5p - %-32.32c{1} - %m\n
log4j.appender.Console.layout.ConversionPattern=%d %p %c - %m%n

6
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/disabled.txt

@ -0,0 +1,6 @@
########################################
# - load all plugins except these
# - add one plugin id on each line
# - put this file in plugins folder
########################################
#welcome-plugin

6
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/enabled.txt

@ -0,0 +1,6 @@
########################################
# - load only these plugins
# - add one plugin id on each line
# - put this file in plugins folder
########################################
#welcome-plugin

5
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/hello/plugin.properties

@ -0,0 +1,5 @@
plugin.id=hello-plugin
plugin.class=${package}.hello.HelloPlugin
plugin.version=${version}
plugin.provider=
plugin.dependencies=

24
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/hello/pom.xml

@ -0,0 +1,24 @@
<?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>${groupId}</groupId>
<artifactId>${rootArtifactId}-plugins</artifactId>
<version>${version}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hello-plugin</artifactId>
<version>${version}</version>
<packaging>jar</packaging>
<name>Hello Plugin</name>
<properties>
<plugin.id>hello-plugin</plugin.id>
<plugin.class>${package}.hello.HelloPlugin</plugin.class>
<plugin.version>${version}</plugin.version>
<plugin.provider/>
<plugin.dependencies/>
</properties>
</project>

37
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/hello/src/main/java/hello/HelloPlugin.java

@ -0,0 +1,37 @@
package ${package}.hello;
import org.pf4j.Extension;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import ${package}.Greeting;
/**
* A very simple plugin.
*/
public class HelloPlugin extends Plugin {
public HelloPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
public void start() {
System.out.println("HelloPlugin.start()");
}
@Override
public void stop() {
System.out.println("HelloPlugin.stop()");
}
@Extension(ordinal=1)
public static class HelloGreeting implements Greeting {
@Override
public String getGreeting() {
return "Hello";
}
}
}

86
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/pom.xml

@ -0,0 +1,86 @@
<?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>${groupId}</groupId>
<artifactId>${rootArtifactId}</artifactId>
<version>${version}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<packaging>pom</packaging>
<name>Plugins Parent</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Override below properties in each plugin's pom.xml -->
<plugin.id></plugin.id>
<plugin.class></plugin.class>
<plugin.version></plugin.version>
<plugin.provider></plugin.provider>
<plugin.dependencies></plugin.dependencies>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>${project.artifactId}-${project.version}-all</finalName>
<appendAssemblyId>false</appendAssemblyId>
<attach>false</attach>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
<Plugin-Class>${plugin.class}</Plugin-Class>
<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>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>${pf4j.version}</version>
<!-- !!! VERY IMPORTANT -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId}</groupId>
<artifactId>${rootArtifactId}-app</artifactId>
<version>${version}</version>
<!-- !!! VERY IMPORTANT -->
<scope>provided</scope>
</dependency>
</dependencies>
</project>

5
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/welcome/plugin.properties

@ -0,0 +1,5 @@
plugin.id=welcome-plugin
plugin.class=${package}.welcome.WelcomePlugin
plugin.version=${version}
plugin.provider=
plugin.dependencies=

32
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/welcome/pom.xml

@ -0,0 +1,32 @@
<?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>${groupId}</groupId>
<artifactId>${rootArtifactId}-plugins</artifactId>
<version>${version}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>welcome-plugin</artifactId>
<version>${version}</version>
<packaging>jar</packaging>
<name>Welcom Plugin</name>
<properties>
<plugin.id>welcome-plugin</plugin.id>
<plugin.class>${package}.welcome.WelcomePlugin</plugin.class>
<plugin.version>${version}</plugin.version>
<plugin.provider/>
<plugin.dependencies/>
</properties>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>

41
maven-archetypes/quickstart/src/main/resources/archetype-resources/plugins/welcome/src/main/java/welcome/WelcomePlugin.java

@ -0,0 +1,41 @@
package ${package}.welcome;
import org.apache.commons.lang.StringUtils;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.pf4j.Extension;
import org.pf4j.Plugin;
import ${package}.Greeting;
public class WelcomePlugin extends Plugin {
public WelcomePlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
public void start() {
System.out.println("WelcomePlugin.start()");
// for testing the development mode
if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
System.out.println(StringUtils.upperCase("WelcomePlugin"));
}
}
@Override
public void stop() {
System.out.println("WelcomePlugin.stop()");
}
@Extension
public static class WelcomeGreeting implements Greeting {
@Override
public String getGreeting() {
return "Welcome";
}
}
}

48
maven-archetypes/quickstart/src/main/resources/archetype-resources/pom.xml

@ -0,0 +1,48 @@
<?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>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<packaging>pom</packaging>
<name>PF4J Quickstart</name>
<repositories>
<repository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>8</maven.compiler.release>
<pf4j.version>{{project.version}}</pf4j.version>
<slf4j.version>1.7.7</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>
</build>
</project>

23
maven-archetypes/quickstart/src/main/resources/archetype-resources/run.sh

@ -0,0 +1,23 @@
#!/bin/sh
# create artifacts using Maven
mvn clean package -DskipTests
# create "dist" directory
rm -fr dist
mkdir -p dist/plugins
# copy plugins to "dist" directory
cp plugins/*/target/*-all.jar dist/plugins/
cp plugins/enabled.txt dist/plugins/
cp plugins/disabled.txt dist/plugins/
cd dist
# unzip app to "dist" directory
jar xf ../app/target/*.zip
# run app
java -jar *.jar
cd -

43
pf4j/pom.xml

@ -4,12 +4,12 @@
<parent> <parent>
<groupId>org.pf4j</groupId> <groupId>org.pf4j</groupId>
<artifactId>pf4j-parent</artifactId> <artifactId>pf4j-parent</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>pf4j</artifactId> <artifactId>pf4j</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>PF4J</name> <name>PF4J</name>
<description>Plugin Framework for Java</description> <description>Plugin Framework for Java</description>
@ -22,6 +22,35 @@
<configuration> <configuration>
<compilerArgument>-proc:none</compilerArgument> <compilerArgument>-proc:none</compilerArgument>
</configuration> </configuration>
<executions>
<!-- compile everything for Java 8 except the module-info.java -->
<execution>
<id>default-compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<excludes>
<exclude>module-info.java</exclude>
</excludes>
</configuration>
</execution>
<!-- compile module-info.java for Java 9+ -->
<execution>
<id>java9-compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>9</release>
<multiReleaseOutput>true</multiReleaseOutput>
<includes>
<include>module-info.java</include>
</includes>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -29,7 +58,7 @@
<configuration> <configuration>
<archive> <archive>
<manifestEntries> <manifestEntries>
<Automatic-Module-Name>org.pf4j</Automatic-Module-Name> <Multi-Release>true</Multi-Release>
</manifestEntries> </manifestEntries>
</archive> </archive>
</configuration> </configuration>
@ -69,7 +98,8 @@
</dependency> </dependency>
<dependency> <dependency>
<!-- An empty artifact, required while JUnit 4 is on the classpath to override its <!--
An empty artifact, required while JUnit 4 is on the classpath to override its
dependency on hamcrest. dependency on hamcrest.
See http://hamcrest.org/JavaHamcrest/distributables#upgrading-from-hamcrest-1x See http://hamcrest.org/JavaHamcrest/distributables#upgrading-from-hamcrest-1x
@ -81,11 +111,12 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version> <version>${junit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>

47
pf4j/src/main/java/module-info.java

@ -0,0 +1,47 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.
*/
/**
* Module descriptor for PF4J.
*
* @author Decebal Suiu
* @author Andreas Rudolph
*/
module org.pf4j {
requires java.base;
// provides javax.annotation
requires java.compiler;
// provided by the ASM library
requires org.objectweb.asm;
// The SLF4J library currently does not provide a module.
// Version 1.8 provides a module called "org.slf4j". But this version is
// currently in beta stage. Therefore I'm not sure, if we already like to
// use it.
requires slf4j.api;
// The java-semver library currently does not provide a module.
// Maybe we should send them a pull request, that at least they provide an
// automatic module name in their MANIFEST file.
requires java.semver;
// Maybe we should reconsider the package hierarchy, that only classes are
// exported, which are required by 3rd party developers.
exports org.pf4j;
exports org.pf4j.processor;
}

163
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java

@ -15,6 +15,10 @@
*/ */
package org.pf4j; package org.pf4j;
import org.pf4j.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -27,9 +31,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.pf4j.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* This class implements the boilerplate plugin code that any {@link PluginManager} * This class implements the boilerplate plugin code that any {@link PluginManager}
@ -44,6 +45,12 @@ public abstract class AbstractPluginManager implements PluginManager {
private static final Logger log = LoggerFactory.getLogger(AbstractPluginManager.class); private static final Logger log = LoggerFactory.getLogger(AbstractPluginManager.class);
public static final String PLUGINS_DIR_PROPERTY_NAME = "pf4j.pluginsDir";
public static final String MODE_PROPERTY_NAME = "pf4j.mode";
public static final String DEFAULT_PLUGINS_DIR = "plugins";
public static final String DEVELOPMENT_PLUGINS_DIR = "../plugins";
private Path pluginsRoot; private Path pluginsRoot;
protected ExtensionFinder extensionFinder; protected ExtensionFinder extensionFinder;
@ -180,18 +187,12 @@ public abstract class AbstractPluginManager implements PluginManager {
log.debug("Loading plugin from '{}'", pluginPath); log.debug("Loading plugin from '{}'", pluginPath);
try {
PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath); PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath);
// try to resolve the loaded plugin together with other possible plugins that depend on this plugin // try to resolve the loaded plugin together with other possible plugins that depend on this plugin
resolvePlugins(); resolvePlugins();
return pluginWrapper.getDescriptor().getPluginId(); return pluginWrapper.getDescriptor().getPluginId();
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return null;
} }
/** /**
@ -221,7 +222,7 @@ public abstract class AbstractPluginManager implements PluginManager {
for (Path pluginPath : pluginPaths) { for (Path pluginPath : pluginPaths) {
try { try {
loadPluginFromPath(pluginPath); loadPluginFromPath(pluginPath);
} catch (PluginException e) { } catch (PluginRuntimeException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }
@ -229,7 +230,7 @@ public abstract class AbstractPluginManager implements PluginManager {
// resolve plugins // resolve plugins
try { try {
resolvePlugins(); resolvePlugins();
} catch (PluginException e) { } catch (PluginRuntimeException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }
@ -275,7 +276,7 @@ public abstract class AbstractPluginManager implements PluginManager {
try { try {
((Closeable) classLoader).close(); ((Closeable) classLoader).close();
} catch (IOException e) { } catch (IOException e) {
log.error("Cannot close classloader", e); throw new PluginRuntimeException(e, "Cannot close classloader");
} }
} }
} }
@ -293,23 +294,24 @@ public abstract class AbstractPluginManager implements PluginManager {
checkPluginId(pluginId); checkPluginId(pluginId);
PluginWrapper pluginWrapper = getPlugin(pluginId); PluginWrapper pluginWrapper = getPlugin(pluginId);
// stop the plugin if it's started
PluginState pluginState = stopPlugin(pluginId); PluginState pluginState = stopPlugin(pluginId);
if (PluginState.STARTED == pluginState) { if (PluginState.STARTED == pluginState) {
log.error("Failed to stop plugin '{}' on delete", pluginId); log.error("Failed to stop plugin '{}' on delete", pluginId);
return false; return false;
} }
// get an instance of plugin before the plugin is unloaded
// for reason see https://github.com/pf4j/pf4j/issues/309
Plugin plugin = pluginWrapper.getPlugin();
if (!unloadPlugin(pluginId)) { if (!unloadPlugin(pluginId)) {
log.error("Failed to unload plugin '{}' on delete", pluginId); log.error("Failed to unload plugin '{}' on delete", pluginId);
return false; return false;
} }
try { // notify the plugin as it's deleted
pluginWrapper.getPlugin().delete(); plugin.delete();
} catch (PluginException e) {
log.error(e.getMessage(), e);
return false;
}
Path pluginPath = pluginWrapper.getPluginPath(); Path pluginPath = pluginWrapper.getPluginPath();
@ -369,16 +371,12 @@ public abstract class AbstractPluginManager implements PluginManager {
startPlugin(dependency.getPluginId()); startPlugin(dependency.getPluginId());
} }
try {
log.info("Start plugin '{}'", getPluginLabel(pluginDescriptor)); log.info("Start plugin '{}'", getPluginLabel(pluginDescriptor));
pluginWrapper.getPlugin().start(); pluginWrapper.getPlugin().start();
pluginWrapper.setPluginState(PluginState.STARTED); pluginWrapper.setPluginState(PluginState.STARTED);
startedPlugins.add(pluginWrapper); startedPlugins.add(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState(); return pluginWrapper.getPluginState();
} }
@ -402,7 +400,7 @@ public abstract class AbstractPluginManager implements PluginManager {
itr.remove(); itr.remove();
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) { } catch (PluginRuntimeException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }
@ -443,16 +441,12 @@ public abstract class AbstractPluginManager implements PluginManager {
} }
} }
try {
log.info("Stop plugin '{}'", getPluginLabel(pluginDescriptor)); log.info("Stop plugin '{}'", getPluginLabel(pluginDescriptor));
pluginWrapper.getPlugin().stop(); pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED); pluginWrapper.setPluginState(PluginState.STOPPED);
startedPlugins.remove(pluginWrapper); startedPlugins.remove(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState(); return pluginWrapper.getPluginState();
} }
@ -480,10 +474,7 @@ public abstract class AbstractPluginManager implements PluginManager {
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED)); firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
if (!pluginStatusProvider.disablePlugin(pluginId)) { pluginStatusProvider.disablePlugin(pluginId);
return false;
}
log.info("Disabled plugin '{}'", getPluginLabel(pluginDescriptor)); log.info("Disabled plugin '{}'", getPluginLabel(pluginDescriptor));
return true; return true;
@ -509,9 +500,7 @@ public abstract class AbstractPluginManager implements PluginManager {
return true; return true;
} }
if (!pluginStatusProvider.enablePlugin(pluginId)) { pluginStatusProvider.enablePlugin(pluginId);
return false;
}
pluginWrapper.setPluginState(PluginState.CREATED); pluginWrapper.setPluginState(PluginState.CREATED);
@ -539,55 +528,28 @@ public abstract class AbstractPluginManager implements PluginManager {
Class<?> c = extensionWrapper.getDescriptor().extensionClass; Class<?> c = extensionWrapper.getDescriptor().extensionClass;
extensionClasses.add(c); extensionClasses.add(c);
} }
return extensionClasses;
}
@SuppressWarnings("unchecked")
@Override
public <T> List<Class<T>> getExtensionClasses(Class<T> type) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
List<Class<T>> extensionClasses = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
Class<T> c = (Class<T>) extensionWrapper.getDescriptor().extensionClass;
extensionClasses.add(c);
}
return extensionClasses; return extensionClasses;
} }
@SuppressWarnings("unchecked")
@Override @Override
public <T> List<Class<T>> getExtensionClasses(Class<T> type, String pluginId) { public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type, pluginId); return getExtensionClasses(extensionFinder.find(type));
List<Class<T>> extensionClasses = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
Class<T> c = (Class<T>) extensionWrapper.getDescriptor().extensionClass;
extensionClasses.add(c);
} }
return extensionClasses; @Override
public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type, String pluginId) {
return getExtensionClasses(extensionFinder.find(type, pluginId));
} }
@Override @Override
public <T> List<T> getExtensions(Class<T> type) { public <T> List<T> getExtensions(Class<T> type) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type); return getExtensions(extensionFinder.find(type));
List<T> extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
return extensions;
} }
@Override @Override
public <T> List<T> getExtensions(Class<T> type, String pluginId) { public <T> List<T> getExtensions(Class<T> type, String pluginId) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type, pluginId); return getExtensions(extensionFinder.find(type, pluginId));
List<T> extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
return extensions;
} }
@Override @Override
@ -596,7 +558,11 @@ public abstract class AbstractPluginManager implements PluginManager {
List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId); List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
List extensions = new ArrayList<>(extensionsWrapper.size()); List extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper extensionWrapper : extensionsWrapper) { for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
try {
extensions.add(extensionWrapper.getExtension()); extensions.add(extensionWrapper.getExtension());
} catch (PluginRuntimeException e) {
log.error("Cannot retrieve extension", e);
}
} }
return extensions; return extensions;
@ -612,7 +578,6 @@ public abstract class AbstractPluginManager implements PluginManager {
return extensionFactory; return extensionFactory;
} }
// TODO remove
public PluginLoader getPluginLoader() { public PluginLoader getPluginLoader() {
return pluginLoader; return pluginLoader;
} }
@ -625,7 +590,7 @@ public abstract class AbstractPluginManager implements PluginManager {
public RuntimeMode getRuntimeMode() { public RuntimeMode getRuntimeMode() {
if (runtimeMode == null) { if (runtimeMode == null) {
// retrieves the runtime mode from system // retrieves the runtime mode from system
String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString()); String modeAsString = System.getProperty(MODE_PROPERTY_NAME, RuntimeMode.DEPLOYMENT.toString());
runtimeMode = RuntimeMode.byName(modeAsString); runtimeMode = RuntimeMode.byName(modeAsString);
} }
@ -723,20 +688,16 @@ public abstract class AbstractPluginManager implements PluginManager {
/** /**
* Add the possibility to override the plugins root. * Add the possibility to override the plugins root.
* If a {@code pf4j.pluginsDir} system property is defined than this method returns that root. * If a {@link #PLUGINS_DIR_PROPERTY_NAME} system property is defined than this method returns that root.
* If {@link #getRuntimeMode()} returns {@link RuntimeMode#DEVELOPMENT} than {@code ../plugins} * If {@link #getRuntimeMode()} returns {@link RuntimeMode#DEVELOPMENT} than {@link #DEVELOPMENT_PLUGINS_DIR}
* is returned else this method returns {@code plugins}. * is returned else this method returns {@link #DEFAULT_PLUGINS_DIR}.
* *
* @return the plugins root * @return the plugins root
*/ */
protected Path createPluginsRoot() { protected Path createPluginsRoot() {
String pluginsDir = System.getProperty("pf4j.pluginsDir"); String pluginsDir = System.getProperty(PLUGINS_DIR_PROPERTY_NAME);
if (pluginsDir == null) { if (pluginsDir == null) {
if (isDevelopment()) { pluginsDir = isDevelopment() ? DEVELOPMENT_PLUGINS_DIR : DEFAULT_PLUGINS_DIR;
pluginsDir = "../plugins";
} else {
pluginsDir = "plugins";
}
} }
return Paths.get(pluginsDir); return Paths.get(pluginsDir);
@ -771,7 +732,7 @@ public abstract class AbstractPluginManager implements PluginManager {
return pluginStatusProvider.isPluginDisabled(pluginId); return pluginStatusProvider.isPluginDisabled(pluginId);
} }
protected void resolvePlugins() throws PluginException { protected void resolvePlugins() {
// retrieves the plugins descriptors // retrieves the plugins descriptors
List<PluginDescriptor> descriptors = new ArrayList<>(); List<PluginDescriptor> descriptors = new ArrayList<>();
for (PluginWrapper plugin : plugins.values()) { for (PluginWrapper plugin : plugins.values()) {
@ -820,7 +781,7 @@ public abstract class AbstractPluginManager implements PluginManager {
} }
} }
protected PluginWrapper loadPluginFromPath(Path pluginPath) throws PluginException { protected PluginWrapper loadPluginFromPath(Path pluginPath) {
// Test for plugin path duplication // Test for plugin path duplication
String pluginId = idForPath(pluginPath); String pluginId = idForPath(pluginPath);
if (pluginId != null) { if (pluginId != null) {
@ -838,7 +799,7 @@ public abstract class AbstractPluginManager implements PluginManager {
pluginId = pluginDescriptor.getPluginId(); pluginId = pluginDescriptor.getPluginId();
if (plugins.containsKey(pluginId)) { if (plugins.containsKey(pluginId)) {
PluginWrapper loadedPlugin = getPlugin(pluginId); PluginWrapper loadedPlugin = getPlugin(pluginId);
throw new PluginException("There is an already loaded plugin ({}) " throw new PluginRuntimeException("There is an already loaded plugin ({}) "
+ "with the same id ({}) as the plugin at path '{}'. Simultaneous loading " + "with the same id ({}) as the plugin at path '{}'. Simultaneous loading "
+ "of plugins with the same PluginId is not currently supported.\n" + "of plugins with the same PluginId is not currently supported.\n"
+ "As a workaround you may include PluginVersion and PluginProvider " + "As a workaround you may include PluginVersion and PluginProvider "
@ -859,7 +820,6 @@ public abstract class AbstractPluginManager implements PluginManager {
log.debug("Creating wrapper for plugin '{}'", pluginPath); log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader); PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
pluginWrapper.setPluginFactory(getPluginFactory()); pluginWrapper.setPluginFactory(getPluginFactory());
pluginWrapper.setRuntimeMode(getRuntimeMode());
// test for disabled plugin // test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) { if (isPluginDisabled(pluginDescriptor.getPluginId())) {
@ -907,21 +867,16 @@ public abstract class AbstractPluginManager implements PluginManager {
* Override this to change the validation criteria. * Override this to change the validation criteria.
* *
* @param descriptor the plugin descriptor to validate * @param descriptor the plugin descriptor to validate
* @throws PluginException if validation fails * @throws PluginRuntimeException if validation fails
*/ */
protected void validatePluginDescriptor(PluginDescriptor descriptor) throws PluginException { protected void validatePluginDescriptor(PluginDescriptor descriptor) {
if (StringUtils.isNullOrEmpty(descriptor.getPluginId())) { if (StringUtils.isNullOrEmpty(descriptor.getPluginId())) {
throw new PluginException("Field 'id' cannot be empty"); throw new PluginRuntimeException("Field 'id' cannot be empty");
} }
if (descriptor.getVersion() == null) { if (descriptor.getVersion() == null) {
throw new PluginException("Field 'version' cannot be empty"); throw new PluginRuntimeException("Field 'version' cannot be empty");
}
} }
// TODO add this method in PluginManager as default method for Java 8.
protected boolean isDevelopment() {
return RuntimeMode.DEVELOPMENT.equals(getRuntimeMode());
} }
/** /**
@ -954,4 +909,28 @@ public abstract class AbstractPluginManager implements PluginManager {
return pluginDescriptor.getPluginId() + "@" + pluginDescriptor.getVersion(); return pluginDescriptor.getPluginId() + "@" + pluginDescriptor.getVersion();
} }
@SuppressWarnings("unchecked")
private <T> List<Class<? extends T>> getExtensionClasses(List<ExtensionWrapper<T>> extensionsWrapper) {
List<Class<? extends T>> extensionClasses = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
Class<T> c = (Class<T>) extensionWrapper.getDescriptor().extensionClass;
extensionClasses.add(c);
}
return extensionClasses;
}
private <T> List<T> getExtensions(List<ExtensionWrapper<T>> extensionsWrapper) {
List<T> extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
try {
extensions.add(extensionWrapper.getExtension());
} catch (PluginRuntimeException e) {
log.error("Cannot retrieve extension", e);
}
}
return extensions;
}
} }

88
pf4j/src/main/java/org/pf4j/BasePluginLoader.java

@ -0,0 +1,88 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.pf4j.util.FileUtils;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
/**
* Load all information needed by a plugin.
* This means add to the plugin's {@link ClassLoader} all the jar files and
* all the class files specified in the {@link PluginClasspath}.
*
* @author Decebal Suiu
*/
public class BasePluginLoader implements PluginLoader {
protected PluginManager pluginManager;
protected PluginClasspath pluginClasspath;
public BasePluginLoader(PluginManager pluginManager, PluginClasspath pluginClasspath) {
this.pluginManager = pluginManager;
this.pluginClasspath = pluginClasspath;
}
@Override
public boolean isApplicable(Path pluginPath) {
return Files.exists(pluginPath);
}
@Override
public ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor) {
PluginClassLoader pluginClassLoader = createPluginClassLoader(pluginPath, pluginDescriptor);
loadClasses(pluginPath, pluginClassLoader);
loadJars(pluginPath, pluginClassLoader);
return pluginClassLoader;
}
protected PluginClassLoader createPluginClassLoader(Path pluginPath, PluginDescriptor pluginDescriptor) {
return new PluginClassLoader(pluginManager, pluginDescriptor, getClass().getClassLoader());
}
/**
* Add all {@code *.class} files from {@link PluginClasspath#getClassesDirectories()}
* to the plugin's {@link ClassLoader}.
*/
protected void loadClasses(Path pluginPath, PluginClassLoader pluginClassLoader) {
for (String directory : pluginClasspath.getClassesDirectories()) {
File file = pluginPath.resolve(directory).toFile();
if (file.exists() && file.isDirectory()) {
pluginClassLoader.addFile(file);
}
}
}
/**
* Add all {@code *.jar} files from {@link PluginClasspath#getJarsDirectories()}
* to the plugin's {@link ClassLoader}.
*/
protected void loadJars(Path pluginPath, PluginClassLoader pluginClassLoader) {
for (String jarsDirectory : pluginClasspath.getJarsDirectories()) {
Path file = pluginPath.resolve(jarsDirectory);
List<File> jars = FileUtils.getJars(file);
for (File jar : jars) {
pluginClassLoader.addFile(jar);
}
}
}
}

19
pf4j/src/main/java/org/pf4j/BasePluginRepository.java

@ -48,14 +48,7 @@ public class BasePluginRepository implements PluginRepository {
this.filter = filter; this.filter = filter;
// last modified file is first // last modified file is first
this.comparator = new Comparator<File>() { this.comparator = (o1, o2) -> (int) (o2.lastModified() - o1.lastModified());
@Override
public int compare(File o1, File o2) {
return (int) (o2.lastModified() - o1.lastModified());
}
};
} }
public void setFilter(FileFilter filter) { public void setFilter(FileFilter filter) {
@ -94,13 +87,17 @@ public class BasePluginRepository implements PluginRepository {
@Override @Override
public boolean deletePluginPath(Path pluginPath) { public boolean deletePluginPath(Path pluginPath) {
if (!filter.accept(pluginPath.toFile())) {
return false;
}
try { try {
FileUtils.delete(pluginPath); FileUtils.delete(pluginPath);
return true; return true;
} catch (NoSuchFileException nsf) { } catch (NoSuchFileException e) {
return false; // Return false on not found to be compatible with previous API return false; // Return false on not found to be compatible with previous API (#135)
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new PluginRuntimeException(e);
} }
} }

4
pf4j/src/main/java/org/pf4j/CompoundPluginDescriptorFinder.java

@ -57,7 +57,7 @@ public class CompoundPluginDescriptorFinder implements PluginDescriptorFinder {
} }
@Override @Override
public PluginDescriptor find(Path pluginPath) throws PluginException { public PluginDescriptor find(Path pluginPath) {
for (PluginDescriptorFinder finder : finders) { for (PluginDescriptorFinder finder : finders) {
if (finder.isApplicable(pluginPath)) { if (finder.isApplicable(pluginPath)) {
log.debug("'{}' is applicable for plugin '{}'", finder, pluginPath); log.debug("'{}' is applicable for plugin '{}'", finder, pluginPath);
@ -81,7 +81,7 @@ public class CompoundPluginDescriptorFinder implements PluginDescriptorFinder {
} }
} }
throw new PluginException("No PluginDescriptorFinder for plugin '{}'", pluginPath); throw new PluginRuntimeException("No PluginDescriptorFinder for plugin '{}'", pluginPath);
} }
} }

16
pf4j/src/main/java/org/pf4j/CompoundPluginLoader.java

@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.BooleanSupplier;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
@ -41,6 +42,21 @@ public class CompoundPluginLoader implements PluginLoader {
return this; return this;
} }
/**
* Add a {@link PluginLoader} only if the {@code condition} is satisfied.
*
* @param loader
* @param condition
* @return
*/
public CompoundPluginLoader add(PluginLoader loader, BooleanSupplier condition) {
if (condition.getAsBoolean()) {
return add(loader);
}
return this;
}
public int size() { public int size() {
return loaders.size(); return loaders.size();
} }

22
pf4j/src/main/java/org/pf4j/CompoundPluginRepository.java

@ -17,7 +17,10 @@ package org.pf4j;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.BooleanSupplier;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
@ -37,14 +40,29 @@ public class CompoundPluginRepository implements PluginRepository {
return this; return this;
} }
/**
* Add a {@link PluginRepository} only if the {@code condition} is satisfied.
*
* @param repository
* @param condition
* @return
*/
public CompoundPluginRepository add(PluginRepository repository, BooleanSupplier condition) {
if (condition.getAsBoolean()) {
return add(repository);
}
return this;
}
@Override @Override
public List<Path> getPluginPaths() { public List<Path> getPluginPaths() {
List<Path> paths = new ArrayList<>(); Set<Path> paths = new LinkedHashSet<>();
for (PluginRepository repository : repositories) { for (PluginRepository repository : repositories) {
paths.addAll(repository.getPluginPaths()); paths.addAll(repository.getPluginPaths());
} }
return paths; return new ArrayList<>(paths);
} }
@Override @Override

10
pf4j/src/main/java/org/pf4j/DefaultExtensionFactory.java

@ -29,20 +29,16 @@ public class DefaultExtensionFactory implements ExtensionFactory {
private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFactory.class); private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFactory.class);
/** /**
* Creates an extension instance. If an error occurs than that error is logged and the method returns {@code null}. * Creates an extension instance.
* @param extensionClass
* @return
*/ */
@Override @Override
public Object create(Class<?> extensionClass) { public <T> T create(Class<T> extensionClass) {
log.debug("Create instance for extension '{}'", extensionClass.getName()); log.debug("Create instance for extension '{}'", extensionClass.getName());
try { try {
return extensionClass.newInstance(); return extensionClass.newInstance();
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); throw new PluginRuntimeException(e);
} }
return null;
} }
} }

9
pf4j/src/main/java/org/pf4j/DefaultPluginClasspath.java

@ -16,17 +16,20 @@
package org.pf4j; package org.pf4j;
/** /**
* The default values are {@code classes} and {@code lib}. * The default values are {@link #CLASSES_DIR} and {@code #LIB_DIR}.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DefaultPluginClasspath extends PluginClasspath { public class DefaultPluginClasspath extends PluginClasspath {
public static final String CLASSES_DIR = "classes";
public static final String LIB_DIR = "lib";
public DefaultPluginClasspath() { public DefaultPluginClasspath() {
super(); super();
addClassesDirectories("classes"); addClassesDirectories(CLASSES_DIR);
addLibDirectories("lib"); addJarsDirectories(LIB_DIR);
} }
} }

59
pf4j/src/main/java/org/pf4j/DefaultPluginLoader.java

@ -15,72 +15,23 @@
*/ */
package org.pf4j; package org.pf4j;
import org.pf4j.util.FileUtils;
import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
/** /**
* Load all information needed by a plugin. * Load all information needed by a plugin from {@link DefaultPluginClasspath}.
* This means add to classpath all jar files from {@code lib} directory
* and all class files from {@code classes}.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DefaultPluginLoader implements PluginLoader { public class DefaultPluginLoader extends BasePluginLoader {
protected PluginManager pluginManager;
protected PluginClasspath pluginClasspath;
public DefaultPluginLoader(PluginManager pluginManager, PluginClasspath pluginClasspath) { public DefaultPluginLoader(PluginManager pluginManager) {
this.pluginManager = pluginManager; super(pluginManager, new DefaultPluginClasspath());
this.pluginClasspath = pluginClasspath;
} }
@Override @Override
public boolean isApplicable(Path pluginPath) { public boolean isApplicable(Path pluginPath) {
return Files.exists(pluginPath) && Files.isDirectory(pluginPath); return super.isApplicable(pluginPath) && Files.isDirectory(pluginPath);
}
@Override
public ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor) {
PluginClassLoader pluginClassLoader = createPluginClassLoader(pluginPath, pluginDescriptor);
loadClasses(pluginPath, pluginClassLoader);
loadJars(pluginPath, pluginClassLoader);
return pluginClassLoader;
}
protected PluginClassLoader createPluginClassLoader(Path pluginPath, PluginDescriptor pluginDescriptor) {
return new PluginClassLoader(pluginManager, pluginDescriptor, getClass().getClassLoader());
}
/**
* Add all {@code *.class} files from {@code classes} directories to plugin class loader.
*/
protected void loadClasses(Path pluginPath, PluginClassLoader pluginClassLoader) {
for (String directory : pluginClasspath.getClassesDirectories()) {
File file = pluginPath.resolve(directory).toFile();
if (file.exists() && file.isDirectory()) {
pluginClassLoader.addFile(file);
}
}
}
/**
* Add all {@code *.jar} files from {@code lib} directories to plugin class loader.
*/
protected void loadJars(Path pluginPath, PluginClassLoader pluginClassLoader) {
for (String libDirectory : pluginClasspath.getLibDirectories()) {
Path file = pluginPath.resolve(libDirectory);
List<File> jars = FileUtils.getJars(file);
for (File jar : jars) {
pluginClassLoader.addFile(jar);
}
}
} }
} }

46
pf4j/src/main/java/org/pf4j/DefaultPluginManager.java

@ -19,12 +19,13 @@ import org.pf4j.util.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
/** /**
* Default implementation of the {@link PluginManager} interface. * Default implementation of the {@link PluginManager} interface.
* In essence it is a {@link ZipPluginManager} plus a {@link JarPluginManager}.
* So, it can load plugins from jar and zip, simultaneous.
* *
* <p>This class is not thread-safe. * <p>This class is not thread-safe.
* *
@ -34,22 +35,12 @@ public class DefaultPluginManager extends AbstractPluginManager {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class); private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
protected PluginClasspath pluginClasspath; public static final String PLUGINS_DIR_CONFIG_PROPERTY_NAME = "pf4j.pluginsConfigDir";
public DefaultPluginManager() { public DefaultPluginManager() {
super(); super();
} }
/**
* Use {@link DefaultPluginManager#DefaultPluginManager(Path)}.
*
* @param pluginsDir
*/
@Deprecated
public DefaultPluginManager(File pluginsDir) {
this(pluginsDir.toPath());
}
public DefaultPluginManager(Path pluginsRoot) { public DefaultPluginManager(Path pluginsRoot) {
super(pluginsRoot); super(pluginsRoot);
} }
@ -81,23 +72,26 @@ public class DefaultPluginManager extends AbstractPluginManager {
@Override @Override
protected PluginStatusProvider createPluginStatusProvider() { protected PluginStatusProvider createPluginStatusProvider() {
String configDir = System.getProperty("pf4j.pluginsConfigDir"); String configDir = System.getProperty(PLUGINS_DIR_CONFIG_PROPERTY_NAME);
Path configPath = configDir != null ? Paths.get(configDir) : getPluginsRoot(); Path configPath = configDir != null ? Paths.get(configDir) : getPluginsRoot();
return new DefaultPluginStatusProvider(configPath); return new DefaultPluginStatusProvider(configPath);
} }
@Override @Override
protected PluginRepository createPluginRepository() { protected PluginRepository createPluginRepository() {
return new CompoundPluginRepository() return new CompoundPluginRepository()
.add(new DefaultPluginRepository(getPluginsRoot(), isDevelopment())) .add(new DevelopmentPluginRepository(getPluginsRoot()), this::isDevelopment)
.add(new JarPluginRepository(getPluginsRoot())); .add(new JarPluginRepository(getPluginsRoot()), this::isNotDevelopment)
.add(new DefaultPluginRepository(getPluginsRoot()), this::isNotDevelopment);
} }
@Override @Override
protected PluginLoader createPluginLoader() { protected PluginLoader createPluginLoader() {
return new CompoundPluginLoader() return new CompoundPluginLoader()
.add(new DefaultPluginLoader(this, pluginClasspath)) .add(new DevelopmentPluginLoader(this), this::isDevelopment)
.add(new JarPluginLoader(this)); .add(new JarPluginLoader(this), this::isNotDevelopment)
.add(new DefaultPluginLoader(this), this::isNotDevelopment);
} }
@Override @Override
@ -105,19 +99,8 @@ public class DefaultPluginManager extends AbstractPluginManager {
return new DefaultVersionManager(); return new DefaultVersionManager();
} }
/**
* By default if {@link DefaultPluginManager#isDevelopment()} returns true
* than a {@link DevelopmentPluginClasspath} is returned
* else this method returns {@link DefaultPluginClasspath}.
*/
protected PluginClasspath createPluginClasspath() {
return isDevelopment() ? new DevelopmentPluginClasspath() : new DefaultPluginClasspath();
}
@Override @Override
protected void initialize() { protected void initialize() {
pluginClasspath = createPluginClasspath();
super.initialize(); super.initialize();
if (isDevelopment()) { if (isDevelopment()) {
@ -128,13 +111,14 @@ public class DefaultPluginManager extends AbstractPluginManager {
} }
/** /**
* Load a plugin from disk. If the path is a zip file, first unpack * Load a plugin from disk. If the path is a zip file, first unpack.
*
* @param pluginPath plugin location on disk * @param pluginPath plugin location on disk
* @return PluginWrapper for the loaded plugin or null if not loaded * @return PluginWrapper for the loaded plugin or null if not loaded
* @throws PluginException if problems during load * @throws PluginRuntimeException if problems during load
*/ */
@Override @Override
protected PluginWrapper loadPluginFromPath(Path pluginPath) throws PluginException { protected PluginWrapper loadPluginFromPath(Path pluginPath) {
// First unzip any ZIP files // First unzip any ZIP files
try { try {
pluginPath = FileUtils.expandIfZip(pluginPath); pluginPath = FileUtils.expandIfZip(pluginPath);

44
pf4j/src/main/java/org/pf4j/DefaultPluginRepository.java

@ -24,7 +24,6 @@ import org.pf4j.util.OrFileFilter;
import org.pf4j.util.ZipFileFilter; import org.pf4j.util.ZipFileFilter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.pf4j.util.NameFileFilter;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
@ -39,29 +38,17 @@ public class DefaultPluginRepository extends BasePluginRepository {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginRepository.class); private static final Logger log = LoggerFactory.getLogger(DefaultPluginRepository.class);
public DefaultPluginRepository(Path pluginsRoot, boolean development) { public DefaultPluginRepository(Path pluginsRoot) {
super(pluginsRoot); super(pluginsRoot);
AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter()); AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter(development))); pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter()));
setFilter(pluginsFilter); setFilter(pluginsFilter);
} }
@Override @Override
public List<Path> getPluginPaths() { public List<Path> getPluginPaths() {
// expand plugins zip files extractZipFiles();
File[] pluginZips = pluginsRoot.toFile().listFiles(new ZipFileFilter());
if ((pluginZips != null) && pluginZips.length > 0) {
for (File pluginZip : pluginZips) {
try {
FileUtils.expandIfZip(pluginZip.toPath());
} catch (IOException e) {
log.error("Cannot expand plugin zip '{}'", pluginZip);
log.error(e.getMessage(), e);
}
}
}
return super.getPluginPaths(); return super.getPluginPaths();
} }
@ -71,16 +58,23 @@ public class DefaultPluginRepository extends BasePluginRepository {
return super.deletePluginPath(pluginPath); return super.deletePluginPath(pluginPath);
} }
protected FileFilter createHiddenPluginFilter(boolean development) { protected FileFilter createHiddenPluginFilter() {
OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter()); return new OrFileFilter(new HiddenFilter());
if (development) {
// skip default build output folders since these will cause errors in the logs
hiddenPluginFilter
.addFileFilter(new NameFileFilter("target")) // MAVEN
.addFileFilter(new NameFileFilter("build")); // GRADLE
} }
return hiddenPluginFilter; private void extractZipFiles() {
// expand plugins zip files
File[] zipFiles = pluginsRoot.toFile().listFiles(new ZipFileFilter());
if ((zipFiles != null) && zipFiles.length > 0) {
for (File pluginZip : zipFiles) {
try {
FileUtils.expandIfZip(pluginZip.toPath());
} catch (IOException e) {
log.error("Cannot expand plugin zip '{}'", pluginZip);
log.error(e.getMessage(), e);
}
}
} }
} }
}

20
pf4j/src/main/java/org/pf4j/DefaultPluginStatusProvider.java

@ -66,31 +66,23 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
} }
@Override @Override
public boolean disablePlugin(String pluginId) { public void disablePlugin(String pluginId) {
if (disabledPlugins.add(pluginId)) { disabledPlugins.add(pluginId);
try { try {
FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile()); FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to disable plugin {}", pluginId, e); throw new PluginRuntimeException(e);
return false;
} }
} }
return true;
}
@Override @Override
public boolean enablePlugin(String pluginId) { public void enablePlugin(String pluginId) {
if (disabledPlugins.remove(pluginId)) { disabledPlugins.remove(pluginId);
try { try {
FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile()); FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to enable plugin {}", pluginId, e); throw new PluginRuntimeException(e);
return false;
} }
} }
return true;
}
} }

6
pf4j/src/main/java/org/pf4j/DependencyResolver.java

@ -272,7 +272,7 @@ public class DependencyResolver {
/** /**
* It will be thrown if a cyclic dependency is detected. * It will be thrown if a cyclic dependency is detected.
*/ */
public static class CyclicDependencyException extends PluginException { public static class CyclicDependencyException extends PluginRuntimeException {
public CyclicDependencyException() { public CyclicDependencyException() {
super("Cyclic dependencies"); super("Cyclic dependencies");
@ -283,7 +283,7 @@ public class DependencyResolver {
/** /**
* Indicates that the dependencies required were not found. * Indicates that the dependencies required were not found.
*/ */
public static class DependenciesNotFoundException extends PluginException { public static class DependenciesNotFoundException extends PluginRuntimeException {
private List<String> dependencies; private List<String> dependencies;
@ -302,7 +302,7 @@ public class DependencyResolver {
/** /**
* Indicates that some dependencies have wrong version. * Indicates that some dependencies have wrong version.
*/ */
public static class DependenciesWrongVersionException extends PluginException { public static class DependenciesWrongVersionException extends PluginRuntimeException {
private List<WrongDependencyVersion> dependencies; private List<WrongDependencyVersion> dependencies;

8
pf4j/src/main/java/org/pf4j/DevelopmentPluginClasspath.java

@ -27,7 +27,7 @@ public class DevelopmentPluginClasspath extends PluginClasspath {
* The development plugin classpath for <a href="https://maven.apache.org">Maven</a>. * The development plugin classpath for <a href="https://maven.apache.org">Maven</a>.
* The classes directory is {@code target/classes} and the lib directory is {@code target/lib}. * The classes directory is {@code target/classes} and the lib directory is {@code target/lib}.
*/ */
public static final PluginClasspath MAVEN = new PluginClasspath().addClassesDirectories("target/classes").addLibDirectories("target/lib"); public static final PluginClasspath MAVEN = new PluginClasspath().addClassesDirectories("target/classes").addJarsDirectories("target/lib");
/** /**
* The development plugin classpath for <a href="https://gradle.org">Gradle</a>. * The development plugin classpath for <a href="https://gradle.org">Gradle</a>.
@ -46,9 +46,9 @@ public class DevelopmentPluginClasspath extends PluginClasspath {
addClassesDirectories(GRADLE.getClassesDirectories()); addClassesDirectories(GRADLE.getClassesDirectories());
addClassesDirectories(KOTLIN.getClassesDirectories()); addClassesDirectories(KOTLIN.getClassesDirectories());
addLibDirectories(MAVEN.getLibDirectories()); addJarsDirectories(MAVEN.getJarsDirectories());
addLibDirectories(GRADLE.getLibDirectories()); addJarsDirectories(GRADLE.getJarsDirectories());
addLibDirectories(KOTLIN.getLibDirectories()); addJarsDirectories(KOTLIN.getJarsDirectories());
} }
} }

29
pf4j/src/main/java/org/pf4j/DevelopmentPluginLoader.java

@ -0,0 +1,29 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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;
/**
* Load all information needed by a plugin from {@link DevelopmentPluginClasspath}.
*
* @author Decebal Suiu
*/
public class DevelopmentPluginLoader extends BasePluginLoader {
public DevelopmentPluginLoader(PluginManager pluginManager) {
super(pluginManager, new DevelopmentPluginClasspath());
}
}

55
pf4j/src/main/java/org/pf4j/DevelopmentPluginRepository.java

@ -0,0 +1,55 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.pf4j.util.AndFileFilter;
import org.pf4j.util.DirectoryFileFilter;
import org.pf4j.util.HiddenFilter;
import org.pf4j.util.NameFileFilter;
import org.pf4j.util.NotFileFilter;
import org.pf4j.util.OrFileFilter;
import java.io.FileFilter;
import java.nio.file.Path;
/**
* @author Decebal Suiu
*/
public class DevelopmentPluginRepository extends BasePluginRepository {
public static final String MAVEN_BUILD_DIR = "target";
public static final String GRADLE_BUILD_DIR = "build";
public DevelopmentPluginRepository(Path pluginsRoot) {
super(pluginsRoot);
AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter()));
setFilter(pluginsFilter);
}
protected FileFilter createHiddenPluginFilter() {
OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
// skip default build output folders since these will cause errors in the logs
hiddenPluginFilter
.addFileFilter(new NameFileFilter(MAVEN_BUILD_DIR))
.addFileFilter(new NameFileFilter(GRADLE_BUILD_DIR));
return hiddenPluginFilter;
}
}

2
pf4j/src/main/java/org/pf4j/ExtensionFactory.java

@ -20,6 +20,6 @@ package org.pf4j;
*/ */
public interface ExtensionFactory { public interface ExtensionFactory {
Object create(Class<?> extensionClass); <T> T create(Class<T> extensionClass);
} }

56
pf4j/src/main/java/org/pf4j/JarPluginManager.java

@ -0,0 +1,56 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.nio.file.Path;
/**
* It's a {@link PluginManager} that loads each plugin from a {@code jar} file.
* Actually, a plugin is a fat jar, a jar which contains classes from all the libraries,
* on which your project depends and, of course, the classes of current project.
*
* @author Decebal Suiu
*/
public class JarPluginManager extends DefaultPluginManager {
public JarPluginManager() {
super();
}
public JarPluginManager(Path pluginsRoot) {
super(pluginsRoot);
}
@Override
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new ManifestPluginDescriptorFinder();
}
@Override
protected PluginLoader createPluginLoader() {
return new CompoundPluginLoader()
.add(new DevelopmentPluginLoader(this), this::isDevelopment)
.add(new JarPluginLoader(this), this::isNotDevelopment);
}
@Override
protected PluginRepository createPluginRepository() {
return new CompoundPluginRepository()
.add(new DevelopmentPluginRepository(getPluginsRoot()), this::isDevelopment)
.add(new JarPluginRepository(getPluginsRoot()), this::isNotDevelopment);
}
}

20
pf4j/src/main/java/org/pf4j/LegacyExtensionFinder.java

@ -20,6 +20,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.net.URL; import java.net.URL;
@ -81,11 +82,14 @@ public class LegacyExtensionFinder extends AbstractExtensionFinder {
Set<String> bucket = new HashSet<>(); Set<String> bucket = new HashSet<>();
try { try {
Enumeration<URL> urls = ((PluginClassLoader) plugin.getPluginClassLoader()).findResources(getExtensionsResource()); log.debug("Read '{}'", getExtensionsResource());
if (urls.hasMoreElements()) { ClassLoader pluginClassLoader = plugin.getPluginClassLoader();
collectExtensions(urls, bucket); try (InputStream resourceStream = pluginClassLoader.getResourceAsStream(getExtensionsResource())) {
} else { if (resourceStream == null) {
log.debug("Cannot find '{}'", getExtensionsResource()); log.debug("Cannot find '{}'", getExtensionsResource());
} else {
collectExtensions(resourceStream, bucket);
}
} }
debugExtensions(bucket); debugExtensions(bucket);
@ -103,9 +107,13 @@ public class LegacyExtensionFinder extends AbstractExtensionFinder {
while (urls.hasMoreElements()) { while (urls.hasMoreElements()) {
URL url = urls.nextElement(); URL url = urls.nextElement();
log.debug("Read '{}'", url.getFile()); log.debug("Read '{}'", url.getFile());
try (Reader reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) { collectExtensions(url.openStream(), bucket);
LegacyExtensionStorage.read(reader, bucket); }
} }
private void collectExtensions(InputStream inputStream, Set<String> bucket) throws IOException {
try (Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
LegacyExtensionStorage.read(reader, bucket);
} }
} }

39
pf4j/src/main/java/org/pf4j/ManifestPluginDescriptorFinder.java

@ -37,19 +37,28 @@ public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class); private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
public static final String PLUGIN_ID = "Plugin-Id";
public static final String PLUGIN_DESCRIPTION = "Plugin-Description";
public static final String PLUGIN_CLASS = "Plugin-Class";
public static final String PLUGIN_VERSION = "Plugin-Version";
public static final String PLUGIN_PROVIDER = "Plugin-Provider";
public static final String PLUGIN_DEPENDENCIES = "Plugin-Dependencies";
public static final String PLUGIN_REQUIRES = "Plugin-Requires";
public static final String PLUGIN_LICENSE = "Plugin-License";
@Override @Override
public boolean isApplicable(Path pluginPath) { public boolean isApplicable(Path pluginPath) {
return Files.exists(pluginPath) && (Files.isDirectory(pluginPath) || FileUtils.isJarFile(pluginPath)); return Files.exists(pluginPath) && (Files.isDirectory(pluginPath) || FileUtils.isJarFile(pluginPath));
} }
@Override @Override
public PluginDescriptor find(Path pluginPath) throws PluginException { public PluginDescriptor find(Path pluginPath) {
Manifest manifest = readManifest(pluginPath); Manifest manifest = readManifest(pluginPath);
return createPluginDescriptor(manifest); return createPluginDescriptor(manifest);
} }
protected Manifest readManifest(Path pluginPath) throws PluginException { protected Manifest readManifest(Path pluginPath) {
if (FileUtils.isJarFile(pluginPath)) { if (FileUtils.isJarFile(pluginPath)) {
try (JarFile jar = new JarFile(pluginPath.toFile())) { try (JarFile jar = new JarFile(pluginPath.toFile())) {
Manifest manifest = jar.getManifest(); Manifest manifest = jar.getManifest();
@ -57,28 +66,28 @@ public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
return manifest; return manifest;
} }
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginRuntimeException(e);
} }
} }
Path manifestPath = getManifestPath(pluginPath); Path manifestPath = getManifestPath(pluginPath);
if (manifestPath == null) { if (manifestPath == null) {
throw new PluginException("Cannot find the manifest path"); throw new PluginRuntimeException("Cannot find the manifest path");
} }
log.debug("Lookup plugin descriptor in '{}'", manifestPath); log.debug("Lookup plugin descriptor in '{}'", manifestPath);
if (Files.notExists(manifestPath)) { if (Files.notExists(manifestPath)) {
throw new PluginException("Cannot find '{}' path", manifestPath); throw new PluginRuntimeException("Cannot find '{}' path", manifestPath);
} }
try (InputStream input = Files.newInputStream(manifestPath)) { try (InputStream input = Files.newInputStream(manifestPath)) {
return new Manifest(input); return new Manifest(input);
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginRuntimeException(e);
} }
} }
protected Path getManifestPath(Path pluginPath) throws PluginException { protected Path getManifestPath(Path pluginPath) {
if (Files.isDirectory(pluginPath)) { if (Files.isDirectory(pluginPath)) {
// legacy (the path is something like "classes/META-INF/MANIFEST.MF") // legacy (the path is something like "classes/META-INF/MANIFEST.MF")
return FileUtils.findFile(pluginPath,"MANIFEST.MF"); return FileUtils.findFile(pluginPath,"MANIFEST.MF");
@ -92,37 +101,37 @@ public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
// TODO validate !!! // TODO validate !!!
Attributes attributes = manifest.getMainAttributes(); Attributes attributes = manifest.getMainAttributes();
String id = attributes.getValue("Plugin-Id"); String id = attributes.getValue(PLUGIN_ID);
pluginDescriptor.setPluginId(id); pluginDescriptor.setPluginId(id);
String description = attributes.getValue("Plugin-Description"); String description = attributes.getValue(PLUGIN_DESCRIPTION);
if (StringUtils.isNullOrEmpty(description)) { if (StringUtils.isNullOrEmpty(description)) {
pluginDescriptor.setPluginDescription(""); pluginDescriptor.setPluginDescription("");
} else { } else {
pluginDescriptor.setPluginDescription(description); pluginDescriptor.setPluginDescription(description);
} }
String clazz = attributes.getValue("Plugin-Class"); String clazz = attributes.getValue(PLUGIN_CLASS);
if (StringUtils.isNotNullOrEmpty(clazz)) { if (StringUtils.isNotNullOrEmpty(clazz)) {
pluginDescriptor.setPluginClass(clazz); pluginDescriptor.setPluginClass(clazz);
} }
String version = attributes.getValue("Plugin-Version"); String version = attributes.getValue(PLUGIN_VERSION);
if (StringUtils.isNotNullOrEmpty(version)) { if (StringUtils.isNotNullOrEmpty(version)) {
pluginDescriptor.setPluginVersion(version); pluginDescriptor.setPluginVersion(version);
} }
String provider = attributes.getValue("Plugin-Provider"); String provider = attributes.getValue(PLUGIN_PROVIDER);
pluginDescriptor.setProvider(provider); pluginDescriptor.setProvider(provider);
String dependencies = attributes.getValue("Plugin-Dependencies"); String dependencies = attributes.getValue(PLUGIN_DEPENDENCIES);
pluginDescriptor.setDependencies(dependencies); pluginDescriptor.setDependencies(dependencies);
String requires = attributes.getValue("Plugin-Requires"); String requires = attributes.getValue(PLUGIN_REQUIRES);
if (StringUtils.isNotNullOrEmpty(requires)) { if (StringUtils.isNotNullOrEmpty(requires)) {
pluginDescriptor.setRequires(requires); pluginDescriptor.setRequires(requires);
} }
pluginDescriptor.setLicense(attributes.getValue("Plugin-License")); pluginDescriptor.setLicense(attributes.getValue(PLUGIN_LICENSE));
return pluginDescriptor; return pluginDescriptor;
} }

6
pf4j/src/main/java/org/pf4j/Plugin.java

@ -60,21 +60,21 @@ public class Plugin {
* This method is called by the application when the plugin is started. * This method is called by the application when the plugin is started.
* See {@link PluginManager#startPlugin(String)}. * See {@link PluginManager#startPlugin(String)}.
*/ */
public void start() throws PluginException { public void start() {
} }
/** /**
* This method is called by the application when the plugin is stopped. * This method is called by the application when the plugin is stopped.
* See {@link PluginManager#stopPlugin(String)}. * See {@link PluginManager#stopPlugin(String)}.
*/ */
public void stop() throws PluginException { public void stop() {
} }
/** /**
* This method is called by the application when the plugin is deleted. * This method is called by the application when the plugin is deleted.
* See {@link PluginManager#deletePlugin(String)}. * See {@link PluginManager#deletePlugin(String)}.
*/ */
public void delete() throws PluginException { public void delete() {
} }
} }

2
pf4j/src/main/java/org/pf4j/PluginAlreadyLoadedException.java

@ -20,7 +20,7 @@ import java.nio.file.Path;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class PluginAlreadyLoadedException extends PluginException { public class PluginAlreadyLoadedException extends PluginRuntimeException {
private final String pluginId; private final String pluginId;
private final Path pluginPath; private final Path pluginPath;

17
pf4j/src/main/java/org/pf4j/PluginClasspath.java

@ -22,14 +22,15 @@ import java.util.Set;
/** /**
* The classpath of the plugin. * The classpath of the plugin.
* It contains {@code classes} directories and {@code lib} directories (directories that contains jars). * It contains {@code classes} directories (directories that contain classes files)
* and {@code jars} directories (directories that contain jars files).
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class PluginClasspath { public class PluginClasspath {
private Set<String> classesDirectories = new HashSet<>(); private Set<String> classesDirectories = new HashSet<>();
private Set<String> libDirectories = new HashSet<>(); private Set<String> jarsDirectories = new HashSet<>();
public Set<String> getClassesDirectories() { public Set<String> getClassesDirectories() {
return classesDirectories; return classesDirectories;
@ -45,16 +46,16 @@ public class PluginClasspath {
return this; return this;
} }
public Set<String> getLibDirectories() { public Set<String> getJarsDirectories() {
return libDirectories; return jarsDirectories;
} }
public PluginClasspath addLibDirectories(String... libDirectories) { public PluginClasspath addJarsDirectories(String... jarsDirectories) {
return addLibDirectories(Arrays.asList(libDirectories)); return addJarsDirectories(Arrays.asList(jarsDirectories));
} }
public PluginClasspath addLibDirectories(Collection<String> libDirectories) { public PluginClasspath addJarsDirectories(Collection<String> jarsDirectories) {
this.libDirectories.addAll(libDirectories); this.jarsDirectories.addAll(jarsDirectories);
return this; return this;
} }

5
pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java

@ -29,12 +29,9 @@ public interface PluginDescriptorFinder {
/** /**
* Returns true if this finder is applicable to the given {@link Path}. * Returns true if this finder is applicable to the given {@link Path}.
*
* @param pluginPath
* @return
*/ */
boolean isApplicable(Path pluginPath); boolean isApplicable(Path pluginPath);
PluginDescriptor find(Path pluginPath) throws PluginException; PluginDescriptor find(Path pluginPath);
} }

31
pf4j/src/main/java/org/pf4j/PluginManager.java

@ -69,8 +69,8 @@ public interface PluginManager {
* Load a plugin. * Load a plugin.
* *
* @param pluginPath the plugin location * @param pluginPath the plugin location
* @return the pluginId of the installed plugin as specified in * @return the pluginId of the installed plugin as specified in its {@linkplain PluginDescriptor metadata}
* its {@linkplain PluginDescriptor metadata}; or {@code null} * @throws PluginRuntimeException if something goes wrong
*/ */
String loadPlugin(Path pluginPath); String loadPlugin(Path pluginPath);
@ -83,6 +83,7 @@ public interface PluginManager {
* Start the specified plugin and its dependencies. * Start the specified plugin and its dependencies.
* *
* @return the plugin state * @return the plugin state
* @throws PluginRuntimeException if something goes wrong
*/ */
PluginState startPlugin(String pluginId); PluginState startPlugin(String pluginId);
@ -95,6 +96,7 @@ public interface PluginManager {
* Stop the specified plugin and its dependencies. * Stop the specified plugin and its dependencies.
* *
* @return the plugin state * @return the plugin state
* @throws PluginRuntimeException if something goes wrong
*/ */
PluginState stopPlugin(String pluginId); PluginState stopPlugin(String pluginId);
@ -103,6 +105,7 @@ public interface PluginManager {
* *
* @param pluginId the unique plugin identifier, specified in its metadata * @param pluginId the unique plugin identifier, specified in its metadata
* @return true if the plugin was unloaded * @return true if the plugin was unloaded
* @throws PluginRuntimeException if something goes wrong
*/ */
boolean unloadPlugin(String pluginId); boolean unloadPlugin(String pluginId);
@ -111,6 +114,7 @@ public interface PluginManager {
* *
* @param pluginId the unique plugin identifier, specified in its metadata * @param pluginId the unique plugin identifier, specified in its metadata
* @return true if plugin is disabled * @return true if plugin is disabled
* @throws PluginRuntimeException if something goes wrong
*/ */
boolean disablePlugin(String pluginId); boolean disablePlugin(String pluginId);
@ -119,6 +123,7 @@ public interface PluginManager {
* *
* @param pluginId the unique plugin identifier, specified in its metadata * @param pluginId the unique plugin identifier, specified in its metadata
* @return true if plugin is enabled * @return true if plugin is enabled
* @throws PluginRuntimeException if something goes wrong
*/ */
boolean enablePlugin(String pluginId); boolean enablePlugin(String pluginId);
@ -127,6 +132,7 @@ public interface PluginManager {
* *
* @param pluginId the unique plugin identifier, specified in its metadata * @param pluginId the unique plugin identifier, specified in its metadata
* @return true if the plugin was deleted * @return true if the plugin was deleted
* @throws PluginRuntimeException if something goes wrong
*/ */
boolean deletePlugin(String pluginId); boolean deletePlugin(String pluginId);
@ -134,9 +140,9 @@ public interface PluginManager {
List<Class<?>> getExtensionClasses(String pluginId); List<Class<?>> getExtensionClasses(String pluginId);
<T> List<Class<T>> getExtensionClasses(Class<T> type); <T> List<Class<? extends T>> getExtensionClasses(Class<T> type);
<T> List<Class<T>> getExtensionClasses(Class<T> type, String pluginId); <T> List<Class<? extends T>> getExtensionClasses(Class<T> type, String pluginId);
<T> List<T> getExtensions(Class<T> type); <T> List<T> getExtensions(Class<T> type);
@ -153,6 +159,20 @@ public interface PluginManager {
*/ */
RuntimeMode getRuntimeMode(); RuntimeMode getRuntimeMode();
/**
* Returns {@code true} if the runtime mode is {@code RuntimeMode.DEVELOPMENT}.
*/
default boolean isDevelopment() {
return RuntimeMode.DEVELOPMENT.equals(getRuntimeMode());
}
/**
* Returns {@code true} if the runtime mode is not {@code RuntimeMode.DEVELOPMENT}.
*/
default boolean isNotDevelopment() {
return !isDevelopment();
}
/** /**
* Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'. * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'.
*/ */
@ -180,7 +200,8 @@ public interface PluginManager {
String getSystemVersion(); String getSystemVersion();
/** /**
* Gets the path of the folder where plugins are installed * Gets the path of the folder where plugins are installed.
*
* @return Path of plugins root * @return Path of plugins root
*/ */
Path getPluginsRoot(); Path getPluginsRoot();

5
pf4j/src/main/java/org/pf4j/PluginRepository.java

@ -19,7 +19,7 @@ import java.nio.file.Path;
import java.util.List; import java.util.List;
/** /**
* Directory that contains plugins. A plugin could be a zip file. * Directory that contains plugins. A plugin could be a {@code directory}, @code zip} or {@code jar} file.
* *
* @author Decebal Suiu * @author Decebal Suiu
* @author Mário Franco * @author Mário Franco
@ -29,7 +29,7 @@ public interface PluginRepository {
/** /**
* List all plugin paths. * List all plugin paths.
* *
* @return a list of files * @return a list with paths
*/ */
List<Path> getPluginPaths(); List<Path> getPluginPaths();
@ -38,6 +38,7 @@ public interface PluginRepository {
* *
* @param pluginPath the plugin path * @param pluginPath the plugin path
* @return true if deleted * @return true if deleted
* @throws PluginRuntimeException if something goes wrong
*/ */
boolean deletePluginPath(Path pluginPath); boolean deletePluginPath(Path pluginPath);

16
pf4j/src/main/java/org/pf4j/PluginException.java → pf4j/src/main/java/org/pf4j/PluginRuntimeException.java

@ -23,29 +23,25 @@ import org.pf4j.util.StringUtils;
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class PluginException extends Exception { public class PluginRuntimeException extends RuntimeException {
public PluginException() { public PluginRuntimeException() {
super(); super();
} }
public PluginException(String message) { public PluginRuntimeException(String message) {
super(message); super(message);
} }
public PluginException(Throwable cause) { public PluginRuntimeException(Throwable cause) {
super(cause); super(cause);
} }
public PluginException(String message, Throwable cause) { public PluginRuntimeException(Throwable cause, String message, Object... args) {
super(message, cause);
}
public PluginException(Throwable cause, String message, Object... args) {
super(StringUtils.format(message, args), cause); super(StringUtils.format(message, args), cause);
} }
public PluginException(String message, Object... args) { public PluginRuntimeException(String message, Object... args) {
super(StringUtils.format(message, args)); super(StringUtils.format(message, args));
} }

8
pf4j/src/main/java/org/pf4j/PluginStatusProvider.java

@ -33,16 +33,16 @@ public interface PluginStatusProvider {
* Disables a plugin from being loaded. * Disables a plugin from being loaded.
* *
* @param pluginId the unique plugin identifier, specified in its metadata * @param pluginId the unique plugin identifier, specified in its metadata
* @return true if plugin is disabled * @throws PluginRuntimeException if something goes wrong
*/ */
boolean disablePlugin(String pluginId); void disablePlugin(String pluginId);
/** /**
* Enables a plugin that has previously been disabled. * Enables a plugin that has previously been disabled.
* *
* @param pluginId the unique plugin identifier, specified in its metadata * @param pluginId the unique plugin identifier, specified in its metadata
* @return true if plugin is enabled * @throws PluginRuntimeException if something goes wrong
*/ */
boolean enablePlugin(String pluginId); void enablePlugin(String pluginId);
} }

11
pf4j/src/main/java/org/pf4j/PluginWrapper.java

@ -41,6 +41,7 @@ public class PluginWrapper {
this.pluginClassLoader = pluginClassLoader; this.pluginClassLoader = pluginClassLoader;
pluginState = PluginState.CREATED; pluginState = PluginState.CREATED;
runtimeMode = pluginManager.getRuntimeMode();
} }
/** /**
@ -120,11 +121,9 @@ public class PluginWrapper {
} }
PluginWrapper other = (PluginWrapper) obj; PluginWrapper other = (PluginWrapper) obj;
if (!descriptor.getPluginId().equals(other.descriptor.getPluginId())) {
return false;
}
return true; return descriptor.getPluginId().equals(other.descriptor.getPluginId());
} }
@Override @Override
@ -136,10 +135,6 @@ public class PluginWrapper {
this.pluginState = pluginState; this.pluginState = pluginState;
} }
void setRuntimeMode(RuntimeMode runtimeMode) {
this.runtimeMode = runtimeMode;
}
void setPluginFactory(PluginFactory pluginFactory) { void setPluginFactory(PluginFactory pluginFactory) {
this.pluginFactory = pluginFactory; this.pluginFactory = pluginFactory;
} }

41
pf4j/src/main/java/org/pf4j/PropertiesPluginDescriptorFinder.java

@ -36,7 +36,16 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
private static final Logger log = LoggerFactory.getLogger(PropertiesPluginDescriptorFinder.class); private static final Logger log = LoggerFactory.getLogger(PropertiesPluginDescriptorFinder.class);
private static final String DEFAULT_PROPERTIES_FILE_NAME = "plugin.properties"; public static final String DEFAULT_PROPERTIES_FILE_NAME = "plugin.properties";
public static final String PLUGIN_ID = "plugin.id";
public static final String PLUGIN_DESCRIPTION = "plugin.description";
public static final String PLUGIN_CLASS = "plugin.class";
public static final String PLUGIN_VERSION = "plugin.version";
public static final String PLUGIN_PROVIDER = "plugin.provider";
public static final String PLUGIN_DEPENDENCIES = "plugin.dependencies";
public static final String PLUGIN_REQUIRES = "plugin.requires";
public static final String PLUGIN_LICENSE = "plugin.license";
protected String propertiesFileName; protected String propertiesFileName;
@ -54,34 +63,34 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
} }
@Override @Override
public PluginDescriptor find(Path pluginPath) throws PluginException { public PluginDescriptor find(Path pluginPath) {
Properties properties = readProperties(pluginPath); Properties properties = readProperties(pluginPath);
return createPluginDescriptor(properties); return createPluginDescriptor(properties);
} }
protected Properties readProperties(Path pluginPath) throws PluginException { protected Properties readProperties(Path pluginPath) {
Path propertiesPath = getPropertiesPath(pluginPath, propertiesFileName); Path propertiesPath = getPropertiesPath(pluginPath, propertiesFileName);
if (propertiesPath == null) { if (propertiesPath == null) {
throw new PluginException("Cannot find the properties path"); throw new PluginRuntimeException("Cannot find the properties path");
} }
log.debug("Lookup plugin descriptor in '{}'", propertiesPath); log.debug("Lookup plugin descriptor in '{}'", propertiesPath);
if (Files.notExists(propertiesPath)) { if (Files.notExists(propertiesPath)) {
throw new PluginException("Cannot find '{}' path", propertiesPath); throw new PluginRuntimeException("Cannot find '{}' path", propertiesPath);
} }
Properties properties = new Properties(); Properties properties = new Properties();
try (InputStream input = Files.newInputStream(propertiesPath)) { try (InputStream input = Files.newInputStream(propertiesPath)) {
properties.load(input); properties.load(input);
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginRuntimeException(e);
} }
return properties; return properties;
} }
protected Path getPropertiesPath(Path pluginPath, String propertiesFileName) throws PluginException { protected Path getPropertiesPath(Path pluginPath, String propertiesFileName) {
if (Files.isDirectory(pluginPath)) { if (Files.isDirectory(pluginPath)) {
return pluginPath.resolve(Paths.get(propertiesFileName)); return pluginPath.resolve(Paths.get(propertiesFileName));
} else { } else {
@ -89,7 +98,7 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
try { try {
return FileUtils.getPath(pluginPath, propertiesFileName); return FileUtils.getPath(pluginPath, propertiesFileName);
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginRuntimeException(e);
} }
} }
} }
@ -98,38 +107,38 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
DefaultPluginDescriptor pluginDescriptor = createPluginDescriptorInstance(); DefaultPluginDescriptor pluginDescriptor = createPluginDescriptorInstance();
// TODO validate !!! // TODO validate !!!
String id = properties.getProperty("plugin.id"); String id = properties.getProperty(PLUGIN_ID);
pluginDescriptor.setPluginId(id); pluginDescriptor.setPluginId(id);
String description = properties.getProperty("plugin.description"); String description = properties.getProperty(PLUGIN_DESCRIPTION);
if (StringUtils.isNullOrEmpty(description)) { if (StringUtils.isNullOrEmpty(description)) {
pluginDescriptor.setPluginDescription(""); pluginDescriptor.setPluginDescription("");
} else { } else {
pluginDescriptor.setPluginDescription(description); pluginDescriptor.setPluginDescription(description);
} }
String clazz = properties.getProperty("plugin.class"); String clazz = properties.getProperty(PLUGIN_CLASS);
if (StringUtils.isNotNullOrEmpty(clazz)) { if (StringUtils.isNotNullOrEmpty(clazz)) {
pluginDescriptor.setPluginClass(clazz); pluginDescriptor.setPluginClass(clazz);
} }
String version = properties.getProperty("plugin.version"); String version = properties.getProperty(PLUGIN_VERSION);
if (StringUtils.isNotNullOrEmpty(version)) { if (StringUtils.isNotNullOrEmpty(version)) {
pluginDescriptor.setPluginVersion(version); pluginDescriptor.setPluginVersion(version);
} }
String provider = properties.getProperty("plugin.provider"); String provider = properties.getProperty(PLUGIN_PROVIDER);
pluginDescriptor.setProvider(provider); pluginDescriptor.setProvider(provider);
String dependencies = properties.getProperty("plugin.dependencies"); String dependencies = properties.getProperty(PLUGIN_DEPENDENCIES);
pluginDescriptor.setDependencies(dependencies); pluginDescriptor.setDependencies(dependencies);
String requires = properties.getProperty("plugin.requires"); String requires = properties.getProperty(PLUGIN_REQUIRES);
if (StringUtils.isNotNullOrEmpty(requires)) { if (StringUtils.isNotNullOrEmpty(requires)) {
pluginDescriptor.setRequires(requires); pluginDescriptor.setRequires(requires);
} }
pluginDescriptor.setLicense(properties.getProperty("plugin.license")); pluginDescriptor.setLicense(properties.getProperty(PLUGIN_LICENSE));
return pluginDescriptor; return pluginDescriptor;
} }

2
pf4j/src/main/java/org/pf4j/RuntimeMode.java

@ -41,7 +41,7 @@ public enum RuntimeMode {
} }
} }
private RuntimeMode(final String name, final String... aliases) { RuntimeMode(final String name, final String... aliases) {
this.name = name; this.name = name;
this.aliases = aliases; this.aliases = aliases;
} }

7
pf4j/src/main/java/org/pf4j/SingletonExtensionFactory.java

@ -39,13 +39,14 @@ public class SingletonExtensionFactory extends DefaultExtensionFactory {
} }
@Override @Override
public Object create(Class<?> extensionClass) { @SuppressWarnings("unchecked")
public <T> T create(Class<T> extensionClass) {
String extensionClassName = extensionClass.getName(); String extensionClassName = extensionClass.getName();
if (cache.containsKey(extensionClassName)) { if (cache.containsKey(extensionClassName)) {
return cache.get(extensionClassName); return (T) cache.get(extensionClassName);
} }
Object extension = super.create(extensionClass); T extension = super.create(extensionClass);
if (extensionClassNames.isEmpty() || extensionClassNames.contains(extensionClassName)) { if (extensionClassNames.isEmpty() || extensionClassNames.contains(extensionClassName)) {
cache.put(extensionClassName, extension); cache.put(extensionClassName, extension);
} }

49
pf4j/src/main/java/org/pf4j/ZipPluginManager.java

@ -0,0 +1,49 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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;
/**
* It's a {@link PluginManager} that loads each plugin from a {@code zip} file.
* The structure of the zip file is:
* <ul>
* <li>{@code lib} directory that contains all dependencies (as jar files); it's optional (no dependencies)
* <li>{@code classes} directory that contains all plugin's classes
* </ul>
*
* @author Decebal Suiu
*/
public class ZipPluginManager extends DefaultPluginManager {
@Override
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new PropertiesPluginDescriptorFinder();
}
@Override
protected PluginLoader createPluginLoader() {
return new CompoundPluginLoader()
.add(new DevelopmentPluginLoader(this), this::isDevelopment)
.add(new DefaultPluginLoader(this), this::isNotDevelopment);
}
@Override
protected PluginRepository createPluginRepository() {
return new CompoundPluginRepository()
.add(new DevelopmentPluginRepository(getPluginsRoot()), this::isDevelopment)
.add(new DefaultPluginRepository(getPluginsRoot()), this::isNotDevelopment);
}
}

6
pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java

@ -109,11 +109,7 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor {
String extension = getBinaryName(extensionElement); String extension = getBinaryName(extensionElement);
for (TypeElement extensionPointElement : extensionPointElements) { for (TypeElement extensionPointElement : extensionPointElements) {
String extensionPoint = getBinaryName(extensionPointElement); String extensionPoint = getBinaryName(extensionPointElement);
Set<String> extensionPoints = extensions.get(extensionPoint); Set<String> extensionPoints = extensions.computeIfAbsent(extensionPoint, k -> new TreeSet<>());
if (extensionPoints == null) {
extensionPoints = new TreeSet<>();
extensions.put(extensionPoint, extensionPoints);
}
extensionPoints.add(extension); extensionPoints.add(extension);
} }
} }

12
pf4j/src/main/java/org/pf4j/util/AndFileFilter.java

@ -23,11 +23,9 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* This filter providing conditional AND logic across a list of * This filter providing conditional AND logic across a list of file filters.
* file filters. This filter returns <code>true</code> if all filters in the * This filter returns {@code true} if all filters in the list return {@code true}. Otherwise, it returns {@code false}.
* list return <code>true</code>. Otherwise, it returns <code>false</code>. * Checking of the file filter list stops when the first filter returns {@code false}.
* Checking of the file filter list stops when the first filter returns
* <code>false</code>.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
@ -37,7 +35,7 @@ public class AndFileFilter implements FileFilter {
private List<FileFilter> fileFilters; private List<FileFilter> fileFilters;
public AndFileFilter() { public AndFileFilter() {
this(new ArrayList<FileFilter>()); this(new ArrayList<>());
} }
public AndFileFilter(FileFilter... fileFilters) { public AndFileFilter(FileFilter... fileFilters) {
@ -68,7 +66,7 @@ public class AndFileFilter implements FileFilter {
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
if (this.fileFilters.size() == 0) { if (this.fileFilters.isEmpty()) {
return false; return false;
} }

10
pf4j/src/main/java/org/pf4j/util/DirectedGraph.java

@ -44,7 +44,7 @@ public class DirectedGraph<V> {
return; return;
} }
neighbors.put(vertex, new ArrayList<V>()); neighbors.put(vertex, new ArrayList<>());
} }
/** /**
@ -85,7 +85,7 @@ public class DirectedGraph<V> {
} }
public List<V> getNeighbors(V vertex) { public List<V> getNeighbors(V vertex) {
return containsVertex(vertex) ? neighbors.get(vertex) : new ArrayList<V>(); return containsVertex(vertex) ? neighbors.get(vertex) : new ArrayList<>();
} }
/** /**
@ -101,7 +101,7 @@ public class DirectedGraph<V> {
} }
/** /**
* Report (as a Map) the in-degree (the number of head ends adjacent to a vertex) of each vertex. * Report (as a {@link Map}) the in-degree (the number of head ends adjacent to a vertex) of each vertex.
*/ */
public Map<V, Integer> inDegree() { public Map<V, Integer> inDegree() {
Map<V, Integer> result = new HashMap<>(); Map<V, Integer> result = new HashMap<>();
@ -181,9 +181,9 @@ public class DirectedGraph<V> {
*/ */
@Override @Override
public String toString() { public String toString() {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (V vertex : neighbors.keySet()) { for (V vertex : neighbors.keySet()) {
sb.append("\n " + vertex + " -> " + neighbors.get(vertex)); sb.append("\n ").append(vertex).append(" -> ").append(neighbors.get(vertex));
} }
return sb.toString(); return sb.toString();

11
pf4j/src/main/java/org/pf4j/util/FileUtils.java

@ -172,17 +172,10 @@ public class FileUtils {
FileTime pluginZipDate = Files.getLastModifiedTime(filePath); FileTime pluginZipDate = Files.getLastModifiedTime(filePath);
String fileName = filePath.getFileName().toString(); String fileName = filePath.getFileName().toString();
Path pluginDirectory = filePath.resolveSibling(fileName.substring(0, fileName.lastIndexOf("."))); String directoryName = fileName.substring(0, fileName.lastIndexOf("."));
Path pluginDirectory = filePath.resolveSibling(directoryName);
if (!Files.exists(pluginDirectory) || pluginZipDate.compareTo(Files.getLastModifiedTime(pluginDirectory)) > 0) { if (!Files.exists(pluginDirectory) || pluginZipDate.compareTo(Files.getLastModifiedTime(pluginDirectory)) > 0) {
// do not overwrite an old version, remove it
if (Files.exists(pluginDirectory)) {
FileUtils.delete(pluginDirectory);
}
// create root for plugin
Files.createDirectories(pluginDirectory);
// expand '.zip' file // expand '.zip' file
Unzip unzip = new Unzip(); Unzip unzip = new Unzip();
unzip.setSource(filePath.toFile()); unzip.setSource(filePath.toFile());

12
pf4j/src/main/java/org/pf4j/util/OrFileFilter.java

@ -23,11 +23,9 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* This filter providing conditional OR logic across a list of * This filter providing conditional OR logic across a list of file filters.
* file filters. This filter returns <code>true</code> if one filter in the * This filter returns {@code true} if one filter in the list return {@code true}. Otherwise, it returns {@code false}.
* list return <code>true</code>. Otherwise, it returns <code>false</code>. * Checking of the file filter list stops when the first filter returns {@code true}.
* Checking of the file filter list stops when the first filter returns
* <code>true</code>.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
@ -37,7 +35,7 @@ public class OrFileFilter implements FileFilter {
private List<FileFilter> fileFilters; private List<FileFilter> fileFilters;
public OrFileFilter() { public OrFileFilter() {
this(new ArrayList<FileFilter>()); this(new ArrayList<>());
} }
public OrFileFilter(FileFilter... fileFilters) { public OrFileFilter(FileFilter... fileFilters) {
@ -68,7 +66,7 @@ public class OrFileFilter implements FileFilter {
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
if (this.fileFilters.size() == 0) { if (this.fileFilters.isEmpty()) {
return true; return true;
} }

6
pf4j/src/main/java/org/pf4j/util/Unzip.java

@ -63,10 +63,14 @@ public class Unzip {
this.destination = destination; this.destination = destination;
} }
/**
* Extract the content of zip file ({@code source}) to destination directory.
* If destination directory already exists it will be deleted before.
*/
public void extract() throws IOException { public void extract() throws IOException {
log.debug("Extract content of '{}' to '{}'", source, destination); log.debug("Extract content of '{}' to '{}'", source, destination);
// delete destination file if exists // delete destination directory if exists
if (destination.exists() && destination.isDirectory()) { if (destination.exists() && destination.isDirectory()) {
FileUtils.delete(destination.toPath()); FileUtils.delete(destination.toPath());
} }

12
pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java

@ -15,9 +15,9 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.After; import org.junit.jupiter.api.AfterEach;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.pf4j.plugin.FailTestPlugin; import org.pf4j.plugin.FailTestPlugin;
import org.pf4j.plugin.TestExtensionPoint; import org.pf4j.plugin.TestExtensionPoint;
@ -28,7 +28,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -40,7 +40,7 @@ public class AbstractExtensionFinderTest {
private PluginManager pluginManager; private PluginManager pluginManager;
@Before @BeforeEach
public void setUp() { public void setUp() {
PluginWrapper pluginStarted = mock(PluginWrapper.class); PluginWrapper pluginStarted = mock(PluginWrapper.class);
when(pluginStarted.getPluginClassLoader()).thenReturn(getClass().getClassLoader()); when(pluginStarted.getPluginClassLoader()).thenReturn(getClass().getClassLoader());
@ -57,7 +57,7 @@ public class AbstractExtensionFinderTest {
when(pluginManager.getExtensionFactory()).thenReturn(new DefaultExtensionFactory()); when(pluginManager.getExtensionFactory()).thenReturn(new DefaultExtensionFactory());
} }
@After @AfterEach
public void tearDown() { public void tearDown() {
pluginManager = null; pluginManager = null;
} }

49
pf4j/src/test/java/org/pf4j/AbstractPluginManagerTest.java

@ -0,0 +1,49 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.junit.jupiter.api.Test;
import org.pf4j.plugin.TestExtension;
import org.pf4j.plugin.TestExtensionPoint;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Decebal Suiu
*/
public class AbstractPluginManagerTest {
@Test
public void getExtensionsByType() {
AbstractPluginManager pluginManager = mock(AbstractPluginManager.class, CALLS_REAL_METHODS);
ExtensionFinder extensionFinder = mock(ExtensionFinder.class);
List<ExtensionWrapper<TestExtensionPoint>> extensionList = new ArrayList<>(1);
extensionList.add(new ExtensionWrapper<>(new ExtensionDescriptor(0, TestExtension.class), new DefaultExtensionFactory()));
when(extensionFinder.find(TestExtensionPoint.class)).thenReturn(extensionList);
pluginManager.extensionFinder = extensionFinder;
List<TestExtensionPoint> extensions = pluginManager.getExtensions(TestExtensionPoint.class);
assertEquals(1, extensions.size());
}
}

82
pf4j/src/test/java/org/pf4j/CompoundPluginDescriptorFinderTest.java

@ -15,28 +15,34 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder; import org.pf4j.plugin.PluginJar;
import org.pf4j.plugin.PluginZip; import org.pf4j.plugin.PluginZip;
import org.pf4j.plugin.TestPlugin;
import java.io.File; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.LinkedHashMap;
import java.util.List; import java.util.Map;
import java.util.Properties;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class CompoundPluginDescriptorFinderTest { public class CompoundPluginDescriptorFinderTest {
@Rule @TempDir
public TemporaryFolder pluginsFolder = new TemporaryFolder(); Path pluginsPath;
@Test @Test
public void add() { public void add() {
@ -49,8 +55,8 @@ public class CompoundPluginDescriptorFinderTest {
@Test @Test
public void find() throws Exception { public void find() throws Exception {
Path pluginPath = pluginsFolder.newFolder("test-plugin-1").toPath(); Path pluginPath = Files.createDirectories(pluginsPath.resolve("test-plugin-1"));
Files.write(pluginPath.resolve("plugin.properties"), getPlugin1Properties(), StandardCharsets.UTF_8); storePropertiesToPath(getPlugin1Properties(), pluginPath);
PluginDescriptorFinder descriptorFinder = new CompoundPluginDescriptorFinder() PluginDescriptorFinder descriptorFinder = new CompoundPluginDescriptorFinder()
.add(new PropertiesPluginDescriptorFinder()); .add(new PropertiesPluginDescriptorFinder());
@ -64,31 +70,32 @@ public class CompoundPluginDescriptorFinderTest {
@Test @Test
public void findInJar() throws Exception { public void findInJar() throws Exception {
PluginDescriptorFinder descriptorFinder = new CompoundPluginDescriptorFinder() PluginDescriptorFinder descriptorFinder = new CompoundPluginDescriptorFinder()
.add(new PropertiesPluginDescriptorFinder()); .add(new ManifestPluginDescriptorFinder());
PluginZip pluginJar = new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.jar"), "myPlugin") PluginJar pluginJar = new PluginJar.Builder(pluginsPath.resolve("my-plugin-1.2.3.jar"), "myPlugin")
.pluginClass(TestPlugin.class.getName())
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
PluginDescriptor pluginDescriptor = descriptorFinder.find(pluginJar.path()); PluginDescriptor pluginDescriptor = descriptorFinder.find(pluginJar.path());
assertNotNull(pluginDescriptor); assertNotNull(pluginDescriptor);
assertEquals("myPlugin", pluginJar.pluginId()); assertEquals("myPlugin", pluginJar.pluginId());
assertEquals(TestPlugin.class.getName(), pluginJar.pluginClass());
assertEquals("1.2.3", pluginJar.pluginVersion()); assertEquals("1.2.3", pluginJar.pluginVersion());
} }
@Test(expected = PluginException.class) @Test
public void testNotFound() throws Exception { public void testNotFound() {
PluginDescriptorFinder descriptorFinder = new CompoundPluginDescriptorFinder(); PluginDescriptorFinder descriptorFinder = new CompoundPluginDescriptorFinder();
Path pluginsPath = pluginsFolder.getRoot().toPath(); assertThrows(PluginRuntimeException.class, () -> descriptorFinder.find(pluginsPath.resolve("test-plugin-3")));
descriptorFinder.find(pluginsPath.resolve("test-plugin-3"));
} }
@Test @Test
public void testSpaceCharacterInFileName() throws Exception { public void testSpaceCharacterInFileName() throws Exception {
PluginDescriptorFinder descriptorFinder = new PropertiesPluginDescriptorFinder(); PluginDescriptorFinder descriptorFinder = new CompoundPluginDescriptorFinder()
File jar = pluginsFolder.newFile("my plugin-1.2.3.jar"); .add(new ManifestPluginDescriptorFinder());
PluginZip pluginJar = new PluginZip.Builder(jar, "myPlugin") PluginJar pluginJar = new PluginJar.Builder(pluginsPath.resolve("my plugin-1.2.3.jar"), "myPlugin")
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
@ -96,21 +103,24 @@ public class CompoundPluginDescriptorFinderTest {
assertNotNull(pluginDescriptor); assertNotNull(pluginDescriptor);
} }
private List<String> getPlugin1Properties() { private Properties getPlugin1Properties() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(7);
"plugin.id=test-plugin-1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_ID, "test-plugin-1");
+ "plugin.version=0.0.1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_CLASS, TestPlugin.class.getName());
+ "plugin.description=Test Plugin 1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
+ "plugin.provider=Decebal Suiu\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "plugin.class=org.pf4j.plugin.TestPlugin\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "test-plugin-2,test-plugin-3@~1.0");
+ "plugin.dependencies=test-plugin-2,test-plugin-3@~1.0\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, ">=1");
+ "plugin.requires=>=1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_LICENSE, "Apache-2.0");
+ "plugin.license=Apache-2.0\n"
+ "\n" return PluginZip.createProperties(map);
+ "" }
};
private void storePropertiesToPath(Properties properties, Path pluginPath) throws IOException {
return Arrays.asList(lines); Path path = pluginPath.resolve(PropertiesPluginDescriptorFinder.DEFAULT_PROPERTIES_FILE_NAME);
try (Writer writer = new OutputStreamWriter(new FileOutputStream(path.toFile()), StandardCharsets.UTF_8)) {
properties.store(writer, "");
}
} }
} }

30
pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java

@ -15,26 +15,38 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Test; import org.junit.jupiter.api.AfterEach;
import org.pf4j.plugin.TestExtension; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pf4j.plugin.FailTestExtension; import org.pf4j.plugin.FailTestExtension;
import org.pf4j.plugin.TestExtension;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
/** /**
*
* @author Mario Franco * @author Mario Franco
*/ */
public class DefaultExtensionFactoryTest { public class DefaultExtensionFactoryTest {
private ExtensionFactory extensionFactory;
@BeforeEach
public void setUp() {
extensionFactory = new DefaultExtensionFactory();
}
@AfterEach
public void tearDown() {
extensionFactory = null;
}
/** /**
* Test of create method, of class DefaultExtensionFactory. * Test of create method, of class DefaultExtensionFactory.
*/ */
@Test @Test
public void testCreate() { public void testCreate() {
DefaultExtensionFactory instance = new DefaultExtensionFactory(); assertNotNull(extensionFactory.create(TestExtension.class));
Object result = instance.create(TestExtension.class);
assertNotNull(result);
} }
/** /**
@ -42,9 +54,7 @@ public class DefaultExtensionFactoryTest {
*/ */
@Test @Test
public void testCreateFailConstructor() { public void testCreateFailConstructor() {
DefaultExtensionFactory instance = new DefaultExtensionFactory(); assertThrows(PluginRuntimeException.class, () -> extensionFactory.create(FailTestExtension.class));
Object result = instance.create(FailTestExtension.class);
assertNull(result);
} }
} }

8
pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java

@ -15,15 +15,15 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.pf4j.plugin.AnotherFailTestPlugin; import org.pf4j.plugin.AnotherFailTestPlugin;
import org.pf4j.plugin.FailTestPlugin; import org.pf4j.plugin.FailTestPlugin;
import org.pf4j.plugin.TestPlugin; import org.pf4j.plugin.TestPlugin;
import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertNotNull; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;

46
pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java

@ -15,21 +15,21 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.After; import org.junit.jupiter.api.AfterEach;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder;
import org.pf4j.plugin.PluginZip; import org.pf4j.plugin.PluginZip;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.Assert.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -38,14 +38,12 @@ public class DefaultPluginManagerTest {
private DefaultPluginManager pluginManager; private DefaultPluginManager pluginManager;
private DefaultPluginDescriptor pluginDescriptor; private DefaultPluginDescriptor pluginDescriptor;
private PluginWrapper pluginWrapper; private PluginWrapper pluginWrapper;
private Path pluginsPath;
@Rule @TempDir
public TemporaryFolder pluginsFolder = new TemporaryFolder(); Path pluginsPath;
@Before @BeforeEach
public void setUp() throws IOException { public void setUp() throws IOException {
pluginsPath = pluginsFolder.getRoot().toPath();
pluginManager = new DefaultPluginManager(pluginsPath); pluginManager = new DefaultPluginManager(pluginsPath);
pluginDescriptor = new DefaultPluginDescriptor(); pluginDescriptor = new DefaultPluginDescriptor();
@ -59,7 +57,7 @@ public class DefaultPluginManagerTest {
pluginWrapper = new PluginWrapper(pluginManager, pluginDescriptor, Files.createTempDirectory("test"), getClass().getClassLoader()); pluginWrapper = new PluginWrapper(pluginManager, pluginDescriptor, Files.createTempDirectory("test"), getClass().getClassLoader());
} }
@After @AfterEach
public void tearDown() { public void tearDown() {
pluginManager = null; pluginManager = null;
pluginDescriptor = null; pluginDescriptor = null;
@ -67,24 +65,24 @@ public class DefaultPluginManagerTest {
} }
@Test @Test
public void validateOK() throws PluginException { public void validateOK() {
pluginManager.validatePluginDescriptor(pluginDescriptor); pluginManager.validatePluginDescriptor(pluginDescriptor);
} }
@Test(expected = PluginException.class) @Test
public void validateFailsOnId() throws PluginException { public void validateFailsOnId() {
pluginDescriptor.setPluginId(""); pluginDescriptor.setPluginId("");
pluginManager.validatePluginDescriptor(pluginDescriptor); assertThrows(PluginRuntimeException.class, () -> pluginManager.validatePluginDescriptor(pluginDescriptor));
} }
@Test(expected = PluginException.class) @Test
public void validateFailsOnVersion() throws PluginException { public void validateFailsOnVersion() {
pluginDescriptor.setPluginVersion(null); pluginDescriptor.setPluginVersion(null);
pluginManager.validatePluginDescriptor(pluginDescriptor); assertThrows(PluginRuntimeException.class, () -> pluginManager.validatePluginDescriptor(pluginDescriptor));
} }
@Test @Test
public void validateNoPluginClass() throws PluginException { public void validateNoPluginClass() {
pluginManager.validatePluginDescriptor(pluginDescriptor); pluginManager.validatePluginDescriptor(pluginDescriptor);
assertEquals(Plugin.class.getName(), pluginDescriptor.getPluginClass()); assertEquals(Plugin.class.getName(), pluginDescriptor.getPluginClass());
} }
@ -132,7 +130,7 @@ public class DefaultPluginManagerTest {
*/ */
@Test @Test
public void testPluginDisabledNoStart() throws IOException { public void testPluginDisabledNoStart() throws IOException {
new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), "myPlugin")
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();

66
pf4j/src/test/java/org/pf4j/DefaultPluginRepositoryTest.java

@ -15,20 +15,18 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Mario Franco * @author Mario Franco
@ -36,25 +34,18 @@ import static org.junit.Assert.assertTrue;
*/ */
public class DefaultPluginRepositoryTest { public class DefaultPluginRepositoryTest {
private Path pluginsPath; @TempDir
Path pluginsPath;
@Rule @BeforeEach
public TemporaryFolder pluginsFolder = new TemporaryFolder();
@Before
public void setUp() throws IOException { public void setUp() throws IOException {
pluginsPath = pluginsFolder.getRoot().toPath(); Path plugin1Path = Files.createDirectory(pluginsPath.resolve("plugin-1"));
pluginsFolder.newFolder("plugin-1");
// Prove that we can delete a folder with a file inside // Prove that we can delete a folder with a file inside
Files.createFile(Paths.get(pluginsFolder.getRoot().getAbsolutePath()).resolve("plugin-1").resolve("myfile")); Files.createFile(plugin1Path.resolve("myfile"));
// Create a zip file for plugin-1 to test that it is deleted when plugin is deleted // Create a zip file for plugin-1 to test that it is deleted when plugin is deleted
Files.createFile(Paths.get(pluginsFolder.getRoot().getAbsolutePath()).resolve("plugin-1.zip")); Files.createFile(pluginsPath.resolve("plugin-1.zip"));
pluginsFolder.newFolder("plugin-2"); Files.createDirectory(pluginsPath.resolve("plugin-2"));
pluginsFolder.newFolder("plugin-3"); Files.createDirectory(pluginsPath.resolve("plugin-3"));
// standard maven/gradle bin folder - these should be skipped in development mode because the cause errors
pluginsFolder.newFolder("target");
pluginsFolder.newFolder("build");
} }
/** /**
@ -62,29 +53,14 @@ public class DefaultPluginRepositoryTest {
*/ */
@Test @Test
public void testGetPluginArchives() { public void testGetPluginArchives() {
PluginRepository repository = new DefaultPluginRepository(pluginsPath, false); PluginRepository repository = new DefaultPluginRepository(pluginsPath);
List<Path> pluginPaths = repository.getPluginPaths(); List<Path> pluginPaths = repository.getPluginPaths();
assertEquals(5, pluginPaths.size()); assertEquals(3, pluginPaths.size());
assertPathExists(pluginPaths, pluginsPath.resolve("plugin-1")); assertPathExists(pluginPaths, pluginsPath.resolve("plugin-1"));
assertPathExists(pluginPaths, pluginsPath.resolve("plugin-2")); assertPathExists(pluginPaths, pluginsPath.resolve("plugin-2"));
assertPathExists(pluginPaths, pluginsPath.resolve("plugin-3")); assertPathExists(pluginPaths, pluginsPath.resolve("plugin-3"));
// when not in development mode we will honor these folders
assertPathExists(pluginPaths, pluginsPath.resolve("target"));
assertPathExists(pluginPaths, pluginsPath.resolve("build"));
}
@Test
public void testGetPluginArchivesInDevelopmentMode() {
PluginRepository repository = new DefaultPluginRepository(pluginsPath, true);
List<Path> pluginPaths = repository.getPluginPaths();
// target and build should be ignored
assertEquals(3, pluginPaths.size());
assertPathDoesNotExists(pluginPaths, pluginsPath.resolve("target"));
assertPathDoesNotExists(pluginPaths, pluginsPath.resolve("build"));
} }
/** /**
@ -92,15 +68,13 @@ public class DefaultPluginRepositoryTest {
*/ */
@Test @Test
public void testDeletePluginPath() { public void testDeletePluginPath() {
PluginRepository repository = new DefaultPluginRepository(pluginsPath, false); PluginRepository repository = new DefaultPluginRepository(pluginsPath);
assertTrue(Files.exists(pluginsPath.resolve("plugin-1.zip"))); assertTrue(Files.exists(pluginsPath.resolve("plugin-1.zip")));
assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-1"))); assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-1")));
assertFalse(Files.exists(pluginsPath.resolve("plugin-1.zip"))); assertFalse(Files.exists(pluginsPath.resolve("plugin-1.zip")));
assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-3"))); assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-3")));
assertFalse(repository.deletePluginPath(pluginsPath.resolve("plugin-4"))); assertFalse(repository.deletePluginPath(pluginsPath.resolve("plugin-4")));
assertTrue(repository.deletePluginPath(pluginsPath.resolve("target")));
assertTrue(repository.deletePluginPath(pluginsPath.resolve("build")));
List<Path> pluginPaths = repository.getPluginPaths(); List<Path> pluginPaths = repository.getPluginPaths();
@ -109,11 +83,7 @@ public class DefaultPluginRepositoryTest {
} }
private void assertPathExists(List<Path> paths, Path path) { private void assertPathExists(List<Path> paths, Path path) {
assertTrue("The directory must contain the file " + path, paths.contains(path)); assertTrue(paths.contains(path), "The directory must contain the file " + path);
}
private void assertPathDoesNotExists(List<Path> paths, Path path) {
assertFalse("The directory must not contain the file " + path, paths.contains(path));
} }
} }

51
pf4j/src/test/java/org/pf4j/DefaultPluginStatusProviderTest.java

@ -15,20 +15,17 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Before; import org.junit.jupiter.api.Test;
import org.junit.Rule; import org.junit.jupiter.api.io.TempDir;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.pf4j.util.FileUtils; import org.pf4j.util.FileUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Mario Franco * @author Mario Franco
@ -36,15 +33,8 @@ import static org.junit.Assert.assertTrue;
*/ */
public class DefaultPluginStatusProviderTest { public class DefaultPluginStatusProviderTest {
private Path pluginsPath; @TempDir
Path pluginsPath;
@Rule
public TemporaryFolder pluginsFolder = new TemporaryFolder();
@Before
public void setUp() {
pluginsPath = pluginsFolder.getRoot().toPath();
}
@Test @Test
public void testIsPluginDisabled() throws IOException { public void testIsPluginDisabled() throws IOException {
@ -70,58 +60,59 @@ public class DefaultPluginStatusProviderTest {
} }
@Test @Test
public void testDisablePlugin() throws IOException { public void testDisablePlugin() throws Exception {
createEnabledFile(); createEnabledFile();
createDisabledFile(); createDisabledFile();
PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath); PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath);
statusProvider.disablePlugin("plugin-1");
assertTrue(statusProvider.disablePlugin("plugin-1"));
assertTrue(statusProvider.isPluginDisabled("plugin-1")); assertTrue(statusProvider.isPluginDisabled("plugin-1"));
assertTrue(statusProvider.isPluginDisabled("plugin-2")); assertTrue(statusProvider.isPluginDisabled("plugin-2"));
assertTrue(statusProvider.isPluginDisabled("plugin-3")); assertTrue(statusProvider.isPluginDisabled("plugin-3"));
} }
@Test @Test
public void testDisablePluginWithEnableEmpty() throws IOException { public void testDisablePluginWithEnableEmpty() throws Exception {
createDisabledFile(); createDisabledFile();
PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath); PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath);
statusProvider.disablePlugin("plugin-1");
assertTrue(statusProvider.disablePlugin("plugin-1"));
assertTrue(statusProvider.isPluginDisabled("plugin-1")); assertTrue(statusProvider.isPluginDisabled("plugin-1"));
assertTrue(statusProvider.isPluginDisabled("plugin-2")); assertTrue(statusProvider.isPluginDisabled("plugin-2"));
assertFalse(statusProvider.isPluginDisabled("plugin-3")); assertFalse(statusProvider.isPluginDisabled("plugin-3"));
} }
@Test @Test
public void testEnablePlugin() throws IOException { public void testEnablePlugin() throws Exception {
createEnabledFile(); createEnabledFile();
PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath); PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath);
statusProvider.enablePlugin("plugin-2");
assertTrue(statusProvider.enablePlugin("plugin-2"));
assertFalse(statusProvider.isPluginDisabled("plugin-1")); assertFalse(statusProvider.isPluginDisabled("plugin-1"));
assertFalse(statusProvider.isPluginDisabled("plugin-2")); assertFalse(statusProvider.isPluginDisabled("plugin-2"));
assertTrue(statusProvider.isPluginDisabled("plugin-3")); assertTrue(statusProvider.isPluginDisabled("plugin-3"));
} }
@Test @Test
public void testEnablePluginWithEnableEmpty() { public void testEnablePluginWithEnableEmpty() throws Exception{
PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath); PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath);
statusProvider.enablePlugin("plugin-2");
assertTrue(statusProvider.enablePlugin("plugin-2"));
assertFalse(statusProvider.isPluginDisabled("plugin-1")); assertFalse(statusProvider.isPluginDisabled("plugin-1"));
assertFalse(statusProvider.isPluginDisabled("plugin-2")); assertFalse(statusProvider.isPluginDisabled("plugin-2"));
assertFalse(statusProvider.isPluginDisabled("plugin-3")); assertFalse(statusProvider.isPluginDisabled("plugin-3"));
} }
@Test @Test
public void testDisablePluginWithoutDisabledFile() { public void testDisablePluginWithoutDisabledFile() throws Exception {
PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath); PluginStatusProvider statusProvider = new DefaultPluginStatusProvider(pluginsPath);
assertFalse(statusProvider.isPluginDisabled("plugin-1")); assertFalse(statusProvider.isPluginDisabled("plugin-1"));
assertTrue(statusProvider.disablePlugin("plugin-1"));
statusProvider.disablePlugin("plugin-1");
assertTrue(statusProvider.isPluginDisabled("plugin-1")); assertTrue(statusProvider.isPluginDisabled("plugin-1"));
} }
@ -129,8 +120,8 @@ public class DefaultPluginStatusProviderTest {
List<String> disabledPlugins = new ArrayList<>(); List<String> disabledPlugins = new ArrayList<>();
disabledPlugins.add("plugin-2"); disabledPlugins.add("plugin-2");
File disabledFile = pluginsFolder.newFile("disabled.txt"); Path disabledPath = pluginsPath.resolve("disabled.txt");
FileUtils.writeLines(disabledPlugins, disabledFile); FileUtils.writeLines(disabledPlugins, disabledPath.toFile());
} }
private void createEnabledFile() throws IOException { private void createEnabledFile() throws IOException {
@ -138,8 +129,8 @@ public class DefaultPluginStatusProviderTest {
enabledPlugins.add("plugin-1"); enabledPlugins.add("plugin-1");
enabledPlugins.add("plugin-2"); enabledPlugins.add("plugin-2");
File enabledFile = pluginsFolder.newFile("enabled.txt"); Path enabledPath = pluginsPath.resolve("enabled.txt");
FileUtils.writeLines(enabledPlugins, enabledFile); FileUtils.writeLines(enabledPlugins, enabledPath.toFile());
} }
} }

18
pf4j/src/test/java/org/pf4j/DefaultVersionManagerTest.java

@ -16,10 +16,12 @@
package org.pf4j; package org.pf4j;
import com.github.zafarkhaja.semver.ParseException; import com.github.zafarkhaja.semver.ParseException;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
@ -28,7 +30,7 @@ public class DefaultVersionManagerTest {
private VersionManager versionManager; private VersionManager versionManager;
@Before @BeforeEach
public void init() { public void init() {
versionManager = new DefaultVersionManager(); versionManager = new DefaultVersionManager();
} }
@ -39,14 +41,14 @@ public class DefaultVersionManagerTest {
assertTrue(versionManager.checkVersionConstraint("1.4.3", ">=1.4.0 & <1.6.0")); // range assertTrue(versionManager.checkVersionConstraint("1.4.3", ">=1.4.0 & <1.6.0")); // range
} }
@Test(expected = IllegalArgumentException.class) @Test
public void nullOrEmptyVersion() { public void nullOrEmptyVersion() {
assertFalse(versionManager.checkVersionConstraint(null, ">2.0.0")); assertThrows(IllegalArgumentException.class, () -> versionManager.checkVersionConstraint(null, ">2.0.0"));
} }
@Test(expected = ParseException.class) @Test
public void invalidVersion() { public void invalidVersion() {
assertFalse(versionManager.checkVersionConstraint("1.0", ">2.0.0")); assertThrows(ParseException.class, () -> versionManager.checkVersionConstraint("1.0", ">2.0.0"));
} }
@Test @Test

10
pf4j/src/test/java/org/pf4j/DependencyResolverTest.java

@ -15,14 +15,16 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
@ -31,7 +33,7 @@ public class DependencyResolverTest {
private DependencyResolver resolver; private DependencyResolver resolver;
@Before @BeforeEach
public void init() { public void init() {
VersionManager versionManager = new DefaultVersionManager(); VersionManager versionManager = new DefaultVersionManager();
resolver = new DependencyResolver(versionManager); resolver = new DependencyResolver(versionManager);

61
pf4j/src/test/java/org/pf4j/DevelopmentPluginRepositoryTest.java

@ -0,0 +1,61 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
/**
* @author Decebal Suiu
*/
public class DevelopmentPluginRepositoryTest {
@TempDir
Path pluginsPath;
@BeforeEach
public void setUp() throws IOException {
// standard maven/gradle bin folder - these should be skipped in development mode because the cause errors
Files.createDirectory(pluginsPath.resolve(DevelopmentPluginRepository.MAVEN_BUILD_DIR));
Files.createDirectory(pluginsPath.resolve(DevelopmentPluginRepository.GRADLE_BUILD_DIR));
}
@Test
public void testGetPluginArchivesInDevelopmentMode() {
PluginRepository repository = new DevelopmentPluginRepository(pluginsPath);
List<Path> pluginPaths = repository.getPluginPaths();
// target and build should be ignored
assertEquals(0, pluginPaths.size());
assertPathDoesNotExists(pluginPaths, pluginsPath.resolve(DevelopmentPluginRepository.MAVEN_BUILD_DIR));
assertPathDoesNotExists(pluginPaths, pluginsPath.resolve(DevelopmentPluginRepository.GRADLE_BUILD_DIR));
}
private void assertPathDoesNotExists(List<Path> paths, Path path) {
assertFalse(paths.contains(path), "The directory must not contain the file " + path);
}
}

6
pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java

@ -15,13 +15,13 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.pf4j.processor.ExtensionAnnotationProcessor; import org.pf4j.processor.ExtensionAnnotationProcessor;
import java.util.Set; import java.util.Set;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Mario Franco * @author Mario Franco

96
pf4j/src/test/java/org/pf4j/JarPluginManagerTest.java

@ -0,0 +1,96 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.pf4j.plugin.PluginJar;
import org.pf4j.plugin.TestExtension;
import org.pf4j.plugin.TestExtensionPoint;
import org.pf4j.plugin.TestPlugin;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class JarPluginManagerTest {
private PluginJar pluginJar;
private JarPluginManager pluginManager;
@TempDir
Path pluginsPath;
@BeforeEach
public void setUp() throws IOException {
pluginJar = new PluginJar.Builder(pluginsPath.resolve("test-plugin.jar"), "test-plugin")
.pluginClass(TestPlugin.class.getName())
.pluginVersion("1.2.3")
.extension(TestExtension.class.getName())
.build();
pluginManager = new JarPluginManager(pluginsPath);
}
@AfterEach
public void tearDown() {
pluginJar = null;
pluginManager = null;
}
@Test
public void getExtensions() {
pluginManager.loadPlugins();
pluginManager.startPlugins();
List<TestExtensionPoint> extensions = pluginManager.getExtensions(TestExtensionPoint.class);
assertEquals(1, extensions.size());
String something = extensions.get(0).saySomething();
assertEquals(new TestExtension().saySomething(), something);
}
@Test
public void unloadPlugin() throws Exception {
pluginManager.loadPlugins();
assertEquals(1, pluginManager.getPlugins().size());
boolean unloaded = pluginManager.unloadPlugin(pluginJar.pluginId());
assertTrue(unloaded);
assertTrue(pluginJar.file().exists());
}
@Test
public void deletePlugin() throws Exception {
pluginManager.loadPlugins();
assertEquals(1, pluginManager.getPlugins().size());
boolean deleted = pluginManager.deletePlugin(pluginJar.pluginId());
assertTrue(deleted);
assertFalse(pluginJar.file().exists());
}
}

58
pf4j/src/test/java/org/pf4j/JarPluginRepositoryTest.java

@ -0,0 +1,58 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Decebal Suiu
*/
public class JarPluginRepositoryTest {
@TempDir
Path pluginsPath;
/**
* Test of {@link JarPluginRepository#deletePluginPath(Path)} method.
*/
@Test
public void testDeletePluginPath() throws Exception {
PluginRepository repository = new JarPluginRepository(pluginsPath);
Path plugin1Path = Files.createDirectory(pluginsPath.resolve("plugin-1"));
Path plugin1JarPath = Files.createFile(pluginsPath.resolve("plugin-1.jar"));
assertFalse(repository.deletePluginPath(plugin1Path));
List<Path> pluginPaths = repository.getPluginPaths();
assertEquals(1, pluginPaths.size());
assertTrue(repository.deletePluginPath(plugin1JarPath));
pluginPaths = repository.getPluginPaths();
assertEquals(0, pluginPaths.size());
}
}

73
pf4j/src/test/java/org/pf4j/LegacyExtensionFinderTest.java

@ -0,0 +1,73 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.io.TempDir;
import org.pf4j.plugin.PluginJar;
import org.pf4j.plugin.TestExtension;
import org.pf4j.plugin.TestPlugin;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
public class LegacyExtensionFinderTest {
@TempDir
Path pluginsPath;
@Test
@EnabledOnOs(WINDOWS)
public void shouldUnlockFileAfterReadingExtensionsFromPlugin() throws Exception {
PluginJar pluginJar = new PluginJar.Builder(pluginsPath.resolve("test-plugin.jar"), "test-plugin")
.pluginClass(TestPlugin.class.getName())
.pluginVersion("1.2.3")
.extension(TestExtension.class.getName())
.build();
assertTrue(pluginJar.file().exists());
PluginManager pluginManager = new JarPluginManager(pluginsPath);
pluginManager.loadPlugins();
assertEquals(1, pluginManager.getPlugins().size());
LegacyExtensionFinder extensionFinder = new LegacyExtensionFinder(pluginManager);
Map<String, Set<String>> pluginsStorages = extensionFinder.readPluginsStorages();
assertNotNull(pluginsStorages);
pluginManager.unloadPlugin(pluginJar.pluginId());
boolean fileDeleted = pluginJar.file().delete();
Set<String> pluginStorages = pluginsStorages.get(pluginJar.pluginId());
assertNotNull(pluginStorages);
assertEquals(1, pluginStorages.size());
assertThat(pluginStorages, contains(TestExtension.class.getName()));
assertTrue(fileDeleted);
assertFalse(pluginJar.file().exists());
}
}

4
pf4j/src/test/java/org/pf4j/LegacyExtensionStorageTest.java

@ -15,7 +15,7 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.pf4j.processor.LegacyExtensionStorage; import org.pf4j.processor.LegacyExtensionStorage;
import java.io.IOException; import java.io.IOException;
@ -24,7 +24,7 @@ import java.io.StringReader;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertEquals;
/** /**
* @author Decebal Suiu * @author Decebal Suiu

83
pf4j/src/test/java/org/pf4j/LoadPluginsTest.java

@ -15,42 +15,42 @@
*/ */
package org.pf4j; package org.pf4j;
import static org.hamcrest.CoreMatchers.containsString; import org.junit.jupiter.api.BeforeEach;
import static org.hamcrest.CoreMatchers.equalTo; import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.startsWith; import org.junit.jupiter.api.io.TempDir;
import static org.junit.Assert.assertEquals; import org.pf4j.plugin.PluginZip;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import org.junit.Before;
import org.junit.Rule; import static org.hamcrest.CoreMatchers.containsString;
import org.junit.Test; import static org.hamcrest.CoreMatchers.equalTo;
import org.junit.rules.TemporaryFolder; import static org.hamcrest.CoreMatchers.startsWith;
import org.pf4j.plugin.PluginZip; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class LoadPluginsTest { public class LoadPluginsTest {
private DefaultPluginManager pluginManager; private DefaultPluginManager pluginManager;
@Rule @TempDir
public TemporaryFolder pluginsFolder = new TemporaryFolder(); Path pluginsPath;
@Before @BeforeEach
public void setUp() { public void setUp() {
pluginManager = new DefaultPluginManager(pluginsFolder.getRoot().toPath()); pluginManager = new DefaultPluginManager(pluginsPath);
} }
@Test @Test
public void load() throws Exception { public void load() throws Exception {
PluginZip pluginZip = new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") PluginZip pluginZip = new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), "myPlugin")
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
@ -64,31 +64,32 @@ public class LoadPluginsTest {
assertEquals(pluginZip.pluginId(), pluginManager.idForPath(pluginZip.unzippedPath())); assertEquals(pluginZip.pluginId(), pluginManager.idForPath(pluginZip.unzippedPath()));
} }
@Test(expected = IllegalArgumentException.class) @Test
public void loadNonExisting() { public void loadNonExisting() {
pluginManager.loadPlugin(Paths.get("nonexisting")); assertThrows(IllegalArgumentException.class, () -> pluginManager.loadPlugin(Paths.get("nonexisting")));
} }
@Test(expected = PluginAlreadyLoadedException.class) @Test
public void loadTwiceFails() throws Exception { public void loadTwiceFails() throws Exception {
PluginZip pluginZip = new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") PluginZip pluginZip = new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), "myPlugin")
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
assertNotNull(pluginManager.loadPluginFromPath(pluginZip.path())); assertNotNull(pluginManager.loadPluginFromPath(pluginZip.path()));
assertNull(pluginManager.loadPluginFromPath(pluginZip.path()));
assertThrows(PluginAlreadyLoadedException.class, () -> pluginManager.loadPluginFromPath(pluginZip.path()));
} }
@Test @Test
public void loadPluginWithSameIdDifferentPathFails() throws Exception { public void loadPluginWithSameIdDifferentPathFails() throws Exception {
String pluginId = "myPlugin"; String pluginId = "myPlugin";
String pluginVersion = "1.2.3"; String pluginVersion = "1.2.3";
File plugin1Path = pluginsFolder.newFile("my-plugin-1.2.3.zip"); Path plugin1Path = pluginsPath.resolve("my-plugin-1.2.3.zip");
PluginZip plugin1 = new PluginZip.Builder(plugin1Path, pluginId) PluginZip plugin1 = new PluginZip.Builder(plugin1Path, pluginId)
.pluginVersion(pluginVersion) .pluginVersion(pluginVersion)
.build(); .build();
File plugin2Path = pluginsFolder.newFile("my-plugin-1.2.3-renamed.zip"); Path plugin2Path = pluginsPath.resolve("my-plugin-1.2.3-renamed.zip");
PluginZip plugin2 = new PluginZip.Builder(plugin2Path, pluginId) PluginZip plugin2 = new PluginZip.Builder(plugin2Path, pluginId)
.pluginVersion(pluginVersion) .pluginVersion(pluginVersion)
.build(); .build();
@ -101,7 +102,7 @@ public class LoadPluginsTest {
// Verify the second plugin is not loaded as it has the same metadata // Verify the second plugin is not loaded as it has the same metadata
pluginManager.loadPluginFromPath(plugin2.path()); pluginManager.loadPluginFromPath(plugin2.path());
fail("Expected loadPluginFromPath to fail"); fail("Expected loadPluginFromPath to fail");
} catch (PluginException e) { } catch (PluginRuntimeException e) {
// Check the path of the loaded plugin remains the same // Check the path of the loaded plugin remains the same
PluginWrapper loadedPlugin = pluginManager.getPlugin(pluginId); PluginWrapper loadedPlugin = pluginManager.getPlugin(pluginId);
assertThat(loadedPlugin.getPluginPath(), equalTo(loadedPlugin1Path)); assertThat(loadedPlugin.getPluginPath(), equalTo(loadedPlugin1Path));
@ -122,13 +123,13 @@ public class LoadPluginsTest {
public void loadPluginWithSameIdDifferentVersionsFails() throws Exception { public void loadPluginWithSameIdDifferentVersionsFails() throws Exception {
String pluginId = "myPlugin"; String pluginId = "myPlugin";
String plugin1Version = "1.2.3"; String plugin1Version = "1.2.3";
File plugin1Path = pluginsFolder.newFile("my-plugin-1.2.3.zip"); Path plugin1Path = pluginsPath.resolve("my-plugin-1.2.3.zip");
PluginZip plugin1 = new PluginZip.Builder(plugin1Path, pluginId) PluginZip plugin1 = new PluginZip.Builder(plugin1Path, pluginId)
.pluginVersion(plugin1Version) .pluginVersion(plugin1Version)
.build(); .build();
String plugin2Version = "2.0.0"; String plugin2Version = "2.0.0";
File plugin2Path = pluginsFolder.newFile("my-plugin-2.0.0.zip"); Path plugin2Path = pluginsPath.resolve("my-plugin-2.0.0.zip");
PluginZip plugin2 = new PluginZip.Builder(plugin2Path, pluginId) PluginZip plugin2 = new PluginZip.Builder(plugin2Path, pluginId)
.pluginVersion(plugin2Version) .pluginVersion(plugin2Version)
.build(); .build();
@ -140,7 +141,7 @@ public class LoadPluginsTest {
// Verify the second plugin is not loaded as it has the same pluginId // Verify the second plugin is not loaded as it has the same pluginId
pluginManager.loadPluginFromPath(plugin2.path()); pluginManager.loadPluginFromPath(plugin2.path());
fail("Expected loadPluginFromPath to fail"); fail("Expected loadPluginFromPath to fail");
} catch (PluginException e) { } catch (PluginRuntimeException e) {
// Check the path and version of the loaded plugin remain the same // Check the path and version of the loaded plugin remain the same
PluginWrapper loadedPlugin = pluginManager.getPlugin(pluginId); PluginWrapper loadedPlugin = pluginManager.getPlugin(pluginId);
assertThat(loadedPlugin.getPluginPath(), equalTo(loadedPlugin1Path)); assertThat(loadedPlugin.getPluginPath(), equalTo(loadedPlugin1Path));
@ -150,7 +151,7 @@ public class LoadPluginsTest {
@Test @Test
public void loadUnloadLoad() throws Exception { public void loadUnloadLoad() throws Exception {
PluginZip pluginZip = new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") PluginZip pluginZip = new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), "myPlugin")
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
@ -169,7 +170,7 @@ public class LoadPluginsTest {
public void upgrade() throws Exception { public void upgrade() throws Exception {
String pluginId = "myPlugin"; String pluginId = "myPlugin";
new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.zip"), pluginId) new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), pluginId)
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
@ -179,7 +180,7 @@ public class LoadPluginsTest {
assertEquals(1, pluginManager.getPlugins().size()); assertEquals(1, pluginManager.getPlugins().size());
assertEquals(1, pluginManager.getStartedPlugins().size()); assertEquals(1, pluginManager.getStartedPlugins().size());
PluginZip pluginZip2 = new PluginZip.Builder(pluginsFolder.newFile("my-plugin-2.0.0.ZIP"), pluginId) PluginZip pluginZip2 = new PluginZip.Builder(pluginsPath.resolve("my-plugin-2.0.0.ZIP"), pluginId)
.pluginVersion("2.0.0") .pluginVersion("2.0.0")
.build(); .build();
@ -196,12 +197,12 @@ public class LoadPluginsTest {
@Test @Test
public void getRoot() { public void getRoot() {
assertEquals(pluginsFolder.getRoot().toPath(), pluginManager.getPluginsRoot()); assertEquals(pluginsPath, pluginManager.getPluginsRoot());
} }
@Test @Test
public void notAPlugin() throws Exception { public void notAPlugin() {
pluginsFolder.newFile("not-a-zip"); pluginsPath.resolve("not-a-zip");
pluginManager.loadPlugins(); pluginManager.loadPlugins();
@ -210,11 +211,11 @@ public class LoadPluginsTest {
@Test @Test
public void deletePlugin() throws Exception { public void deletePlugin() throws Exception {
PluginZip pluginZip1 = new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") PluginZip pluginZip1 = new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), "myPlugin")
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
PluginZip pluginZip3 = new PluginZip.Builder(pluginsFolder.newFile("other-3.0.0.Zip"), "other") PluginZip pluginZip3 = new PluginZip.Builder(pluginsPath.resolve("other-3.0.0.Zip"), "other")
.pluginVersion("3.0.0") .pluginVersion("3.0.0")
.build(); .build();

218
pf4j/src/test/java/org/pf4j/ManifestPluginDescriptorFinderTest.java

@ -15,19 +15,23 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder; import org.pf4j.plugin.PluginJar;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.LinkedHashMap;
import java.util.List; import java.util.Map;
import java.util.jar.Manifest;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Mario Franco * @author Mario Franco
@ -36,42 +40,32 @@ import static org.junit.Assert.*;
public class ManifestPluginDescriptorFinderTest { public class ManifestPluginDescriptorFinderTest {
private VersionManager versionManager; private VersionManager versionManager;
private Path pluginsPath;
@Rule @TempDir
public TemporaryFolder pluginsFolder = new TemporaryFolder(); Path pluginsPath;
@Before @BeforeEach
public void setUp() throws IOException { public void setUp() throws IOException {
pluginsPath = pluginsFolder.getRoot().toPath(); Path pluginPath = Files.createDirectories(pluginsPath.resolve("test-plugin-1"));
storeManifestToPath(getPlugin1Manifest(), pluginPath);
Charset charset = Charset.forName("UTF-8"); pluginPath = Files.createDirectories(pluginsPath.resolve("test-plugin-2"));
storeManifestToPath(getPlugin2Manifest(), pluginPath);
Path pluginPath = pluginsFolder.newFolder("test-plugin-1", "classes", "META-INF").toPath();
Files.write(pluginPath.resolve("extensions.idx"), "org.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin1Manifest(), charset);
pluginPath = pluginsFolder.newFolder("test-plugin-2", "classes", "META-INF").toPath();
Files.write(pluginPath.resolve("extensions.idx"), "org.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin2Manifest(), charset);
// empty plugin // empty plugin
pluginsFolder.newFolder("test-plugin-3"); Files.createDirectories(pluginsPath.resolve("test-plugin-3"));
// no plugin class // no plugin class
pluginPath = pluginsFolder.newFolder("test-plugin-4", "classes", "META-INF").toPath(); pluginPath = Files.createDirectories(pluginsPath.resolve("test-plugin-4"));
Files.write(pluginPath.resolve("extensions.idx"), "org.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); storeManifestToPath(getPlugin4Manifest(), pluginPath);
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin4Manifest(), charset);
// no plugin version // no plugin version
pluginPath = pluginsFolder.newFolder("test-plugin-5", "classes", "META-INF").toPath(); pluginPath = Files.createDirectories(pluginsPath.resolve("test-plugin-5"));
Files.write(pluginPath.resolve("extensions.idx"), "org.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); storeManifestToPath(getPlugin5Manifest(), pluginPath);
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin5Manifest(), charset);
// no plugin id // no plugin id
pluginPath = pluginsFolder.newFolder("test-plugin-6", "classes", "META-INF").toPath(); pluginPath = Files.createDirectories(pluginsPath.resolve("test-plugin-6"));
Files.write(pluginPath.resolve("extensions.idx"), "org.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); storeManifestToPath(getPlugin6Manifest(), pluginPath);
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin6Manifest(), charset);
versionManager = new DefaultVersionManager(); versionManager = new DefaultVersionManager();
} }
@ -110,126 +104,68 @@ public class ManifestPluginDescriptorFinderTest {
/** /**
* Test of {@link ManifestPluginDescriptorFinder#find(Path)} method. * Test of {@link ManifestPluginDescriptorFinder#find(Path)} method.
*/ */
@Test(expected = PluginException.class) @Test
public void testFindNotFound() throws Exception { public void testFindNotFound() {
PluginDescriptorFinder descriptorFinder = new ManifestPluginDescriptorFinder(); PluginDescriptorFinder descriptorFinder = new ManifestPluginDescriptorFinder();
descriptorFinder.find(pluginsPath.resolve("test-plugin-3")); assertThrows(PluginRuntimeException.class, () -> descriptorFinder.find(pluginsPath.resolve("test-plugin-3")));
}
private Manifest getPlugin1Manifest() {
Map<String, String> map = new LinkedHashMap<>(8);
map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, "test-plugin-1");
map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, "org.pf4j.plugin.TestPlugin");
map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
map.put(ManifestPluginDescriptorFinder.PLUGIN_DESCRIPTION, "Test Plugin 1");
map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
map.put(ManifestPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "test-plugin-2,test-plugin-3@~1.0");
map.put(ManifestPluginDescriptorFinder.PLUGIN_REQUIRES, "*");
map.put(ManifestPluginDescriptorFinder.PLUGIN_LICENSE, "Apache-2.0");
return PluginJar.createManifest(map);
} }
private List<String> getPlugin1Manifest() { private Manifest getPlugin2Manifest() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(5);
"Manifest-Version: 1.0\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, "test-plugin-2");
+ "Implementation-Title: Test Plugin #1\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, "org.pf4j.plugin.TestPlugin");
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
+ "Archiver-Version: Plexus Archiver\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "Built-By: Mario Franco\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "");
+ "Specification-Title: Test Plugin #1\n"
+ "Implementation-Vendor-Id: org.pf4j.demo\n" return PluginJar.createManifest(map);
+ "Plugin-Version: 0.0.1\n"
+ "Plugin-Id: test-plugin-1\n"
+ "Plugin-Description: Test Plugin 1\n"
+ "Plugin-Provider: Decebal Suiu\n"
+ "Plugin-Class: org.pf4j.plugin.TestPlugin\n"
+ "Plugin-Dependencies: test-plugin-2,test-plugin-3@~1.0\n"
+ "Plugin-Requires: *\n"
+ "Plugin-License: Apache-2.0\n"
+ "Created-By: Apache Maven 3.0.5\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
} }
private List<String> getPlugin2Manifest() { private Manifest getPlugin4Manifest() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(3);
"Manifest-Version: 1.0\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, "test-plugin-1");
+ "Plugin-Dependencies: \n" map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
+ "Implementation-Title: Test Plugin #2\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n" return PluginJar.createManifest(map);
+ "Built-By: Mario Franco\n"
+ "Specification-Title: Test Plugin #2\n"
+ "Implementation-Vendor-Id: org.pf4j.demo\n"
+ "Plugin-Version: 0.0.1\n"
+ "Plugin-Id: test-plugin-2\n"
+ "Plugin-Provider: Decebal Suiu\n"
+ "Plugin-Class: org.pf4j.plugin.TestPlugin\n"
+ "Created-By: Apache Maven 3.0.5\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
} }
private List<String> getPlugin4Manifest() { private Manifest getPlugin5Manifest() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(3);
"Manifest-Version: 1.0\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, "test-plugin-2");
+ "Implementation-Title: Test Plugin #4\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, "org.pf4j.plugin.TestPlugin");
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "Archiver-Version: Plexus Archiver\n"
+ "Built-By: Mario Franco\n" return PluginJar.createManifest(map);
+ "Specification-Title: Test Plugin #4\n"
+ "Implementation-Vendor-Id: org.pf4j.demo\n"
+ "Plugin-Version: 0.0.1\n"
+ "Plugin-Id: test-plugin-2\n"
+ "Plugin-Provider: Decebal Suiu\n"
+ "Created-By: Apache Maven 3.0.5\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
} }
private List<String> getPlugin5Manifest() { private Manifest getPlugin6Manifest() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(2);
"Manifest-Version: 1.0\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, "org.pf4j.plugin.TestPlugin");
+ "Implementation-Title: Test Plugin #5\n" map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n" return PluginJar.createManifest(map);
+ "Built-By: Mario Franco\n"
+ "Specification-Title: Test Plugin #5\n"
+ "Implementation-Vendor-Id: org.pf4j.demo\n"
+ "Plugin-Id: test-plugin-2\n"
+ "Plugin-Provider: Decebal Suiu\n"
+ "Plugin-Class: org.pf4j.plugin.TestPlugin\n"
+ "Created-By: Apache Maven 3.0.5\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
} }
private List<String> getPlugin6Manifest() { private void storeManifestToPath(Manifest manifest, Path pluginPath) throws IOException {
String[] lines = new String[] { Path path = Files.createDirectory(pluginPath.resolve("META-INF"));
"Manifest-Version: 1.0\n" try (OutputStream output = new FileOutputStream(path.resolve("MANIFEST.MF").toFile())) {
+ "Implementation-Title: Test Plugin #6\n" manifest.write(output);
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" }
+ "Archiver-Version: Plexus Archiver\n"
+ "Built-By: Mario Franco\n"
+ "Specification-Title: Test Plugin #6\n"
+ "Implementation-Vendor-Id: org.pf4j.demo\n"
+ "Plugin-Provider: Decebal Suiu\n"
+ "Plugin-Class: org.pf4j.plugin.TestPlugin\n"
+ "Created-By: Apache Maven 3.0.5\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
} }
} }

19
pf4j/src/test/java/org/pf4j/PluginDependencyTest.java

@ -15,9 +15,12 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Test;
import static org.junit.Assert.*; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Mario Franco * @author Mario Franco
@ -32,33 +35,33 @@ public class PluginDependencyTest {
PluginDependency instance = new PluginDependency("test"); PluginDependency instance = new PluginDependency("test");
assertEquals("test", instance.getPluginId()); assertEquals("test", instance.getPluginId());
assertEquals("*", instance.getPluginVersionSupport()); assertEquals("*", instance.getPluginVersionSupport());
assertEquals(false, instance.isOptional()); assertFalse(instance.isOptional());
instance = new PluginDependency("test@"); instance = new PluginDependency("test@");
assertEquals("test", instance.getPluginId()); assertEquals("test", instance.getPluginId());
assertEquals("*", instance.getPluginVersionSupport()); assertEquals("*", instance.getPluginVersionSupport());
assertEquals(false, instance.isOptional()); assertFalse(instance.isOptional());
instance = new PluginDependency("test?"); instance = new PluginDependency("test?");
assertEquals("test", instance.getPluginId()); assertEquals("test", instance.getPluginId());
assertEquals("*", instance.getPluginVersionSupport()); assertEquals("*", instance.getPluginVersionSupport());
assertEquals(true, instance.isOptional()); assertTrue(instance.isOptional());
instance = new PluginDependency("test?@"); instance = new PluginDependency("test?@");
assertEquals("test", instance.getPluginId()); assertEquals("test", instance.getPluginId());
assertEquals("*", instance.getPluginVersionSupport()); assertEquals("*", instance.getPluginVersionSupport());
assertEquals(true, instance.isOptional()); assertTrue(instance.isOptional());
instance = new PluginDependency("test@1.0"); instance = new PluginDependency("test@1.0");
assertEquals("test", instance.getPluginId()); assertEquals("test", instance.getPluginId());
assertEquals("1.0", instance.getPluginVersionSupport()); assertEquals("1.0", instance.getPluginVersionSupport());
assertEquals(false, instance.isOptional()); assertFalse(instance.isOptional());
assertEquals("PluginDependency [pluginId=test, pluginVersionSupport=1.0, optional=false]", instance.toString()); assertEquals("PluginDependency [pluginId=test, pluginVersionSupport=1.0, optional=false]", instance.toString());
instance = new PluginDependency("test?@1.0"); instance = new PluginDependency("test?@1.0");
assertEquals("test", instance.getPluginId()); assertEquals("test", instance.getPluginId());
assertEquals("1.0", instance.getPluginVersionSupport()); assertEquals("1.0", instance.getPluginVersionSupport());
assertEquals(true, instance.isOptional()); assertTrue(instance.isOptional());
assertEquals("PluginDependency [pluginId=test, pluginVersionSupport=1.0, optional=true]", instance.toString()); assertEquals("PluginDependency [pluginId=test, pluginVersionSupport=1.0, optional=true]", instance.toString());
} }

193
pf4j/src/test/java/org/pf4j/PropertiesPluginDescriptorFinderTest.java

@ -15,55 +15,57 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder;
import org.pf4j.plugin.PluginZip; import org.pf4j.plugin.PluginZip;
import org.pf4j.plugin.TestPlugin;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.LinkedHashMap;
import java.util.List; import java.util.Map;
import java.util.Properties;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class PropertiesPluginDescriptorFinderTest { public class PropertiesPluginDescriptorFinderTest {
private VersionManager versionManager; private VersionManager versionManager;
private Path pluginsPath;
@Rule @TempDir
public TemporaryFolder pluginsFolder = new TemporaryFolder(); Path pluginsPath;
@Before @BeforeEach
public void setUp() throws IOException { public void setUp() throws IOException {
pluginsPath = pluginsFolder.getRoot().toPath(); Path pluginPath = Files.createDirectory(pluginsPath.resolve("test-plugin-1"));
storePropertiesToPath(getPlugin1Properties(), pluginPath);
Charset charset = Charset.forName("UTF-8"); pluginPath = Files.createDirectory(pluginsPath.resolve("test-plugin-2"));
storePropertiesToPath(getPlugin2Properties(), pluginPath);
Path pluginPath = pluginsFolder.newFolder("test-plugin-1").toPath();
Files.write(pluginPath.resolve("plugin.properties"), getPlugin1Properties(), charset);
pluginPath = pluginsFolder.newFolder("test-plugin-2").toPath();
Files.write(pluginPath.resolve("plugin.properties"), getPlugin2Properties(), charset);
// empty plugin // empty plugin
pluginsFolder.newFolder("test-plugin-3"); Files.createDirectories(pluginsPath.resolve("test-plugin-3"));
// no plugin class // no plugin class
pluginPath = pluginsFolder.newFolder("test-plugin-4").toPath(); pluginPath = Files.createDirectory(pluginsPath.resolve("test-plugin-4"));
Files.write(pluginPath.resolve("plugin.properties"), getPlugin4Properties(), charset); storePropertiesToPath(getPlugin4Properties(), pluginPath);
// no plugin version // no plugin version
pluginPath = pluginsFolder.newFolder("test-plugin-5").toPath(); pluginPath = Files.createDirectory(pluginsPath.resolve("test-plugin-5"));
Files.write(pluginPath.resolve("plugin.properties"), getPlugin5Properties(), charset); storePropertiesToPath(getPlugin5Properties(), pluginPath);
// no plugin id // no plugin id
pluginPath = pluginsFolder.newFolder("test-plugin-6").toPath(); pluginPath = Files.createDirectory(pluginsPath.resolve("test-plugin-6"));
Files.write(pluginPath.resolve("plugin.properties"), getPlugin6Properties(), charset); storePropertiesToPath(getPlugin6Properties(), pluginPath);
versionManager = new DefaultVersionManager(); versionManager = new DefaultVersionManager();
} }
@ -77,7 +79,7 @@ public class PropertiesPluginDescriptorFinderTest {
assertEquals("test-plugin-1", plugin1.getPluginId()); assertEquals("test-plugin-1", plugin1.getPluginId());
assertEquals("Test Plugin 1", plugin1.getPluginDescription()); assertEquals("Test Plugin 1", plugin1.getPluginDescription());
assertEquals("org.pf4j.plugin.TestPlugin", plugin1.getPluginClass()); assertEquals(TestPlugin.class.getName(), plugin1.getPluginClass());
assertEquals("0.0.1", plugin1.getVersion()); assertEquals("0.0.1", plugin1.getVersion());
assertEquals("Decebal Suiu", plugin1.getProvider()); assertEquals("Decebal Suiu", plugin1.getProvider());
assertEquals(2, plugin1.getDependencies().size()); assertEquals(2, plugin1.getDependencies().size());
@ -91,7 +93,7 @@ public class PropertiesPluginDescriptorFinderTest {
assertEquals("test-plugin-2", plugin2.getPluginId()); assertEquals("test-plugin-2", plugin2.getPluginId());
assertEquals("", plugin2.getPluginDescription()); assertEquals("", plugin2.getPluginDescription());
assertEquals("org.pf4j.plugin.TestPlugin", plugin2.getPluginClass()); assertEquals(TestPlugin.class.getName(), plugin2.getPluginClass());
assertEquals("0.0.1", plugin2.getVersion()); assertEquals("0.0.1", plugin2.getVersion());
assertEquals("Decebal Suiu", plugin2.getProvider()); assertEquals("Decebal Suiu", plugin2.getProvider());
assertEquals(0, plugin2.getDependencies().size()); assertEquals(0, plugin2.getDependencies().size());
@ -99,98 +101,75 @@ public class PropertiesPluginDescriptorFinderTest {
assertTrue(versionManager.checkVersionConstraint("1.0.0", plugin2.getRequires())); assertTrue(versionManager.checkVersionConstraint("1.0.0", plugin2.getRequires()));
} }
@Test(expected = PluginException.class) @Test
public void testNotFound() throws Exception { public void testNotFound() {
PluginDescriptorFinder descriptorFinder = new PropertiesPluginDescriptorFinder(); PluginDescriptorFinder descriptorFinder = new PropertiesPluginDescriptorFinder();
descriptorFinder.find(pluginsPath.resolve("test-plugin-3")); assertThrows(PluginRuntimeException.class, () -> descriptorFinder.find(pluginsPath.resolve("test-plugin-3")));
} }
@Test private Properties getPlugin1Properties() {
public void findInJar() throws Exception { Map<String, String> map = new LinkedHashMap<>(8);
PluginZip pluginJar = new PluginZip.Builder(pluginsFolder.newFile("my-plugin-1.2.3.jar"), "myPlugin") map.put(PropertiesPluginDescriptorFinder.PLUGIN_ID, "test-plugin-1");
.pluginVersion("1.2.3") map.put(PropertiesPluginDescriptorFinder.PLUGIN_CLASS, TestPlugin.class.getName());
.build(); map.put(PropertiesPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_DESCRIPTION, "Test Plugin 1");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "test-plugin-2,test-plugin-3@~1.0");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, ">=1");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_LICENSE, "Apache-2.0");
return PluginZip.createProperties(map);
}
assertTrue(Files.exists(pluginJar.path())); private Properties getPlugin2Properties() {
Map<String, String> map = new LinkedHashMap<>(5);
map.put(PropertiesPluginDescriptorFinder.PLUGIN_ID, "test-plugin-2");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_CLASS, TestPlugin.class.getName());
map.put(PropertiesPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "");
PluginDescriptorFinder descriptorFinder = new PropertiesPluginDescriptorFinder(); return PluginZip.createProperties(map);
PluginDescriptor pluginDescriptor = descriptorFinder.find(pluginJar.path());
assertNotNull(pluginDescriptor);
assertEquals("myPlugin", pluginJar.pluginId());
assertEquals("1.2.3", pluginJar.pluginVersion());
} }
private List<String> getPlugin1Properties() { private Properties getPlugin4Properties() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(5);
"plugin.id=test-plugin-1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_ID, "test-plugin-2");
+ "plugin.version=0.0.1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
+ "plugin.description=Test Plugin 1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "plugin.provider=Decebal Suiu\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "");
+ "plugin.class=org.pf4j.plugin.TestPlugin\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, "*");
+ "plugin.dependencies=test-plugin-2,test-plugin-3@~1.0\n"
+ "plugin.requires=>=1\n"
+ "plugin.license=Apache-2.0\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
}
private List<String> getPlugin2Properties() { return PluginZip.createProperties(map);
String[] lines = new String[] {
"plugin.id=test-plugin-2\n"
+ "plugin.version=0.0.1\n"
+ "plugin.provider=Decebal Suiu\n"
+ "plugin.class=org.pf4j.plugin.TestPlugin\n"
+ "plugin.dependencies=\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
} }
private List<String> getPlugin4Properties() { private Properties getPlugin5Properties() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(5);
"plugin.id=test-plugin-2\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_ID, "test-plugin-2");
+ "plugin.version=0.0.1\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_CLASS, TestPlugin.class.getName());
+ "plugin.provider=Decebal Suiu\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "plugin.dependencies=\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "");
+ "plugin.requires=*\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, "*");
+ "\n"
+ "" return PluginZip.createProperties(map);
};
return Arrays.asList(lines);
} }
private List<String> getPlugin5Properties() { private Properties getPlugin6Properties() {
String[] lines = new String[] { Map<String, String> map = new LinkedHashMap<>(5);
"plugin.id=test-plugin-2\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_CLASS, TestPlugin.class.getName());
+ "plugin.provider=Decebal Suiu\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1");
+ "plugin.class=org.pf4j.plugin.TestPlugin\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu");
+ "plugin.dependencies=\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, "");
+ "plugin.requires=*\n" map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, "*");
+ "\n"
+ "" return PluginZip.createProperties(map);
};
return Arrays.asList(lines);
} }
private List<String> getPlugin6Properties() { private void storePropertiesToPath(Properties properties, Path pluginPath) throws IOException {
String[] lines = new String[] { Path path = pluginPath.resolve(PropertiesPluginDescriptorFinder.DEFAULT_PROPERTIES_FILE_NAME);
"plugin.version=0.0.1\n" try (Writer writer = new OutputStreamWriter(new FileOutputStream(path.toFile()), StandardCharsets.UTF_8)) {
+ "plugin.provider=Decebal Suiu\n" properties.store(writer, "");
+ "plugin.class=org.pf4j.plugin.TestPlugin\n" }
+ "plugin.dependencies=\n"
+ "plugin.requires=*\n"
+ "\n"
+ ""
};
return Arrays.asList(lines);
} }
} }

5
pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java

@ -15,11 +15,12 @@
*/ */
package org.pf4j; package org.pf4j;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.pf4j.plugin.FailTestExtension; import org.pf4j.plugin.FailTestExtension;
import org.pf4j.plugin.TestExtension; import org.pf4j.plugin.TestExtension;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
/** /**
* @author Decebal Suiu * @author Decebal Suiu

33
pf4j/src/test/java/org/pf4j/plugin/ClassDataProvider.java

@ -0,0 +1,33 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.plugin;
/**
* Defines the interface for classes that know to supply class data for a class name.
* The idea is to have the possibility to retrieve the data for a class from different sources:
* <ul>
* <li>Class path - the class is already loaded by the class loader</li>
* <li>String - the string (the source code) is compiled dynamically via {@link javax.tools.JavaCompiler}</>
* <li>Generate the source code programmatically using something like {@code https://github.com/square/javapoet}</li>
* </ul>
*
* @author Decebal Suiu
*/
public interface ClassDataProvider {
byte[] getClassData(String className);
}

55
pf4j/src/test/java/org/pf4j/plugin/DefaultClassDataProvider.java

@ -0,0 +1,55 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.plugin;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Get class data from the class path.
*
* @author Decebal Suiu
*/
public class DefaultClassDataProvider implements ClassDataProvider {
@Override
public byte[] getClassData(String className) {
String path = className.replace('.', '/') + ".class";
InputStream classDataStream = getClass().getClassLoader().getResourceAsStream(path);
if (classDataStream == null) {
throw new RuntimeException("Cannot find class data");
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
copyStream(classDataStream, outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private void copyStream(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}

5
pf4j/src/test/java/org/pf4j/plugin/FailTestExtension.java

@ -26,4 +26,9 @@ public class FailTestExtension implements TestExtensionPoint {
public FailTestExtension(String name) { public FailTestExtension(String name) {
} }
@Override
public String saySomething() {
return "I am a fail test extension";
}
} }

200
pf4j/src/test/java/org/pf4j/plugin/PluginJar.java

@ -0,0 +1,200 @@
/*
* Copyright (C) 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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.plugin;
import org.pf4j.ManifestPluginDescriptorFinder;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
/**
* Represents a plugin {@code jar} file.
* The {@code MANIFEST.MF} file is created on the fly from the information supplied in {@link Builder}.
*
* @author Decebal Suiu
*/
public class PluginJar {
private final Path path;
private final String pluginId;
private final String pluginClass;
private final String pluginVersion;
protected PluginJar(Builder builder) {
this.path = builder.path;
this.pluginId = builder.pluginId;
this.pluginClass = builder.pluginClass;
this.pluginVersion = builder.pluginVersion;
}
public Path path() {
return path;
}
public File file() {
return path.toFile();
}
public String pluginClass() {
return pluginClass;
}
public String pluginId() {
return pluginId;
}
public String pluginVersion() {
return pluginVersion;
}
public static Manifest createManifest(Map<String, String> map) {
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
for (Map.Entry<String, String> entry : map.entrySet()) {
attributes.put(new Attributes.Name(entry.getKey()), entry.getValue());
}
return manifest;
}
public static class Builder {
private final Path path;
private final String pluginId;
private String pluginClass;
private String pluginVersion;
private Map<String, String> manifestAttributes = new LinkedHashMap<>();
private Set<String> extensions = new LinkedHashSet<>();
private ClassDataProvider classDataProvider = new DefaultClassDataProvider();
public Builder(Path path, String pluginId) {
this.path = path;
this.pluginId = pluginId;
}
public Builder pluginClass(String pluginClass) {
this.pluginClass = pluginClass;
return this;
}
public Builder pluginVersion(String pluginVersion) {
this.pluginVersion = pluginVersion;
return this;
}
/**
* Add extra attributes to the {@code manifest} file.
* As possible attribute name please see {@link ManifestPluginDescriptorFinder}.
*/
public Builder manifestAttributes(Map<String, String> manifestAttributes) {
this.manifestAttributes.putAll(manifestAttributes);
return this;
}
/**
* Add extra attribute to the {@code manifest} file.
* As possible attribute name please see {@link ManifestPluginDescriptorFinder}.
*/
public Builder manifestAttribute(String name, String value) {
manifestAttributes.put(name, value);
return this;
}
public Builder extension(String extensionClassName) {
extensions.add(extensionClassName);
return this;
}
public Builder classDataProvider(ClassDataProvider classDataProvider) {
this.classDataProvider = classDataProvider;
return this;
}
public PluginJar build() throws IOException {
Manifest manifest = createManifest();
try (OutputStream outputStream = new FileOutputStream(path.toFile())) {
JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest);
if (!extensions.isEmpty()) {
// add extensions.idx
JarEntry jarEntry = new JarEntry("META-INF/extensions.idx");
jarOutputStream.putNextEntry(jarEntry);
jarOutputStream.write(extensionsAsByteArray());
jarOutputStream.closeEntry();
// add extensions classes
for (String extension : extensions) {
String extensionPath = extension.replace('.', '/') + ".class";
JarEntry classEntry = new JarEntry(extensionPath);
jarOutputStream.putNextEntry(classEntry);
jarOutputStream.write(classDataProvider.getClassData(extension));
jarOutputStream.closeEntry();
}
}
jarOutputStream.close();
}
return new PluginJar(this);
}
private Manifest createManifest() {
Map<String, String> map = new LinkedHashMap<>();
map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, pluginId);
map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, pluginVersion);
if (pluginClass != null) {
map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, pluginClass);
}
if (manifestAttributes != null) {
map.putAll(manifestAttributes);
}
return PluginJar.createManifest(map);
}
private byte[] extensionsAsByteArray() throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
PrintWriter writer = new PrintWriter(outputStream);
for (String extension : extensions) {
writer.println(extension);
}
writer.flush();
return outputStream.toByteArray();
}
}
}
}

86
pf4j/src/test/java/org/pf4j/plugin/PluginZip.java

@ -15,44 +15,54 @@
*/ */
package org.pf4j.plugin; package org.pf4j.plugin;
import org.pf4j.PropertiesPluginDescriptorFinder;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
/** /**
* Represents a plugin zip/jar file. * Represents a plugin {@code zip} file.
* The "plugin.properties" file is created on the fly from the information supplied in Builder. * The {@code plugin.properties} file is created on the fly from the information supplied in {@link Builder}.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class PluginZip { public class PluginZip {
private final File file; private final Path path;
private final String pluginId; private final String pluginId;
private final String pluginClass;
private final String pluginVersion; private final String pluginVersion;
protected PluginZip(Builder builder) { protected PluginZip(Builder builder) {
this.file = builder.file; this.path = builder.path;
this.pluginId = builder.pluginId; this.pluginId = builder.pluginId;
this.pluginClass = builder.pluginClass;
this.pluginVersion = builder.pluginVersion; this.pluginVersion = builder.pluginVersion;
} }
public File file() { public Path path() {
return file; return path;
} }
public Path path() { public File file() {
return file.toPath(); return path.toFile();
} }
public String pluginId() { public String pluginId() {
return pluginId; return pluginId;
} }
public String pluginClass() {
return pluginClass;
}
public String pluginVersion() { public String pluginVersion() {
return pluginVersion; return pluginVersion;
} }
@ -64,24 +74,59 @@ public class PluginZip {
return path.getParent().resolve(fileName.substring(0, fileName.length() - 4)); // without ".zip" suffix return path.getParent().resolve(fileName.substring(0, fileName.length() - 4)); // without ".zip" suffix
} }
public static Properties createProperties(Map<String, String> map) {
Properties properties = new Properties();
properties.putAll(map);
return properties;
}
public static class Builder { public static class Builder {
private final File file; private final Path path;
private final String pluginId; private final String pluginId;
private String pluginClass;
private String pluginVersion; private String pluginVersion;
private Map<String, String> properties = new LinkedHashMap<>();
public Builder(File file, String pluginId) { public Builder(Path path, String pluginId) {
this.file = file; this.path = path;
this.pluginId = pluginId; this.pluginId = pluginId;
} }
public Builder pluginClass(String pluginClass) {
this.pluginClass = pluginClass;
return this;
}
public Builder pluginVersion(String pluginVersion) { public Builder pluginVersion(String pluginVersion) {
this.pluginVersion = pluginVersion; this.pluginVersion = pluginVersion;
return this; return this;
} }
/**
* Add extra properties to the {@code properties} file.
* As possible attribute name please see {@link PropertiesPluginDescriptorFinder}.
*/
public Builder properties(Map<String, String> properties) {
this.properties.putAll(properties);
return this;
}
/**
* Add extra property to the {@code properties} file.
* As possible property name please see {@link PropertiesPluginDescriptorFinder}.
*/
public Builder property(String name, String value) {
properties.put(name, value);
return this;
}
public PluginZip build() throws IOException { public PluginZip build() throws IOException {
createPropertiesFile(); createPropertiesFile();
@ -89,15 +134,20 @@ public class PluginZip {
} }
protected void createPropertiesFile() throws IOException { protected void createPropertiesFile() throws IOException {
Properties properties = new Properties(); Map<String, String> map = new LinkedHashMap<>();
properties.setProperty("plugin.id", pluginId); map.put(PropertiesPluginDescriptorFinder.PLUGIN_ID, pluginId);
properties.setProperty("plugin.version", pluginVersion); map.put(PropertiesPluginDescriptorFinder.PLUGIN_VERSION, pluginVersion);
properties.setProperty("plugin.class", "org.pf4j.plugin.TestPlugin"); if (pluginClass != null) {
map.put(PropertiesPluginDescriptorFinder.PLUGIN_CLASS, pluginClass);
}
if (properties != null) {
map.putAll(properties);
}
ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(file)); ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(path.toFile()));
ZipEntry propertiesFile = new ZipEntry("plugin.properties"); ZipEntry propertiesFile = new ZipEntry(PropertiesPluginDescriptorFinder.DEFAULT_PROPERTIES_FILE_NAME);
outputStream.putNextEntry(propertiesFile); outputStream.putNextEntry(propertiesFile);
properties.store(outputStream, ""); createProperties(map).store(outputStream, "");
outputStream.closeEntry(); outputStream.closeEntry();
outputStream.close(); outputStream.close();
} }

5
pf4j/src/test/java/org/pf4j/plugin/TestExtension.java

@ -23,4 +23,9 @@ import org.pf4j.Extension;
@Extension @Extension
public class TestExtension implements TestExtensionPoint { public class TestExtension implements TestExtensionPoint {
@Override
public String saySomething() {
return "I am a test extension";
}
} }

2
pf4j/src/test/java/org/pf4j/plugin/TestExtensionPoint.java

@ -22,4 +22,6 @@ import org.pf4j.ExtensionPoint;
*/ */
public interface TestExtensionPoint extends ExtensionPoint { public interface TestExtensionPoint extends ExtensionPoint {
String saySomething();
} }

15
pf4j/src/test/java/org/pf4j/processor/ServiceProviderExtensionStorageTest.java

@ -16,19 +16,23 @@
package org.pf4j.processor; package org.pf4j.processor;
import org.junit.Test; import org.junit.jupiter.api.Test;
import javax.annotation.processing.Filer; import javax.annotation.processing.Filer;
import javax.tools.FileObject; import javax.tools.FileObject;
import javax.tools.StandardLocation; import javax.tools.StandardLocation;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.*; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is; import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
@ -46,7 +50,6 @@ public class ServiceProviderExtensionStorageTest {
assertThat(entries.contains("World"), is(true)); assertThat(entries.contains("World"), is(true));
} }
@Test @Test
public void ensureReadingExtensionsProducesCorrectListOfExtensions() { public void ensureReadingExtensionsProducesCorrectListOfExtensions() {
final StringReader file = new StringReader("#hello\n World"); final StringReader file = new StringReader("#hello\n World");
@ -56,6 +59,7 @@ public class ServiceProviderExtensionStorageTest {
given(processor.getExtensions()).willReturn(extensions); given(processor.getExtensions()).willReturn(extensions);
ServiceProviderExtensionStorage extensionStorage = new ServiceProviderExtensionStorage(processor) { ServiceProviderExtensionStorage extensionStorage = new ServiceProviderExtensionStorage(processor) {
@Override @Override
protected Filer getFiler() { protected Filer getFiler() {
try { try {
@ -72,6 +76,7 @@ public class ServiceProviderExtensionStorageTest {
throw new IllegalStateException("Shouldn't have gotten here"); throw new IllegalStateException("Shouldn't have gotten here");
} }
} }
}; };
Map<String, Set<String>> read = extensionStorage.read(); Map<String, Set<String>> read = extensionStorage.read();

98
pf4j/src/test/java/org/pf4j/util/DirectedGraphTest.java

@ -0,0 +1,98 @@
/*
* Copyright 2015 Decebal Suiu
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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 org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Decebal Suiu
*/
public class DirectedGraphTest {
private static DirectedGraph<Character> graph;
@BeforeAll
public static void setUp() {
graph = new DirectedGraph<>();
// add vertex
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addVertex('F');
graph.addVertex('G');
// add edges
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
graph.addEdge('B', 'F');
graph.addEdge('D', 'E');
graph.addEdge('F', 'G');
}
@Test
public void reverseTopologicalSort() {
List<Character> result = graph.reverseTopologicalSort();
List<Character> expected = Arrays.asList('C', 'G', 'F', 'B', 'A', 'E', 'D');
assertEquals(expected, result);
}
@Test
public void topologicalSort() {
List<Character> result = graph.topologicalSort();
List<Character> expected = Arrays.asList('D', 'E', 'A', 'B', 'F', 'G', 'C');
assertEquals(expected, result);
}
@Test
public void inDegree() {
Map<Character, Integer> result = graph.inDegree();
Map<Character, Integer> expected = new HashMap<>(7);
expected.put('A', 0);
expected.put('B', 1);
expected.put('C', 1);
expected.put('D', 0);
expected.put('E', 1);
expected.put('F', 1);
expected.put('G', 1);
assertEquals(expected, result);
}
@Test
public void outDegree() {
Map<Character, Integer> result = graph.outDegree();
Map<Character, Integer> expected = new HashMap<>(7);
expected.put('A', 1);
expected.put('B', 2);
expected.put('C', 0);
expected.put('D', 1);
expected.put('E', 0);
expected.put('F', 1);
expected.put('G', 0);
assertEquals(expected, result);
}
}

18
pf4j/src/test/java/org/pf4j/util/FileUtilsTest.java

@ -15,24 +15,24 @@
*/ */
package org.pf4j.util; package org.pf4j.util;
import org.junit.Rule; import org.junit.jupiter.api.Test;
import org.junit.Test; import org.junit.jupiter.api.io.TempDir;
import org.junit.rules.TemporaryFolder;
import org.pf4j.plugin.PluginZip; import org.pf4j.plugin.PluginZip;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class FileUtilsTest { public class FileUtilsTest {
@Rule @TempDir
public TemporaryFolder testFolder = new TemporaryFolder(); Path pluginsPath;
@Test @Test
public void expandIfZip() throws Exception { public void expandIfZip() throws Exception {
PluginZip pluginZip = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") PluginZip pluginZip = new PluginZip.Builder(pluginsPath.resolve("my-plugin-1.2.3.zip"), "myPlugin")
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.build(); .build();
@ -41,10 +41,10 @@ public class FileUtilsTest {
assertTrue(Files.exists(unzipped.resolve("plugin.properties"))); assertTrue(Files.exists(unzipped.resolve("plugin.properties")));
// File without .suffix // File without .suffix
Path extra = testFolder.newFile("extra").toPath(); Path extra = pluginsPath.resolve("extra");
assertEquals(extra, FileUtils.expandIfZip(extra)); assertEquals(extra, FileUtils.expandIfZip(extra));
// Folder // Folder
Path folder = testFolder.newFile("folder").toPath(); Path folder = pluginsPath.resolve("folder");
assertEquals(folder, FileUtils.expandIfZip(folder)); assertEquals(folder, FileUtils.expandIfZip(folder));
} }

10
pom.xml

@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.pf4j</groupId> <groupId>org.pf4j</groupId>
<artifactId>pf4j-parent</artifactId> <artifactId>pf4j-parent</artifactId>
<version>2.7.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>PF4J Parent</name> <name>PF4J Parent</name>
<description>Plugin Framework for Java</description> <description>Plugin Framework for Java</description>
@ -43,11 +43,12 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version> <maven.compiler.release>8</maven.compiler.release>
<slf4j.version>1.7.25</slf4j.version> <slf4j.version>1.7.25</slf4j.version>
<asm.version>7.1</asm.version> <asm.version>7.1</asm.version>
<junit.version>4.12</junit.version> <junit.version>5.4.0</junit.version>
<hamcrest.version>2.1</hamcrest.version> <hamcrest.version>2.1</hamcrest.version>
<mockito.version>2.24.0</mockito.version> <mockito.version>2.24.0</mockito.version>
<cobertura.version>2.7</cobertura.version> <cobertura.version>2.7</cobertura.version>
@ -66,8 +67,6 @@
<version>3.8.0</version> <version>3.8.0</version>
<configuration> <configuration>
<showWarnings>true</showWarnings> <showWarnings>true</showWarnings>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration> </configuration>
</plugin> </plugin>
@ -161,6 +160,7 @@
<modules> <modules>
<module>pf4j</module> <module>pf4j</module>
<module>demo</module> <module>demo</module>
<module>maven-archetypes/quickstart</module>
</modules> </modules>
<profiles> <profiles>

Loading…
Cancel
Save