@@ -1,5 +0,0 @@ | |||
language: java | |||
jdk: | |||
- openjdk11 | |||
after_success: | |||
- mvn clean cobertura:cobertura coveralls:report |
@@ -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 |
@@ -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 |
@@ -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> | |||
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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> | |||
@@ -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)); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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> | |||
@@ -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 |
@@ -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> | |||
@@ -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) |
@@ -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> | |||
@@ -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> | |||
@@ -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> | |||
@@ -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> |
@@ -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); | |||
} | |||
} |
@@ -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 >=x.y.z | |||
* | |||
* @param exactVersionAllowed set to true or false | |||
*/ |
@@ -30,32 +30,32 @@ import java.util.List; | |||
public class ClassLoadingStrategy { | |||
/** | |||
* application(parent) -> plugin -> dependencies | |||
* application(parent) -> plugin -> dependencies | |||
*/ | |||
public static final ClassLoadingStrategy APD = new ClassLoadingStrategy(Arrays.asList(Source.APPLICATION, Source.PLUGIN, Source.DEPENDENCIES)); | |||
/** | |||
* application(parent) -> dependencies -> plugin | |||
* application(parent) -> dependencies -> plugin | |||
*/ | |||
public static final ClassLoadingStrategy ADP = new ClassLoadingStrategy(Arrays.asList(Source.APPLICATION, Source.DEPENDENCIES, Source.PLUGIN)); | |||
/** | |||
* plugin -> application(parent) -> dependencies | |||
* plugin -> application(parent) -> dependencies | |||
*/ | |||
public static final ClassLoadingStrategy PAD = new ClassLoadingStrategy(Arrays.asList(Source.PLUGIN, Source.APPLICATION, Source.DEPENDENCIES)); | |||
/** | |||
* dependencies -> application(parent) -> plugin | |||
* dependencies -> application(parent) -> plugin | |||
*/ | |||
public static final ClassLoadingStrategy DAP = new ClassLoadingStrategy(Arrays.asList(Source.DEPENDENCIES, Source.APPLICATION, Source.PLUGIN)); | |||
/** | |||
* dependencies -> plugin -> application(parent) | |||
* dependencies -> plugin -> application(parent) | |||
*/ | |||
public static final ClassLoadingStrategy DPA = new ClassLoadingStrategy(Arrays.asList(Source.DEPENDENCIES, Source.PLUGIN, Source.APPLICATION)); | |||
/** | |||
* plugin -> dependencies -> application(parent) | |||
* plugin -> dependencies -> application(parent) | |||
*/ | |||
public static final ClassLoadingStrategy PDA = new ClassLoadingStrategy(Arrays.asList(Source.PLUGIN, Source.DEPENDENCIES, Source.APPLICATION)); | |||
@@ -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); | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -131,7 +131,9 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor { | |||
} | |||
// write extensions | |||
storage.write(extensions); | |||
if (extensions.size() > 0) { | |||
storage.write(extensions); | |||
} | |||
return false; | |||
} |
@@ -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<>(); |
@@ -20,6 +20,8 @@ package org.pf4j.util; | |||
*/ | |||
public class StringUtils { | |||
private StringUtils() {} | |||
public static boolean isNullOrEmpty(String str) { | |||
return (str == null) || str.isEmpty(); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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(); | |||
@@ -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); | |||
} | |||
} |
@@ -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); |
@@ -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); | |||
} | |||
} | |||
@@ -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 { | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 | |||
*/ |
@@ -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> |