Browse Source

Merge branch 'master' into patch-1

pull/499/head
Decebal Suiu 1 year ago
parent
commit
038c607308
No account linked to committer's email address
40 changed files with 991 additions and 726 deletions
  1. 0
    5
      .travis.yml
  2. 35
    59
      CHANGELOG.md
  3. 4
    10
      README.md
  4. 2
    2
      demo/api/pom.xml
  5. 11
    8
      demo/api/src/main/java/org/pf4j/demo/api/DemoPlugin.java
  6. 14
    12
      demo/api/src/main/java/org/pf4j/demo/api/PluginContext.java
  7. 2
    2
      demo/app/pom.xml
  8. 23
    31
      demo/app/src/main/java/org/pf4j/demo/Boot.java
  9. 40
    0
      demo/app/src/main/java/org/pf4j/demo/DemoPluginFactory.java
  10. 42
    0
      demo/app/src/main/java/org/pf4j/demo/DemoPluginManager.java
  11. 2
    2
      demo/plugins/plugin1/pom.xml
  12. 10
    11
      demo/plugins/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java
  13. 2
    2
      demo/plugins/plugin2/pom.xml
  14. 7
    7
      demo/plugins/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java
  15. 2
    2
      demo/plugins/pom.xml
  16. 2
    2
      demo/pom.xml
  17. 1
    1
      maven-archetypes/quickstart/pom.xml
  18. 3
    3
      pf4j/pom.xml
  19. 2
    3
      pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java
  20. 6
    6
      pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
  21. 6
    6
      pf4j/src/main/java/org/pf4j/ClassLoadingStrategy.java
  22. 20
    6
      pf4j/src/main/java/org/pf4j/DefaultPluginFactory.java
  23. 12
    0
      pf4j/src/main/java/org/pf4j/Plugin.java
  24. 308
    305
      pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java
  25. 3
    1
      pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java
  26. 2
    30
      pf4j/src/main/java/org/pf4j/util/ClassUtils.java
  27. 2
    0
      pf4j/src/main/java/org/pf4j/util/StringUtils.java
  28. 41
    71
      pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java
  29. 22
    2
      pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java
  30. 62
    6
      pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java
  31. 39
    54
      pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java
  32. 1
    2
      pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java
  33. 5
    15
      pf4j/src/test/java/org/pf4j/test/DefaultClassDataProvider.java
  34. 0
    26
      pf4j/src/test/java/org/pf4j/test/FailTestPlugin.java
  35. 73
    0
      pf4j/src/test/java/org/pf4j/test/JavaFileObjectClassLoader.java
  36. 62
    0
      pf4j/src/test/java/org/pf4j/test/JavaFileObjectDataProvider.java
  37. 52
    0
      pf4j/src/test/java/org/pf4j/test/JavaFileObjectUtils.java
  38. 64
    0
      pf4j/src/test/java/org/pf4j/test/JavaSources.java
  39. 1
    2
      pf4j/src/test/java/org/pf4j/test/TestPlugin.java
  40. 6
    32
      pom.xml

+ 0
- 5
.travis.yml View File

@@ -1,5 +0,0 @@
language: java
jdk:
- openjdk11
after_success:
- mvn clean cobertura:cobertura coveralls:report

+ 35
- 59
CHANGELOG.md View File

@@ -4,6 +4,34 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### [Unreleased][unreleased]

#### Fixed

#### Changed

#### Added

#### Removed

### [3.9.0] - 2023-01-30

#### Changed
- [#512]: Relax Plugin construction (remove dependency on PluginWrapper)

### [3.8.0] - 2022-10-27

#### Fixed
- [#492]: Loading extensions crashes kotlin application
- [#508]: Not create extensions.idx if no extensions exist
#### Changed
- Make ServiceProviderExtensionFinder optional in demo (commented code)

#### Added
- Add support for reading plugin descriptor from zip
- Use logger instead of System.out.println for demo

### [3.7.0] - 2022-06-28

#### Fixed
- [#435]: Fix the path separator used in the SingletonExtensionFactoryTest.java
- [#451]: Fix Dependency version check fails even if required is '*'
@@ -21,20 +49,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Add code analyses via Sonar
- Add support for reading plugin descriptor from zip

#### Removed

### [3.6.0] - 2021-01-16

#### Fixed
- [#394]: `DependencyResolver` lost dependent info after plugin stop

#### Changed

#### Added
- [#415]: Externalize some useful classes from testing

#### Removed

### [3.5.0] - 2020-11-08

#### Fixed
@@ -50,20 +72,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#400]: Add support for JPMS (`module-info.java`)
- [#404]: Support multiple plugin root directories

#### Removed

### [3.4.1] - 2020-08-14

#### Fixed
- [#371]: `ClosedFileSystemException` when I run demo (Windows)
- [#391]: Incorrect enum selection in `ClassLoadingStrategy.ADP`

#### Changed

#### Added

#### Removed

### [3.3.0] - 2020-04-21

#### Fixed
@@ -80,8 +94,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#352]: Add `equals`/`hashCode` to some data classes
- [#365]: `PluginClassLoader` does not resolve classpath resources from plugin dependencies

#### Removed

### [3.2.0] - 2019-11-29

#### Fixed
@@ -96,8 +108,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#348]: Found extensions when using decorated annotations
- [#350]: Support any interface as an `ExtensionPoint`

#### Removed

### [3.1.0] - 2019-09-08

#### Fixed
@@ -111,8 +121,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#323]: Add IDEA classpath for Development mode
- [#337]: Implement `PluginClassLoader.getResources`

#### Removed

### [3.0.0] - 2019-06-12

#### Fixed
@@ -167,8 +175,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#270]: Optional plugin dependencies
- [#275]: Add automatic module name to `pf4j.jar`

#### Removed

### [2.5.0] - 2018-12-12

#### Fixed
@@ -183,8 +189,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#242]: Add delete hook method in `Plugin`
- [#256]: Adds ability to configure plugin directory

#### Removed

### [2.4.0] - 2018-08-01

#### Fixed
@@ -193,14 +197,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#229]: Can't find `plugin.properties` file inside `.jar`
- Fix error in `FileUtils.getFileSystem`

#### Changed

#### Added
- [#229]: Add `SingletonExtensionFactory`
- [#229]: Allow a way to query all extension classes for a given plugin

#### Removed

### [2.3.0] - 2018-06-04

#### Fixed
@@ -217,8 +217,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#206]: Support multiple plugin directories
- Add aliases to the runtime modes (`dev` for `development` and `prod` for `deployment`)

#### Removed

### [2.2.0] - 2018-02-11

#### Fixed
@@ -237,8 +235,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Add new `RESOLVED` as plugin state
- Add support for PARENT FIRST loading strategy

#### Removed

### [2.1.0] - 2018-01-10

#### Fixed
@@ -249,10 +245,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
#### Changed
- [#180]: Refactoring to make `PluginDescriptor` more usable

#### Added

#### Removed

### [2.0.0] - 2017-10-17

#### Fixed
@@ -295,8 +287,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#139]: Ability to get `pluginsRoot` from PluginManager
- Add constructors with varargs in PluginException

#### Removed

### [1.2.0] - 2017-03-03

#### Fixed
@@ -309,32 +299,22 @@ This project adheres to [Semantic Versioning](http://semver.org/).
#### Added
- [#128]: Add `JarPluginManager`, `PluginLoader`, `AbstractPluginManager`

#### Removed

### [1.1.1] - 2016-11-17

#### Fixed
- [#116]: Default/System extensions are duplicated

#### Changed

#### Added
- [#111]: Add inheritance support on Extension annotation

#### Removed

### [1.1.0] - 2016-08-22

#### Fixed

#### Changed
- [#107]: PluginDescriptor can't be extended

#### Added
- [#108]: Return a list of all extensions from a plugin and optional for an extension point

#### Removed

### [1.0.0] - 2016-07-07

#### Fixed
@@ -348,10 +328,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Eliminate duplicate log messages from demo
- Improve debugging for "no extensions found"

#### Added

#### Removed

### [0.13.1] - 2016-04-01

#### Fixed
@@ -392,8 +368,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Add `NameFileFilter` and `OrFileFilter`
- [#85]: ExtensionStorage based on Java Service Provider (META-INf/services)

#### Removed

### [0.11.0] - 2015-11-19

#### Fixed
@@ -410,8 +384,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added one more fail test to DefaultExtensionFactory
- Added ManifestPluginDescriptorFinder tests

#### Removed

### [0.10.0] - 2015-08-11

#### Fixed
@@ -432,9 +404,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#41]: Added plugin archive source abstraction
- Added test for DefaultPluginRepository

#### Removed

[unreleased]: https://github.com/decebals/pf4j/compare/release-3.6.0...HEAD
[unreleased]: https://github.com/decebals/pf4j/compare/release-3.9.0...HEAD
[3.9.0]: https://github.com/decebals/pf4j/compare/release-3.8.0...release-3.9.0
[3.8.0]: https://github.com/decebals/pf4j/compare/release-3.7.0...release-3.8.0
[3.7.0]: https://github.com/decebals/pf4j/compare/release-3.6.0...release-3.7.0
[3.6.0]: https://github.com/decebals/pf4j/compare/release-3.5.0...release-3.6.0
[3.5.0]: https://github.com/decebals/pf4j/compare/release-3.4.1...release-3.5.0
[3.4.1]: https://github.com/decebals/pf4j/compare/release-3.4.0...release-3.4.1
@@ -461,6 +434,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
[0.11.0]: https://github.com/decebals/pf4j/compare/release-0.10.0...release-0.11.0
[0.10.0]: https://github.com/decebals/pf4j/compare/release-0.9.0...release-0.10.0

[#512]: https://github.com/pf4j/pf4j/pull/512
[#508]: https://github.com/pf4j/pf4j/issues/508
[#492]: https://github.com/pf4j/pf4j/issues/492
[#490]: https://github.com/pf4j/pf4j/pull/490
[#455]: https://github.com/pf4j/pf4j/pull/455
[#451]: https://github.com/pf4j/pf4j/pull/451

+ 4
- 10
README.md View File

@@ -3,12 +3,12 @@
Plugin Framework for Java (PF4J)
=====================
[![Join the chat at https://gitter.im/decebals/pf4j](https://badges.gitter.im/decebals/pf4j.svg)](https://gitter.im/decebals/pf4j?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Travis CI Build Status](https://travis-ci.org/pf4j/pf4j.png)](https://travis-ci.org/pf4j/pf4j)
[![GitHub Actions Status](https://github.com/pf4j/pf4j/actions/workflows/build.yml/badge.svg)](https://github.com/pf4j/pf4j/actions/workflows/build.yml)
[![Coverage Status](https://coveralls.io/repos/pf4j/pf4j/badge.svg?branch=master&service=github)](https://coveralls.io/github/pf4j/pf4j?branch=master)
[![Maven Central](http://img.shields.io/maven-central/v/org.pf4j/pf4j.svg)](http://search.maven.org/#search|ga|1|pf4j)

A plugin is a way for a third party to extend the functionality of an application. A plugin implements extension points
declared by application or other plugins. Also a plugin can define extension points.
declared by application or other plugins. Also, a plugin can define extension points.

**NOTE:** Starting with version 0.9 you can define an extension directly in the application jar (you're not obligated to put the extension in a plugin - you can see this extension as a default/system extension). See [WhazzupGreeting](https://github.com/pf4j/pf4j/blob/master/demo/app/src/main/java/org/pf4j/demo/WhazzupGreeting.java) for a real example.

@@ -70,17 +70,11 @@ public class WelcomeGreeting implements Greeting {
}
```

Create (it's __optional__) a `Plugin` class if you are interested for plugin's lifecycle events (start, stop, ...):
Create (it's __optional__) a `Plugin` class if you are interested in plugin's lifecycle events (start, stop, ...):

```java
public class WelcomePlugin extends Plugin {

public WelcomePlugin(PluginWrapper wrapper) {
super(wrapper);

// you can use "wrapper" to have access to the plugin context (plugin manager, descriptor, ...)
}

@Override
public void start() {
System.out.println("WelcomePlugin.start()");
@@ -101,7 +95,7 @@ public class WelcomePlugin extends Plugin {

In above code we've been created a plugin (welcome) that comes with one extension for the `Greeting` extension point.

You can distribute you plugin as a jar file (the simple solution). In this case add the plugin's metadata in `MANIFEST.MF` file of jar:
You can distribute your plugin as a jar file (the simple solution). In this case add the plugin's metadata in `MANIFEST.MF` file of jar:

```
Manifest-Version: 1.0

+ 2
- 2
demo/api/pom.xml View File

@@ -4,12 +4,12 @@
<parent>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-api</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo Api</name>


pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java → demo/api/src/main/java/org/pf4j/demo/api/DemoPlugin.java View File

@@ -5,7 +5,7 @@
* 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
* 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,
@@ -13,20 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pf4j.test;
package org.pf4j.demo.api;

import org.pf4j.Plugin;

/**
* A wrong {@link org.pf4j.Plugin}.
* It's wrong because it doesn't contain a constructor with one parameter ({@link org.pf4j.PluginWrapper} as parameter type).
* Base {@link Plugin} for all demo plugins.
*
* @author Mario Franco
* @author Decebal Suiu
*/
public class AnotherFailTestPlugin extends Plugin {
public abstract class DemoPlugin extends Plugin {

public AnotherFailTestPlugin() {
super(null);
protected final PluginContext context;

protected DemoPlugin(PluginContext context) {
super();

this.context = context;
}

}

pf4j/src/test/java/org/pf4j/test/FailTestExtension.java → demo/api/src/main/java/org/pf4j/demo/api/PluginContext.java View File

@@ -5,7 +5,7 @@
* 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
* 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,
@@ -13,25 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pf4j.test;
package org.pf4j.demo.api;

import org.pf4j.Extension;
import org.pf4j.RuntimeMode;

/**
* A wrong {@link org.pf4j.Extension}.
* It's wrong because it doesn't contain a constructor with empty parameters (or only the default constructor).
* An instance of this class is provided to plugins in their constructor.
* It's safe for plugins to keep a reference to the instance for later use.
* This class facilitates communication with application and plugin manager.
*
* @author Mario Franco
* @author Decebal Suiu
*/
@Extension
public class FailTestExtension implements TestExtensionPoint {
public class PluginContext {

public FailTestExtension(String name) {
private final RuntimeMode runtimeMode;

public PluginContext(RuntimeMode runtimeMode) {
this.runtimeMode = runtimeMode;
}

@Override
public String saySomething() {
return "I am a fail test extension";
public RuntimeMode getRuntimeMode() {
return runtimeMode;
}

}

+ 2
- 2
demo/app/pom.xml View File

@@ -4,12 +4,12 @@
<parent>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-app</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo App</name>


+ 23
- 31
demo/app/src/main/java/org/pf4j/demo/Boot.java View File

@@ -16,12 +16,11 @@
package org.pf4j.demo;

import org.apache.commons.lang.StringUtils;
import org.pf4j.DefaultExtensionFinder;
import org.pf4j.DefaultPluginManager;
import org.pf4j.ExtensionFinder;
import org.pf4j.PluginManager;
import org.pf4j.PluginWrapper;
import org.pf4j.demo.api.Greeting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Set;
@@ -33,21 +32,14 @@ import java.util.Set;
*/
public class Boot {

private static final Logger log = LoggerFactory.getLogger(Boot.class);

public static void main(String[] args) {
// print logo
printLogo();

// create the plugin manager
final PluginManager pluginManager = new DefaultPluginManager() {

protected ExtensionFinder createExtensionFinder() {
DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder();
extensionFinder.addServiceProviderExtensionFinder(); // to activate "HowdyGreeting" extension

return extensionFinder;
}

};
PluginManager pluginManager = new DemoPluginManager();

// load the plugins
pluginManager.loadPlugins();
@@ -60,59 +52,59 @@ public class Boot {

// 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()));
log.info("Found {} extensions for extension point '{}'", greetings.size(), Greeting.class.getName());
for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.getGreeting());
log.info(">>> {}", greeting.getGreeting());
}

// print extensions from classpath (non plugin)
System.out.println("Extensions added by classpath:");
log.info("Extensions added by classpath:");
Set<String> extensionClassNames = pluginManager.getExtensionClassNames(null);
for (String extension : extensionClassNames) {
System.out.println(" " + extension);
log.info(" {}", extension);
}

System.out.println("Extension classes by classpath:");
log.info("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());
log.info(" 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));
log.info("Extensions added by plugin '{}}':", pluginId);
extensionClassNames = pluginManager.getExtensionClassNames(pluginId);
for (String extension : extensionClassNames) {
System.out.println(" " + extension);
log.info(" {}", extension);
}
}

// print the 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()));
log.info("Extensions instances added by plugin '{}' for extension point '{}':", pluginId, Greeting.class.getName());
List<Greeting> extensions = pluginManager.getExtensions(Greeting.class, pluginId);
for (Object extension : extensions) {
System.out.println(" " + extension);
log.info(" {}", extension);
}
}

// print extensions instances from classpath (non plugin)
System.out.println("Extensions instances added by classpath:");
List extensions = pluginManager.getExtensions((String) null);
log.info("Extensions instances added by classpath:");
List<?> extensions = pluginManager.getExtensions((String) null);
for (Object extension : extensions) {
System.out.println(" " + extension);
log.info(" {}", 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));
log.info("Extensions instances added by plugin '{}':", pluginId);
extensions = pluginManager.getExtensions(pluginId);
for (Object extension : extensions) {
System.out.println(" " + extension);
log.info(" {}", extension);
}
}

@@ -131,9 +123,9 @@ public class Boot {
}

private static void printLogo() {
System.out.println(StringUtils.repeat("#", 40));
System.out.println(StringUtils.center("PF4J-DEMO", 40));
System.out.println(StringUtils.repeat("#", 40));
log.info(StringUtils.repeat("#", 40));
log.info(StringUtils.center("PF4J-DEMO", 40));
log.info(StringUtils.repeat("#", 40));
}

}

+ 40
- 0
demo/app/src/main/java/org/pf4j/demo/DemoPluginFactory.java View File

@@ -0,0 +1,40 @@
/*
* 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.demo;

import org.pf4j.DefaultPluginFactory;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import org.pf4j.demo.api.PluginContext;

import java.lang.reflect.Constructor;

class DemoPluginFactory extends DefaultPluginFactory {

@Override
protected Plugin createInstance(Class<?> pluginClass, PluginWrapper pluginWrapper) {
PluginContext context = new PluginContext(pluginWrapper.getRuntimeMode());
try {
Constructor<?> constructor = pluginClass.getConstructor(PluginContext.class);
return (Plugin) constructor.newInstance(context);
} catch (Exception e) {
log.error(e.getMessage(), e);
}

return null;
}

}

+ 42
- 0
demo/app/src/main/java/org/pf4j/demo/DemoPluginManager.java View File

@@ -0,0 +1,42 @@
/*
* 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.demo;

import org.pf4j.DefaultExtensionFinder;
import org.pf4j.DefaultPluginFactory;
import org.pf4j.DefaultPluginManager;
import org.pf4j.ExtensionFinder;
import org.pf4j.PluginFactory;

class DemoPluginManager extends DefaultPluginManager {

// Use below code if you want to enable ServiceProviderExtensionFinder
/*
@Override
protected ExtensionFinder createExtensionFinder() {
DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder();
extensionFinder.addServiceProviderExtensionFinder(); // to activate "HowdyGreeting" extension

return extensionFinder;
}
*/

@Override
protected PluginFactory createPluginFactory() {
return new DemoPluginFactory();
}

}

+ 2
- 2
demo/plugins/plugin1/pom.xml View File

@@ -4,12 +4,12 @@
<parent>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin1</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo Plugin #1</name>


+ 10
- 11
demo/plugins/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java View File

@@ -16,34 +16,33 @@
package org.pf4j.demo.welcome;

import org.apache.commons.lang.StringUtils;

import org.pf4j.PluginWrapper;
import org.pf4j.Extension;
import org.pf4j.RuntimeMode;
import org.pf4j.demo.api.DemoPlugin;
import org.pf4j.demo.api.Greeting;
import org.pf4j.Extension;
import org.pf4j.Plugin;
import org.pf4j.demo.api.PluginContext;

/**
* @author Decebal Suiu
*/
public class WelcomePlugin extends Plugin {
public class WelcomePlugin extends DemoPlugin {

public WelcomePlugin(PluginWrapper wrapper) {
super(wrapper);
public WelcomePlugin(PluginContext context) {
super(context);
}

@Override
public void start() {
System.out.println("WelcomePlugin.start()");
log.info("WelcomePlugin.start()");
// for testing the development mode
if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
System.out.println(StringUtils.upperCase("WelcomePlugin"));
if (RuntimeMode.DEVELOPMENT.equals(context.getRuntimeMode())) {
log.info(StringUtils.upperCase("WelcomePlugin"));
}
}

@Override
public void stop() {
System.out.println("WelcomePlugin.stop()");
log.info("WelcomePlugin.stop()");
}

@Extension

+ 2
- 2
demo/plugins/plugin2/pom.xml View File

@@ -4,12 +4,12 @@
<parent>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-plugins</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugin2</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo Plugin #2</name>


+ 7
- 7
demo/plugins/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java View File

@@ -16,29 +16,29 @@
package org.pf4j.demo.hello;

import org.pf4j.Extension;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import org.pf4j.demo.api.DemoPlugin;
import org.pf4j.demo.api.Greeting;
import org.pf4j.demo.api.PluginContext;

/**
* A very simple plugin.
*
* @author Decebal Suiu
*/
public class HelloPlugin extends Plugin {
public class HelloPlugin extends DemoPlugin {

public HelloPlugin(PluginWrapper wrapper) {
super(wrapper);
public HelloPlugin(PluginContext context) {
super(context);
}

@Override
public void start() {
System.out.println("HelloPlugin.start()");
log.info("HelloPlugin.start()");
}

@Override
public void stop() {
System.out.println("HelloPlugin.stop()");
log.info("HelloPlugin.stop()");
}

@Extension(ordinal=1)

+ 2
- 2
demo/plugins/pom.xml View File

@@ -4,12 +4,12 @@
<parent>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j-demo-plugins</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Demo Plugins Parent</name>


+ 2
- 2
demo/pom.xml View File

@@ -4,13 +4,13 @@
<parent>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<groupId>org.pf4j.demo</groupId>
<artifactId>pf4j-demo-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Demo Parent</name>


+ 1
- 1
maven-archetypes/quickstart/pom.xml View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>


+ 3
- 3
pf4j/pom.xml View File

@@ -4,12 +4,12 @@
<parent>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>pf4j</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>PF4J</name>
<description>Plugin Framework for Java</description>
@@ -170,7 +170,7 @@
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
<version>0.18</version>
<version>0.21.0</version>
<scope>test</scope>
</dependency>
</dependencies>

+ 2
- 3
pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java View File

@@ -41,7 +41,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
protected volatile Map<String, ExtensionInfo> extensionInfos; // cache extension infos by class name
protected Boolean checkForExtensionDependencies = null;

public AbstractExtensionFinder(PluginManager pluginManager) {
protected AbstractExtensionFinder(PluginManager pluginManager) {
this.pluginManager = pluginManager;
}

@@ -50,7 +50,6 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
public abstract Map<String, Set<String>> readClasspathStorages();

@Override
@SuppressWarnings("unchecked")
public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
log.debug("Finding extensions of extension point '{}'", type.getName());
Map<String, Set<String>> entries = getEntries();
@@ -151,7 +150,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
checkDifferentClassLoaders(type, extensionClass);
}
}
} catch (ClassNotFoundException e) {
} catch (ClassNotFoundException | NoClassDefFoundError e) {
log.error(e.getMessage(), e);
}
}

+ 6
- 6
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java View File

@@ -113,7 +113,7 @@ public abstract class AbstractPluginManager implements PluginManager {
/**
* The plugins roots are supplied as comma-separated list by {@code System.getProperty("pf4j.pluginsDir", "plugins")}.
*/
public AbstractPluginManager() {
protected AbstractPluginManager() {
initialize();
}

@@ -122,7 +122,7 @@ public abstract class AbstractPluginManager implements PluginManager {
*
* @param pluginsRoots the roots to search for plugins
*/
public AbstractPluginManager(Path... pluginsRoots) {
protected AbstractPluginManager(Path... pluginsRoots) {
this(Arrays.asList(pluginsRoots));
}

@@ -131,7 +131,7 @@ public abstract class AbstractPluginManager implements PluginManager {
*
* @param pluginsRoots the roots to search for plugins
*/
public AbstractPluginManager(List<Path> pluginsRoots) {
protected AbstractPluginManager(List<Path> pluginsRoots) {
this.pluginsRoots.addAll(pluginsRoots);

initialize();
@@ -877,10 +877,10 @@ public abstract class AbstractPluginManager implements PluginManager {

return pluginWrapper;
}
/**
* creates the plugin wrapper. override this if you want to prevent plugins having full access to the plugin manager
*
*
* @return
*/
protected PluginWrapper createPluginWrapper(PluginDescriptor pluginDescriptor, Path pluginPath, ClassLoader pluginClassLoader) {
@@ -933,7 +933,7 @@ public abstract class AbstractPluginManager implements PluginManager {
/**
* Set to true to allow requires expression to be exactly x.y.z.
* The default is false, meaning that using an exact version x.y.z will
* implicitly mean the same as >=x.y.z
* implicitly mean the same as &gt;=x.y.z
*
* @param exactVersionAllowed set to true or false
*/

+ 6
- 6
pf4j/src/main/java/org/pf4j/ClassLoadingStrategy.java View File

@@ -30,32 +30,32 @@ import java.util.List;
public class ClassLoadingStrategy {

/**
* application(parent) -> plugin -> dependencies
* application(parent) -&gt; plugin -&gt; dependencies
*/
public static final ClassLoadingStrategy APD = new ClassLoadingStrategy(Arrays.asList(Source.APPLICATION, Source.PLUGIN, Source.DEPENDENCIES));

/**
* application(parent) -> dependencies -> plugin
* application(parent) -&gt; dependencies -&gt; plugin
*/
public static final ClassLoadingStrategy ADP = new ClassLoadingStrategy(Arrays.asList(Source.APPLICATION, Source.DEPENDENCIES, Source.PLUGIN));

/**
* plugin -> application(parent) -> dependencies
* plugin -&gt; application(parent) -&gt; dependencies
*/
public static final ClassLoadingStrategy PAD = new ClassLoadingStrategy(Arrays.asList(Source.PLUGIN, Source.APPLICATION, Source.DEPENDENCIES));

/**
* dependencies -> application(parent) -> plugin
* dependencies -&gt; application(parent) -&gt; plugin
*/
public static final ClassLoadingStrategy DAP = new ClassLoadingStrategy(Arrays.asList(Source.DEPENDENCIES, Source.APPLICATION, Source.PLUGIN));

/**
* dependencies -> plugin -> application(parent)
* dependencies -&gt; plugin -&gt; application(parent)
*/
public static final ClassLoadingStrategy DPA = new ClassLoadingStrategy(Arrays.asList(Source.DEPENDENCIES, Source.PLUGIN, Source.APPLICATION));

/**
* plugin -> dependencies -> application(parent)
* plugin -&gt; dependencies -&gt; application(parent)
*/
public static final ClassLoadingStrategy PDA = new ClassLoadingStrategy(Arrays.asList(Source.PLUGIN, Source.DEPENDENCIES, Source.APPLICATION));


+ 20
- 6
pf4j/src/main/java/org/pf4j/DefaultPluginFactory.java View File

@@ -23,18 +23,16 @@ import java.lang.reflect.Modifier;

/**
* The default implementation for {@link PluginFactory}.
* It uses {@link Class#newInstance()} method.
* It uses {@link Constructor#newInstance(Object...)} method.
*
* @author Decebal Suiu
*/
public class DefaultPluginFactory implements PluginFactory {

private static final Logger log = LoggerFactory.getLogger(DefaultPluginFactory.class);
protected static final Logger log = LoggerFactory.getLogger(DefaultPluginFactory.class);

/**
* Creates a plugin instance. If an error occurs than that error is logged and the method returns null.
* @param pluginWrapper
* @return
* Creates a plugin instance. If an error occurs than that error is logged and the method returns {@code null}.
*/
@Override
public Plugin create(final PluginWrapper pluginWrapper) {
@@ -58,10 +56,26 @@ public class DefaultPluginFactory implements PluginFactory {
return null;
}

// create the plugin instance
return createInstance(pluginClass, pluginWrapper);
}

protected Plugin createInstance(Class<?> pluginClass, PluginWrapper pluginWrapper) {
try {
Constructor<?> constructor = pluginClass.getConstructor(PluginWrapper.class);
return (Plugin) constructor.newInstance(pluginWrapper);
} catch (NoSuchMethodException e) {
return createUsingNoParametersConstructor(pluginClass);
} catch (Exception e) {
log.error(e.getMessage(), e);
}

return null;
}

protected Plugin createUsingNoParametersConstructor(Class<?> pluginClass) {
try {
Constructor<?> constructor = pluginClass.getConstructor();
return (Plugin) constructor.newInstance();
} catch (Exception e) {
log.error(e.getMessage(), e);
}

+ 12
- 0
pf4j/src/main/java/org/pf4j/Plugin.java View File

@@ -33,14 +33,20 @@ public class Plugin {

/**
* Wrapper of the plugin.
* @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper}.
* See demo for more details.
*/
@Deprecated
protected PluginWrapper wrapper;

/**
* Constructor to be used by plugin manager for plugin instantiation.
* Your plugins have to provide constructor with this exact signature to
* be successfully loaded by manager.
* @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper}.
* See demo for more details.
*/
@Deprecated
public Plugin(final PluginWrapper wrapper) {
if (wrapper == null) {
throw new IllegalArgumentException("Wrapper cannot be null");
@@ -49,9 +55,15 @@ public class Plugin {
this.wrapper = wrapper;
}

public Plugin() {
}

/**
* Retrieves the wrapper of this plug-in.
* @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper}.
* See demo for more details.
*/
@Deprecated
public final PluginWrapper getWrapper() {
return wrapper;
}

+ 308
- 305
pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java View File

@@ -1,305 +1,308 @@
package org.pf4j;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Use this class to wrap the original plugin manager to prevent full access from within plugins.
* Override AbstractPluginManager.createPluginWrapper to use this class
* @author Wolfram Haussig
*
*/
public class SecurePluginManagerWrapper implements PluginManager {
private static final String PLUGIN_PREFIX = "Plugin ";
/**
* the current plugin
*/
private String currentPluginId;
/**
* the original plugin manager
*/
private PluginManager original;
/**
* The registered {@link PluginStateListener}s.
*/
protected List<PluginStateListener> pluginStateListeners = new ArrayList<>();
/**
* wrapper for pluginStateListeners
*/
private PluginStateListenerWrapper listenerWrapper = new PluginStateListenerWrapper();
/**
* constructor
* @param original the original plugin manager
* @param currentPlugin the current pluginId
*/
public SecurePluginManagerWrapper(PluginManager original, String currentPluginId) {
this.original = original;
this.currentPluginId = currentPluginId;
}
@Override
public boolean isDevelopment() {
return original.isDevelopment();
}
@Override
public boolean isNotDevelopment() {
return original.isNotDevelopment();
}
@Override
public List<PluginWrapper> getPlugins() {
return Arrays.asList(getPlugin(currentPluginId));
}
@Override
public List<PluginWrapper> getPlugins(PluginState pluginState) {
return getPlugins().stream().filter(p -> p.getPluginState() == pluginState).collect(Collectors.toList());
}
@Override
public List<PluginWrapper> getResolvedPlugins() {
return getPlugins().stream().filter(p -> p.getPluginState().ordinal() >= PluginState.RESOLVED.ordinal()).collect(Collectors.toList());
}
@Override
public List<PluginWrapper> getUnresolvedPlugins() {
return Collections.emptyList();
}
@Override
public List<PluginWrapper> getStartedPlugins() {
return getPlugins(PluginState.STARTED);
}
@Override
public PluginWrapper getPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPlugin for foreign pluginId!");
}
}
@Override
public void loadPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugins!");
}
@Override
public String loadPlugin(Path pluginPath) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugin!");
}
@Override
public void startPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugins!");
}
@Override
public PluginState startPlugin(String pluginId) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugin!");
}
@Override
public void stopPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugins!");
}
@Override
public PluginState stopPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.stopPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugin for foreign pluginId!");
}
}
@Override
public void unloadPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugins!");
}
@Override
public boolean unloadPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.unloadPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugin for foreign pluginId!");
}
}
@Override
public boolean disablePlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.disablePlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute disablePlugin for foreign pluginId!");
}
}
@Override
public boolean enablePlugin(String pluginId) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute enablePlugin!");
}
@Override
public boolean deletePlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.deletePlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute deletePlugin for foreign pluginId!");
}
}
@Override
public ClassLoader getPluginClassLoader(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getPluginClassLoader(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginClassLoader for foreign pluginId!");
}
}
@Override
public List<Class<?>> getExtensionClasses(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClasses(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");
}
}
@Override
public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type) {
return getExtensionClasses(type, currentPluginId);
}
@Override
public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type, String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClasses(type, pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");
}
}
@Override
public <T> List<T> getExtensions(Class<T> type) {
return getExtensions(type, currentPluginId);
}
@Override
public <T> List<T> getExtensions(Class<T> type, String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensions(type, pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");
}
}
@Override
public List<?> getExtensions(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensions(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");
}
}
@Override
public Set<String> getExtensionClassNames(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClassNames(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClassNames for foreign pluginId!");
}
}
@Override
public ExtensionFactory getExtensionFactory() {
return original.getExtensionFactory();
}
@Override
public RuntimeMode getRuntimeMode() {
return original.getRuntimeMode();
}
@Override
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
PluginWrapper plugin = getPlugin(currentPluginId);
if (plugin.getPluginClassLoader() == classLoader) {
return plugin;
}
return null;
}
@Override
public void addPluginStateListener(PluginStateListener listener) {
if (pluginStateListeners.isEmpty()) {
this.original.addPluginStateListener(listenerWrapper);
}
pluginStateListeners.add(listener);
}
@Override
public void removePluginStateListener(PluginStateListener listener) {
pluginStateListeners.remove(listener);
if (pluginStateListeners.isEmpty()) {
this.original.removePluginStateListener(listenerWrapper);
}
}
@Override
public void setSystemVersion(String version) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute setSystemVersion!");
}
@Override
public String getSystemVersion() {
return original.getSystemVersion();
}
@Override
public Path getPluginsRoot() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoot!");
}
@Override
public List<Path> getPluginsRoots() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoots!");
}
@Override
public VersionManager getVersionManager() {
return original.getVersionManager();
}
/**
* Wrapper for PluginStateListener events. will only propagate events if they match the current pluginId
* @author Wolfram Haussig
*
*/
private class PluginStateListenerWrapper implements PluginStateListener {
@Override
public void pluginStateChanged(PluginStateEvent event) {
if (event.getPlugin().getPluginId().equals(currentPluginId)) {
for (PluginStateListener listener : pluginStateListeners) {
listener.pluginStateChanged(event);
}
}
}
}
}
package org.pf4j;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Use this class to wrap the original plugin manager to prevent full access from within plugins.
* Override AbstractPluginManager.createPluginWrapper to use this class.
* @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper} to communicate with {@link Plugin}.
* See demo for more details.
*
* @author Wolfram Haussig
*/
@Deprecated()
public class SecurePluginManagerWrapper implements PluginManager {

private static final String PLUGIN_PREFIX = "Plugin ";
/**
* the current plugin
*/
private String currentPluginId;
/**
* the original plugin manager
*/
private PluginManager original;

/**
* The registered {@link PluginStateListener}s.
*/
protected List<PluginStateListener> pluginStateListeners = new ArrayList<>();
/**
* wrapper for pluginStateListeners
*/
private PluginStateListenerWrapper listenerWrapper = new PluginStateListenerWrapper();

/**
* constructor
* @param original the original plugin manager
* @param currentPluginId the current pluginId
*/
public SecurePluginManagerWrapper(PluginManager original, String currentPluginId) {
this.original = original;
this.currentPluginId = currentPluginId;
}

@Override
public boolean isDevelopment() {
return original.isDevelopment();
}

@Override
public boolean isNotDevelopment() {
return original.isNotDevelopment();
}

@Override
public List<PluginWrapper> getPlugins() {
return Arrays.asList(getPlugin(currentPluginId));
}

@Override
public List<PluginWrapper> getPlugins(PluginState pluginState) {
return getPlugins().stream().filter(p -> p.getPluginState() == pluginState).collect(Collectors.toList());
}

@Override
public List<PluginWrapper> getResolvedPlugins() {
return getPlugins().stream().filter(p -> p.getPluginState().ordinal() >= PluginState.RESOLVED.ordinal()).collect(Collectors.toList());
}

@Override
public List<PluginWrapper> getUnresolvedPlugins() {
return Collections.emptyList();
}

@Override
public List<PluginWrapper> getStartedPlugins() {
return getPlugins(PluginState.STARTED);
}

@Override
public PluginWrapper getPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPlugin for foreign pluginId!");
}
}

@Override
public void loadPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugins!");
}

@Override
public String loadPlugin(Path pluginPath) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugin!");
}

@Override
public void startPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugins!");
}

@Override
public PluginState startPlugin(String pluginId) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugin!");
}

@Override
public void stopPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugins!");
}

@Override
public PluginState stopPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.stopPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugin for foreign pluginId!");
}
}

@Override
public void unloadPlugins() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugins!");
}

@Override
public boolean unloadPlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.unloadPlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugin for foreign pluginId!");
}
}

@Override
public boolean disablePlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.disablePlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute disablePlugin for foreign pluginId!");
}
}

@Override
public boolean enablePlugin(String pluginId) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute enablePlugin!");
}

@Override
public boolean deletePlugin(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.deletePlugin(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute deletePlugin for foreign pluginId!");
}
}

@Override
public ClassLoader getPluginClassLoader(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getPluginClassLoader(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginClassLoader for foreign pluginId!");
}
}

@Override
public List<Class<?>> getExtensionClasses(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClasses(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");
}
}

@Override
public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type) {
return getExtensionClasses(type, currentPluginId);
}

@Override
public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type, String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClasses(type, pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!");
}
}

@Override
public <T> List<T> getExtensions(Class<T> type) {
return getExtensions(type, currentPluginId);
}

@Override
public <T> List<T> getExtensions(Class<T> type, String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensions(type, pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");
}
}

@Override
public List<?> getExtensions(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensions(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!");
}
}

@Override
public Set<String> getExtensionClassNames(String pluginId) {
if (currentPluginId.equals(pluginId)) {
return original.getExtensionClassNames(pluginId);
} else {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClassNames for foreign pluginId!");
}
}

@Override
public ExtensionFactory getExtensionFactory() {
return original.getExtensionFactory();
}

@Override
public RuntimeMode getRuntimeMode() {
return original.getRuntimeMode();
}

@Override
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
PluginWrapper plugin = getPlugin(currentPluginId);
if (plugin.getPluginClassLoader() == classLoader) {
return plugin;
}
return null;
}

@Override
public void addPluginStateListener(PluginStateListener listener) {
if (pluginStateListeners.isEmpty()) {
this.original.addPluginStateListener(listenerWrapper);
}
pluginStateListeners.add(listener);
}

@Override
public void removePluginStateListener(PluginStateListener listener) {
pluginStateListeners.remove(listener);
if (pluginStateListeners.isEmpty()) {
this.original.removePluginStateListener(listenerWrapper);
}
}

@Override
public void setSystemVersion(String version) {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute setSystemVersion!");
}

@Override
public String getSystemVersion() {
return original.getSystemVersion();
}

@Override
public Path getPluginsRoot() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoot!");
}

@Override
public List<Path> getPluginsRoots() {
throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoots!");
}

@Override
public VersionManager getVersionManager() {
return original.getVersionManager();
}

/**
* Wrapper for PluginStateListener events. will only propagate events if they match the current pluginId
* @author Wolfram Haussig
*
*/
private class PluginStateListenerWrapper implements PluginStateListener {

@Override
public void pluginStateChanged(PluginStateEvent event) {
if (event.getPlugin().getPluginId().equals(currentPluginId)) {
for (PluginStateListener listener : pluginStateListeners) {
listener.pluginStateChanged(event);
}
}
}

}
}

+ 3
- 1
pf4j/src/main/java/org/pf4j/processor/ExtensionAnnotationProcessor.java View File

@@ -131,7 +131,9 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor {
}

// write extensions
storage.write(extensions);
if (extensions.size() > 0) {
storage.write(extensions);
}

return false;
}

+ 2
- 30
pf4j/src/main/java/org/pf4j/util/ClassUtils.java View File

@@ -28,6 +28,8 @@ import java.util.Map;
*/
public class ClassUtils {

private ClassUtils() {}

public static List<String> getAllInterfacesNames(Class<?> aClass) {
return toString(getAllInterfaces(aClass));
}
@@ -56,26 +58,6 @@ public class ClassUtils {
return list;
}

/*
public static List<String> getAllAbstractClassesNames(Class<?> aClass) {
return toString(getAllInterfaces(aClass));
}

public static List getAllAbstractClasses(Class aClass) {
List<Class<?>> list = new ArrayList<>();

Class<?> superclass = aClass.getSuperclass();
while (superclass != null) {
if (Modifier.isAbstract(superclass.getModifiers())) {
list.add(superclass);
}
superclass = superclass.getSuperclass();
}

return list;
}
*/

/**
* Get a certain annotation of a {@link TypeElement}.
* See <a href="https://stackoverflow.com/a/10167558">stackoverflow.com</a> for more information.
@@ -96,13 +78,6 @@ public class ClassUtils {
return null;
}

/*
public static Element getAnnotationMirrorElement(TypeElement typeElement, Class<?> annotationClass) {
AnnotationMirror annotationMirror = getAnnotationMirror(typeElement, annotationClass);
return annotationMirror != null ? annotationMirror.getAnnotationType().asElement() : null;
}
*/

/**
* Get a certain parameter of an {@link AnnotationMirror}.
* See <a href="https://stackoverflow.com/a/10167558">stackoverflow.com</a> for more information.
@@ -139,9 +114,6 @@ public class ClassUtils {

/**
* Uses {@link Class#getSimpleName()} to convert from {@link Class} to {@link String}.
*
* @param classes
* @return
*/
private static List<String> toString(List<Class<?>> classes) {
List<String> list = new ArrayList<>();

+ 2
- 0
pf4j/src/main/java/org/pf4j/util/StringUtils.java View File

@@ -20,6 +20,8 @@ package org.pf4j.util;
*/
public class StringUtils {

private StringUtils() {}

public static boolean isNullOrEmpty(String str) {
return (str == null) || str.isEmpty();
}

+ 41
- 71
pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java View File

@@ -15,34 +15,26 @@
*/
package org.pf4j;

import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.testing.compile.Compilation;
import java.util.Comparator;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pf4j.test.FailTestPlugin;
import org.pf4j.test.JavaFileObjectClassLoader;
import org.pf4j.test.JavaFileObjectUtils;
import org.pf4j.test.JavaSources;
import org.pf4j.test.TestExtension;
import org.pf4j.test.TestExtensionPoint;

import javax.tools.JavaFileObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@@ -64,9 +56,9 @@ public class AbstractExtensionFinderTest {
when(pluginStopped.getPluginState()).thenReturn(PluginState.STOPPED);

pluginManager = mock(PluginManager.class);
when(pluginManager.getPlugin(eq("plugin1"))).thenReturn(pluginStarted);
when(pluginManager.getPlugin(eq("plugin2"))).thenReturn(pluginStopped);
when(pluginManager.getPluginClassLoader(eq("plugin1"))).thenReturn(getClass().getClassLoader());
when(pluginManager.getPlugin("plugin1")).thenReturn(pluginStarted);
when(pluginManager.getPlugin("plugin2")).thenReturn(pluginStopped);
when(pluginManager.getPluginClassLoader("plugin1")).thenReturn(getClass().getClassLoader());
when(pluginManager.getExtensionFactory()).thenReturn(new DefaultExtensionFactory());
}

@@ -93,7 +85,7 @@ public class AbstractExtensionFinderTest {
}

};
List<ExtensionWrapper<FailTestPlugin>> list = instance.find(FailTestPlugin.class);
List<ExtensionWrapper<TestExtension>> list = instance.find(TestExtension.class);
assertEquals(0, list.size());
}

@@ -115,7 +107,6 @@ public class AbstractExtensionFinderTest {

Set<String> bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
bucket.add("org.pf4j.test.FailTestExtension");
entries.put(null, bucket);

return entries;
@@ -124,7 +115,7 @@ public class AbstractExtensionFinderTest {
};

List<ExtensionWrapper<TestExtensionPoint>> list = instance.find(TestExtensionPoint.class);
assertEquals(2, list.size());
assertEquals(1, list.size());
}

/**
@@ -140,7 +131,6 @@ public class AbstractExtensionFinderTest {

Set<String> bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
bucket.add("org.pf4j.test.FailTestExtension");
entries.put("plugin1", bucket);
bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
@@ -157,12 +147,13 @@ public class AbstractExtensionFinderTest {
};

List<ExtensionWrapper<TestExtensionPoint>> list = instance.find(TestExtensionPoint.class);
assertEquals(2, list.size());
assertEquals(1, list.size());

list = instance.find(TestExtensionPoint.class, "plugin1");
assertEquals(2, list.size());
assertEquals(1, list.size());

list = instance.find(TestExtensionPoint.class, "plugin2");
// "0" because the status of "plugin2" is STOPPED => no extensions
assertEquals(0, list.size());
}

@@ -210,6 +201,16 @@ public class AbstractExtensionFinderTest {
*/
@Test
public void testFindExtensionWrappersFromPluginId() {
// complicate the test to show hot to deal with dynamic Java classes (generated at runtime from sources)
PluginWrapper plugin3 = mock(PluginWrapper.class);
JavaFileObject object = JavaSources.compile(DefaultExtensionFactoryTest.FailTestExtension);
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(object);
when(plugin3.getPluginClassLoader()).thenReturn(classLoader);
when(plugin3.getPluginState()).thenReturn(PluginState.STARTED);
when(pluginManager.getPluginClassLoader("plugin3")).thenReturn(classLoader);
when(pluginManager.getPlugin("plugin3")).thenReturn(plugin3);

ExtensionFinder instance = new AbstractExtensionFinder(pluginManager) {

@Override
@@ -218,11 +219,13 @@ public class AbstractExtensionFinderTest {

Set<String> bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
bucket.add("org.pf4j.test.FailTestExtension");
entries.put("plugin1", bucket);
bucket = new HashSet<>();
bucket.add("org.pf4j.test.TestExtension");
entries.put("plugin2", bucket);
bucket = new HashSet<>();
bucket.add(JavaFileObjectUtils.getClassName(object));
entries.put("plugin3", bucket);

return entries;
}
@@ -235,73 +238,40 @@ public class AbstractExtensionFinderTest {
};

List<ExtensionWrapper> plugin1Result = instance.find("plugin1");
assertEquals(2, plugin1Result.size());
assertEquals(1, plugin1Result.size());

List<ExtensionWrapper> plugin2Result = instance.find("plugin2");
assertEquals(0, plugin2Result.size());

List<ExtensionWrapper> plugin3Result = instance.find(UUID.randomUUID().toString());
assertEquals(0, plugin3Result.size());
List<ExtensionWrapper> plugin3Result = instance.find("plugin3");
assertEquals(1, plugin3Result.size());

List<ExtensionWrapper> plugin4Result = instance.find(UUID.randomUUID().toString());
assertEquals(0, plugin4Result.size());
}

@Test
public void findExtensionAnnotation() throws Exception {
Compilation compilation = javac().compile(ExtensionAnnotationProcessorTest.Greeting,
ExtensionAnnotationProcessorTest.WhazzupGreeting);
assertThat(compilation).succeededWithoutWarnings();
ImmutableList<JavaFileObject> generatedFiles = compilation.generatedFiles();
public void findExtensionAnnotation() {
List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.Greeting, JavaSources.WhazzupGreeting);
assertEquals(2, generatedFiles.size());

JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
Map<String, Class<?>> loadedClasses = classLoader.loadClasses(new ArrayList<>(generatedFiles));
Map<String, Class<?>> loadedClasses = new JavaFileObjectClassLoader().load(generatedFiles);
Class<?> clazz = loadedClasses.get("test.WhazzupGreeting");
Extension extension = AbstractExtensionFinder.findExtensionAnnotation(clazz);
assertNotNull(extension);
Assertions.assertNotNull(extension);
}

@Test
public void findExtensionAnnotationThatMissing() throws Exception {
Compilation compilation = javac().compile(ExtensionAnnotationProcessorTest.Greeting,
public void findExtensionAnnotationThatMissing() {
List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.Greeting,
ExtensionAnnotationProcessorTest.SpinnakerExtension_NoExtension,
ExtensionAnnotationProcessorTest.WhazzupGreeting_SpinnakerExtension);
assertThat(compilation).succeededWithoutWarnings();
ImmutableList<JavaFileObject> generatedFiles = compilation.generatedFiles();
assertEquals(3, generatedFiles.size());

JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
Map<String, Class<?>> loadedClasses = classLoader.loadClasses(new ArrayList<>(generatedFiles));
Map<String, Class<?>> loadedClasses = new JavaFileObjectClassLoader().load(generatedFiles);
Class<?> clazz = loadedClasses.get("test.WhazzupGreeting");
Extension extension = AbstractExtensionFinder.findExtensionAnnotation(clazz);
assertNull(extension);
}

static class JavaFileObjectClassLoader extends ClassLoader {

public Map<String, Class<?>> loadClasses(List<JavaFileObject> classes) throws IOException {
// Sort generated ".class" by lastModified field
classes.sort(Comparator.comparingLong(JavaFileObject::getLastModified));

// Load classes
Map<String, Class<?>> loadedClasses = new HashMap<>(classes.size());
for (JavaFileObject clazz : classes) {
String className = getClassName(clazz);
byte[] data = ByteStreams.toByteArray(clazz.openInputStream());
Class<?> loadedClass = defineClass(className, data,0, data.length);
loadedClasses.put(className, loadedClass);
}

return loadedClasses;
}

private static String getClassName(JavaFileObject object) {
String name = object.getName();
// Remove "/CLASS_OUT/" from head and ".class" from tail
name = name.substring(14, name.length() - 6);
name = name.replace('/', '.');

return name;
}

Assertions.assertNull(extension);
}

}

+ 22
- 2
pf4j/src/test/java/org/pf4j/DefaultExtensionFactoryTest.java View File

@@ -15,12 +15,16 @@
*/
package org.pf4j;

import com.google.testing.compile.JavaFileObjects;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pf4j.test.FailTestExtension;
import org.pf4j.test.JavaFileObjectClassLoader;
import org.pf4j.test.JavaSources;
import org.pf4j.test.TestExtension;

import javax.tools.JavaFileObject;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

@@ -29,6 +33,19 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
*/
public class DefaultExtensionFactoryTest {

public static final JavaFileObject FailTestExtension = JavaFileObjects.forSourceLines("FailTestExtension",
"package test;",
"import org.pf4j.test.TestExtensionPoint;",
"import org.pf4j.Extension;",
"",
"@Extension",
"public class FailTestExtension implements TestExtensionPoint {",
" public FailTestExtension(String name) {}",
"",
" @Override",
" public String saySomething() { return \"I am a fail test extension\";}",
"}");

private ExtensionFactory extensionFactory;

@BeforeEach
@@ -54,7 +71,10 @@ public class DefaultExtensionFactoryTest {
*/
@Test
public void testCreateFailConstructor() {
assertThrows(PluginRuntimeException.class, () -> extensionFactory.create(FailTestExtension.class));
JavaFileObject object = JavaSources.compile(FailTestExtension);
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
Class<?> extensionClass = (Class<?>) classLoader.load(object).values().toArray()[0];
assertThrows(PluginRuntimeException.class, () -> extensionFactory.create(extensionClass));
}

}

+ 62
- 6
pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java View File

@@ -15,13 +15,18 @@
*/
package org.pf4j;

import com.google.testing.compile.JavaFileObjects;
import org.junit.jupiter.api.Test;
import org.pf4j.test.AnotherFailTestPlugin;
import org.pf4j.test.FailTestPlugin;
import org.pf4j.test.JavaFileObjectClassLoader;
import org.pf4j.test.JavaFileObjectUtils;
import org.pf4j.test.JavaSources;
import org.pf4j.test.TestPlugin;

import javax.tools.JavaFileObject;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
@@ -32,6 +37,29 @@ import static org.mockito.Mockito.when;
*/
public class DefaultPluginFactoryTest {

public static final JavaFileObject FailTestPlugin = JavaFileObjects.forSourceLines("FailTestPlugin",
"package test;",
"import org.pf4j.Plugin;",
"",
"public class FailTestPlugin {",
"}");

public static final JavaFileObject AnotherFailTestPlugin = JavaFileObjects.forSourceLines("AnotherFailTestPlugin",
"package test;",
"import org.pf4j.Plugin;",
"",
"public class AnotherFailTestPlugin extends Plugin {",
" public AnotherFailTestPlugin() { super(null); }",
"}");

public static final JavaFileObject AnotherTestPlugin = JavaFileObjects.forSourceLines("AnotherTestPlugin",
"package test;",
"import org.pf4j.Plugin;",
"",
"public class AnotherTestPlugin extends Plugin {",
" public AnotherTestPlugin() { super(); }",
"}");

@Test
public void testCreate() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
@@ -48,14 +76,38 @@ public class DefaultPluginFactoryTest {
assertThat(result, instanceOf(TestPlugin.class));
}

@Test
public void pluginConstructorNoParameters() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
JavaFileObject object = JavaSources.compile(AnotherTestPlugin);
String pluginClassName = JavaFileObjectUtils.getClassName(object);
when(pluginDescriptor.getPluginClass()).thenReturn(pluginClassName);

PluginWrapper pluginWrapper = mock(PluginWrapper.class);
when(pluginWrapper.getDescriptor()).thenReturn(pluginDescriptor);
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(AnotherTestPlugin);
when(pluginWrapper.getPluginClassLoader()).thenReturn(classLoader);

PluginFactory pluginFactory = new DefaultPluginFactory();

Plugin result = pluginFactory.create(pluginWrapper);
assertNotNull(result);
assertEquals(pluginClassName, result.getClass().getName());
}

@Test
public void testCreateFail() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
when(pluginDescriptor.getPluginClass()).thenReturn(FailTestPlugin.class.getName());
JavaFileObject object = JavaSources.compile(FailTestPlugin);
String pluginClassName = JavaFileObjectUtils.getClassName(object);
when(pluginDescriptor.getPluginClass()).thenReturn(pluginClassName);

PluginWrapper pluginWrapper = mock(PluginWrapper.class);
when(pluginWrapper.getDescriptor()).thenReturn(pluginDescriptor);
when(pluginWrapper.getPluginClassLoader()).thenReturn(getClass().getClassLoader());
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(FailTestPlugin);
when(pluginWrapper.getPluginClassLoader()).thenReturn(classLoader);

PluginFactory pluginFactory = new DefaultPluginFactory();

@@ -81,11 +133,15 @@ public class DefaultPluginFactoryTest {
@Test
public void testCreateFailConstructor() {
PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class);
when(pluginDescriptor.getPluginClass()).thenReturn(AnotherFailTestPlugin.class.getName());
JavaFileObject object = JavaSources.compile(AnotherFailTestPlugin);
String pluginClassName = JavaFileObjectUtils.getClassName(object);
when(pluginDescriptor.getPluginClass()).thenReturn(pluginClassName);

PluginWrapper pluginWrapper = mock(PluginWrapper.class);
when(pluginWrapper.getDescriptor()).thenReturn(pluginDescriptor);
when(pluginWrapper.getPluginClassLoader()).thenReturn(getClass().getClassLoader());
JavaFileObjectClassLoader classLoader = new JavaFileObjectClassLoader();
classLoader.load(AnotherFailTestPlugin);
when(pluginWrapper.getPluginClassLoader()).thenReturn(classLoader);

PluginFactory pluginFactory = new DefaultPluginFactory();


+ 39
- 54
pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java View File

@@ -16,12 +16,16 @@
package org.pf4j;

import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;
import com.google.testing.compile.JavaFileObjects;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pf4j.processor.ExtensionAnnotationProcessor;
import org.pf4j.processor.LegacyExtensionStorage;
import org.pf4j.test.JavaSources;

import javax.tools.JavaFileObject;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -38,30 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
*/
public class ExtensionAnnotationProcessorTest {

public static final JavaFileObject Greeting = JavaFileObjects.forSourceLines(
"Greeting",
"package test;",
"import org.pf4j.ExtensionPoint;",
"",
"public interface Greeting extends ExtensionPoint {",
" String getGreeting();",
"}");

public static final JavaFileObject WhazzupGreeting = JavaFileObjects.forSourceLines(
"WhazzupGreeting",
"package test;",
"import org.pf4j.Extension;",
"",
"@Extension",
"public class WhazzupGreeting implements Greeting {",
" @Override",
" public String getGreeting() {",
" return \"Whazzup\";",
" }",
"}");

public static final JavaFileObject WhazzupGreeting_NoExtensionPoint = JavaFileObjects.forSourceLines(
"WhazzupGreeting",
public static final JavaFileObject WhazzupGreeting_NoExtensionPoint = JavaFileObjects.forSourceLines("WhazzupGreeting",
"package test;",
"import org.pf4j.Extension;",
"",
@@ -73,8 +54,7 @@ public class ExtensionAnnotationProcessorTest {
" }",
"}");

public static final JavaFileObject SpinnakerExtension = JavaFileObjects.forSourceLines(
"SpinnakerExtension",
public static final JavaFileObject SpinnakerExtension = JavaFileObjects.forSourceLines("SpinnakerExtension",
"package test;",
"",
"import org.pf4j.Extension;",
@@ -91,8 +71,7 @@ public class ExtensionAnnotationProcessorTest {
"public @interface SpinnakerExtension {",
"}");

public static final JavaFileObject WhazzupGreeting_SpinnakerExtension = JavaFileObjects.forSourceLines(
"WhazzupGreeting",
public static final JavaFileObject WhazzupGreeting_SpinnakerExtension = JavaFileObjects.forSourceLines("WhazzupGreeting",
"package test;",
"",
"@SpinnakerExtension",
@@ -104,10 +83,9 @@ public class ExtensionAnnotationProcessorTest {
"}");

/**
* The same like {@link #SpinnakerExtension} but without {@code Extension} annotation.
* The same as {@link #SpinnakerExtension} but without {@code Extension} annotation.
*/
public static final JavaFileObject SpinnakerExtension_NoExtension = JavaFileObjects.forSourceLines(
"SpinnakerExtension",
public static final JavaFileObject SpinnakerExtension_NoExtension = JavaFileObjects.forSourceLines("SpinnakerExtension",
"package test;",
"",
"import org.pf4j.Extension;",
@@ -124,52 +102,53 @@ public class ExtensionAnnotationProcessorTest {
"public @interface SpinnakerExtension {",
"}");

private ExtensionAnnotationProcessor annotationProcessor;

@BeforeEach
public void setUp() throws IOException {
annotationProcessor = new ExtensionAnnotationProcessor();
}

@Test
public void getSupportedAnnotationTypes() {
ExtensionAnnotationProcessor instance = new ExtensionAnnotationProcessor();
Set<String> result = instance.getSupportedAnnotationTypes();
Set<String> result = annotationProcessor.getSupportedAnnotationTypes();
assertEquals(1, result.size());
assertEquals("*", result.iterator().next());
}

@Test
public void getSupportedOptions() {
ExtensionAnnotationProcessor instance = new ExtensionAnnotationProcessor();
Set<String> result = instance.getSupportedOptions();
Set<String> result = annotationProcessor.getSupportedOptions();
assertEquals(2, result.size());
}

@Test
public void options() {
ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
Compilation compilation = javac().withProcessors(processor).withOptions("-Ab=2", "-Ac=3")
.compile(Greeting, WhazzupGreeting);
assertEquals(compilation.status(), Compilation.Status.SUCCESS);
Compilation compilation = compiler().withOptions("-Ab=2", "-Ac=3")
.compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
assertEquals(Compilation.Status.SUCCESS, compilation.status());
Map<String, String> options = new HashMap<>();
options.put("b", "2");
options.put("c", "3");
assertEquals(options, processor.getProcessingEnvironment().getOptions());
assertEquals(options, annotationProcessor.getProcessingEnvironment().getOptions());
}

@Test
public void storage() {
ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting);
assertEquals(compilation.status(), Compilation.Status.SUCCESS);
assertEquals(processor.getStorage().getClass(), LegacyExtensionStorage.class);
Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
assertEquals(Compilation.Status.SUCCESS, compilation.status());
assertEquals(annotationProcessor.getStorage().getClass(), LegacyExtensionStorage.class);
}

@Test
public void compileWithoutError() {
ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting);
Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
assertThat(compilation).succeededWithoutWarnings();
}

@Test
public void compileWithError() {
ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting_NoExtensionPoint);
Compilation compilation = compile(JavaSources.Greeting, WhazzupGreeting_NoExtensionPoint);
assertThat(compilation).failed();
assertThat(compilation).hadErrorContaining("it doesn't implement ExtensionPoint")
.inFile(WhazzupGreeting_NoExtensionPoint)
@@ -179,22 +158,28 @@ public class ExtensionAnnotationProcessorTest {

@Test
public void getExtensions() {
ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
Compilation compilation = javac().withProcessors(processor).compile(Greeting, WhazzupGreeting);
Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
assertThat(compilation).succeededWithoutWarnings();
Map<String, Set<String>> extensions = new HashMap<>();
extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting")));
assertEquals(extensions, processor.getExtensions());
assertEquals(extensions, annotationProcessor.getExtensions());
}

@Test
public void compileNestedExtensionAnnotation() {
ExtensionAnnotationProcessor processor = new ExtensionAnnotationProcessor();
Compilation compilation = javac().withProcessors(processor).compile(Greeting, SpinnakerExtension, WhazzupGreeting_SpinnakerExtension);
Compilation compilation = compile(JavaSources.Greeting, SpinnakerExtension, WhazzupGreeting_SpinnakerExtension);
assertThat(compilation).succeededWithoutWarnings();
Map<String, Set<String>> extensions = new HashMap<>();
extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting")));
assertEquals(extensions, processor.getExtensions());
assertEquals(extensions, annotationProcessor.getExtensions());
}

private Compiler compiler() {
return javac().withProcessors(annotationProcessor);
}

private Compilation compile(JavaFileObject... sources) {
return compiler().compile(sources);
}

}

+ 1
- 2
pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java View File

@@ -18,7 +18,6 @@ package org.pf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pf4j.test.FailTestExtension;
import org.pf4j.test.TestExtension;

import java.io.File;
@@ -57,7 +56,7 @@ public class SingletonExtensionFactoryTest {

@Test
public void createNewEachTime() {
ExtensionFactory extensionFactory = new SingletonExtensionFactory(pluginManager, FailTestExtension.class.getName());
ExtensionFactory extensionFactory = new SingletonExtensionFactory(pluginManager, "FailTestExtension.class");
Object extensionOne = extensionFactory.create(TestExtension.class);
Object extensionTwo = extensionFactory.create(TestExtension.class);
assertNotSame(extensionOne, extensionTwo);

+ 5
- 15
pf4j/src/test/java/org/pf4j/test/DefaultClassDataProvider.java View File

@@ -15,10 +15,10 @@
*/
package org.pf4j.test;

import java.io.ByteArrayOutputStream;
import com.google.common.io.ByteStreams;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
* Get class data from the class path.
@@ -35,20 +35,10 @@ public class DefaultClassDataProvider implements ClassDataProvider {
throw new RuntimeException("Cannot find class data");
}

try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
copyStream(classDataStream, outputStream);
return outputStream.toByteArray();
try {
return ByteStreams.toByteArray(classDataStream);
} 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);
throw new IllegalStateException(e);
}
}


+ 0
- 26
pf4j/src/test/java/org/pf4j/test/FailTestPlugin.java View File

@@ -1,26 +0,0 @@
/*
* 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.test;

/**
* A wrong {@link org.pf4j.Plugin}.
* It's wrong because it doesn't extends {@link org.pf4j.Plugin}.
*
* @author Mario Franco
*/
public class FailTestPlugin {

}

+ 73
- 0
pf4j/src/test/java/org/pf4j/test/JavaFileObjectClassLoader.java View File

@@ -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.test;

import javax.tools.JavaFileObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
* {@link ClassLoader} that loads {@link JavaFileObject.Kind#CLASS}s.
* If {@code JavaFileObject} type is {@link JavaFileObject.Kind#SOURCE} them the source is compiled.
*
* @author Decebal Suiu
*/
public class JavaFileObjectClassLoader extends ClassLoader {

public Map<String, Class<?>> load(JavaFileObject... objects) {
return load(Arrays.asList(objects));
}

public Map<String, Class<?>> load(List<JavaFileObject> objects) {
Objects.requireNonNull(objects);

List<JavaFileObject> mutableObjects = new ArrayList<>(objects);

// Sort generated ".class" by lastModified field
mutableObjects.sort(Comparator.comparingLong(JavaFileObject::getLastModified));

// Compile Java sources (if exists)
for (int i = 0; i < mutableObjects.size(); i++) {
JavaFileObject object = mutableObjects.get(i);
if (object.getKind() == JavaFileObject.Kind.CLASS) {
continue;
}

if (object.getKind() == JavaFileObject.Kind.SOURCE) {
mutableObjects.set(i, JavaSources.compile(object));
} else {
throw new IllegalStateException("Type " + object.getKind() + " is not supported");
}
}

// Load objects
Map<String, Class<?>> loadedClasses = new HashMap<>();
for (JavaFileObject object : mutableObjects) {
String className = JavaFileObjectUtils.getClassName(object);
byte[] data = JavaFileObjectUtils.getAllBytes(object);
Class<?> loadedClass = defineClass(className, data, 0, data.length);
loadedClasses.put(className, loadedClass);
}

return loadedClasses;
}

}

+ 62
- 0
pf4j/src/test/java/org/pf4j/test/JavaFileObjectDataProvider.java View File

@@ -0,0 +1,62 @@
/*
* 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.test;

import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Get class data from {@link JavaFileObject}.
* If {@code JavaFileObject} type is {@link JavaFileObject.Kind#SOURCE} them the source is compiled.
*
* @author Decebal Suiu
*/
public class JavaFileObjectDataProvider implements ClassDataProvider {

private final Map<String, JavaFileObject> classes;

public JavaFileObjectDataProvider(Map<String, JavaFileObject> classes) {
this.classes = classes;
}

public static JavaFileObjectDataProvider of(List<JavaFileObject> objects) {
List<JavaFileObject> tmp = new ArrayList<>(objects.size());
for (JavaFileObject object : objects) {
if (object.getKind() == JavaFileObject.Kind.CLASS) {
tmp.add(object);
} else if (object.getKind() == JavaFileObject.Kind.SOURCE) {
tmp.add(JavaSources.compile(object));
} else {
throw new IllegalStateException("Type " + object.getKind() + " is not supported");
}
}

// TODO JavaFileObjectUtils.getClassName() ?!
Map<String, JavaFileObject> classes = tmp.stream().collect(Collectors.toMap(FileObject::getName, c -> c));

return new JavaFileObjectDataProvider(classes);
}

@Override
public byte[] getClassData(String className) {
return JavaFileObjectUtils.getAllBytes(classes.get(className));
}

}

+ 52
- 0
pf4j/src/test/java/org/pf4j/test/JavaFileObjectUtils.java View File

@@ -0,0 +1,52 @@
/*
* 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.test;

import com.google.common.io.ByteStreams;

import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.InputStream;

/**
* @author Decebal Suiu
*/
public class JavaFileObjectUtils {

private JavaFileObjectUtils() {}

public static String getClassName(JavaFileObject object) {
if (object.getKind() != JavaFileObject.Kind.CLASS) {
throw new IllegalStateException("Only Kind.CLASS is supported");
}

String name = object.getName();
// Remove "/CLASS_OUT/" from head and ".class" from tail
name = name.substring(14, name.length() - 6);
name = name.replace('/', '.');

return name;
}

public static byte[] getAllBytes(JavaFileObject object) {
try (InputStream in = object.openInputStream()) {
return ByteStreams.toByteArray(in);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

}

+ 64
- 0
pf4j/src/test/java/org/pf4j/test/JavaSources.java View File

@@ -0,0 +1,64 @@
/*
* 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.test;

import com.google.testing.compile.JavaFileObjects;

import javax.tools.JavaFileObject;
import java.util.List;

import static com.google.testing.compile.Compiler.javac;

/**
* Keep common Java sources (useful in many tests).
* For Java 13+ is recommended to use Text Block feature (it's more clear).
*
* @author Decebal Suiu
*/
public class JavaSources {

public static final JavaFileObject Greeting = JavaFileObjects.forSourceLines("Greeting",
"package test;",
"import org.pf4j.ExtensionPoint;",
"",
"public interface Greeting extends ExtensionPoint {",
" String getGreeting();",
"}");

public static final JavaFileObject WhazzupGreeting = JavaFileObjects.forSourceLines("WhazzupGreeting",
"package test;",
"import org.pf4j.Extension;",
"",
"@Extension",
"public class WhazzupGreeting implements Greeting {",
" @Override",
" public String getGreeting() {",
" return \"Whazzup\";",
" }",
"}");

/**
* Compile a list of sources using javac compiler.
*/
public static List<JavaFileObject> compileAll(JavaFileObject... sources) {
return javac().compile(sources).generatedFiles();
}

public static JavaFileObject compile(JavaFileObject source) {
return compileAll(source).get(0);
}

}

+ 1
- 2
pf4j/src/test/java/org/pf4j/test/TestPlugin.java View File

@@ -20,9 +20,8 @@ import org.pf4j.PluginWrapper;

/**
* A simple {@link Plugin}.
*
* In real applications you don't need to create a plugin like this if you are not interested in lifecycle events.
* {@codes PF4J} will automatically create a plugin similar to this (empty / dummy) if no class plugin is specified.
* {@code PF4J} will automatically create a plugin similar to this (empty / dummy) if no class plugin is specified.
*
* @author Mario Franco
*/

+ 6
- 32
pom.xml View File

@@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-parent</artifactId>
<version>3.7.0-SNAPSHOT</version>
<version>3.10.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>PF4J Parent</name>
<description>Plugin Framework for Java</description>
@@ -19,7 +19,7 @@
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
@@ -53,8 +53,6 @@
<junit.version>5.4.0</junit.version>
<hamcrest.version>2.1</hamcrest.version>
<mockito.version>3.8.0</mockito.version>
<cobertura.version>2.7</cobertura.version>
<coveralls.version>3.1.0</coveralls.version>

<javadoc.disabled>false</javadoc.disabled>
<deploy.disabled>false</deploy.disabled>
@@ -68,6 +66,7 @@
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
@@ -76,11 +75,13 @@
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
</plugin>
@@ -151,6 +152,7 @@
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
@@ -171,34 +173,6 @@
</modules>

<profiles>
<profile>
<id>travis</id>
<activation>
<property>
<name>env.TRAVIS</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>${cobertura.version}</version>
<configuration>
<formats>
<format>xml</format>
</formats>
</configuration>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>${coveralls.version}</version>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>

Loading…
Cancel
Save