Skip to content

Commit 027b0a6

Browse files
committed
version 0.0.2
1 parent 87f21e7 commit 027b0a6

41 files changed

Lines changed: 1791 additions & 468 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 109 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,34 @@
66
# plugin-loader
77
A java plugin loader.
88

9+
## Table of contents
10+
- [Introduction](#introduction)
11+
- [Requirements](#requirements)
12+
- [How to load plugins from jar files](#how-to-load-plugins-from-jar-files)
13+
- [How to load plugins from ClassLoader](#how-to-load-plugins-from-classloader)
14+
- [Working with custom plugins](#working-with-custom-plugins)
15+
- [A word about error management](#a-word-about-error-management)
16+
- [Advanced usage](#advanced-usage)
17+
- [Plugin registry](#plugin-registry)
18+
- [Download plugins from a repository](#download-plugins-from-a-repository)
19+
20+
## Introduction
921
A plugin is a class that is loaded dynamically by an application to give extra functionalities or customization.
1022

1123
From the technical point of view, the plugin should implement an interface (or extends a class) defined by the application.
1224
Usually, the plugin can be stored in a jar file, but you can imagine loading it from the network.
1325

1426
This library helps application developper's to manage plugins in their application.
15-
It provides an abstraction of the process of loading plugins and a concrete implementation to load plugins stored in jar files.
27+
It provides an abstraction of the process of loading plugins and concrete implementations to load plugins stored in jar files.
28+
Unlike [java.util.ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html), it allows to customize error management and how plugin classes are discovered and instantiated.
1629

1730
The [plugin-loader-example](https://github.com/fathzer/plugin-loader/tree/main/plugin-loader-example) folder contains an example of jar plugin implementation and loading.
1831

19-
It requires java 8+
32+
## Requirements
33+
It requires java 11+.
34+
Nevertheless, a variant of this library is available for Java 8 users. They have to use the 'jdk8' [maven classifier](https://www.baeldung.com/maven-artifact-classifiers#bd-3-consuming-jar-artifact-of-a-specific-java-version) in their dependency. Only the [com.fathzer.plugin.loader.utils.AbstractPluginDownloader class](#download-plugins-from-a-repository) is not available in this variant.
2035

21-
## How to use the plugin loader with jar files
36+
## How to load plugins from jar files
2237

2338
### First define an interface for your plugin.
2439

@@ -27,6 +42,7 @@ It's a good practice to define this interface in a library different from the ap
2742
Here is the example:
2843

2944
```java
45+
package com.myapp;
3046
public interface AppPlugin {
3147
String getGreeting();
3248
}
@@ -47,30 +63,101 @@ public class MyPlugin implements AppPlugin {
4763
```
4864

4965
You should package the class in a jar file.
50-
As the plugin can be complex, the jar file can contains many classes. So, you have to define which class implements the plugin interface in a manifest attribute of the jar.
51-
By default *Plugin-Class* attribute is used. See [pom.xml of plugin example](https://github.com/fathzer/plugin-loader/blob/main/plugin-loader-example/plugin-loader-example-plugin/pom.xml) to view how to do it with Maven.
66+
As the plugin can be complex, the jar file can contains many classes. So, you have to define which class implements the plugin interface. The standard way is to add a resource file in META-INF/services. In this example, its path should be META-INF/services/com.myapp.AppPlugin and it should contain the canonical name of every plugin implementation classes (see [ServiceLoader documentation](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) to have the exact format of the file).
5267

5368
### Finally load the plugin in your application
5469

55-
The **JarPluginLoader** ([Javadoc is here](https://javadoc.io/doc/com.fathzer/plugin-loader)) class allow you to load the plugins contained in a local folder.
70+
com.fathzer.plugin.loader.jar.JarPluginLoader will allow you to get the plugin in your application.
5671

57-
Here is an example:
72+
Here is an example that loads the plugins contained in the *pluginFile* local jar file:
5873

5974
```java
60-
final JarPluginLoader loader = new JarPluginLoader();
61-
// Loads all the plugins at first level inside the pluginRepository folder.
62-
List<PlugInContainer<AppPlugin>> greetings = loader.getPlugins(pluginRepository, 1, AppPlugin.class);
63-
greetings.forEach(c -> {
64-
// For each plugin
65-
final AppPlugin p = c.get();
66-
if (p==null) {
67-
// An error occurred while loading the plugin.
68-
final File file = ((JarPlugInContainer<AppPlugin>)c).getFile();
69-
System.err.println("Unable to load plugin in file "+file+", error is "+c.getException());
70-
} else {
71-
// The plugin was successfully loaded, use it.
72-
System.out.println("Found plugin "+p.getClass()+". It returns "+p.getGreeting());
73-
}
74-
});
75+
final PluginLoader<Path> loader = new JarPluginLoader();
76+
final List<AppPlugin> plugins = loader.getPlugins(pluginFile, AppPlugin.class);
7577
```
7678

79+
An usual need is to load all plugins in a local directory.
80+
*com.fathzer.loader.utils.FileUtils.getJarFiles* method allows you to search for jar files in a directory.
81+
You have then to iterate over the returned files.
82+
83+
## How to load plugins from ClassLoader
84+
JarPluginLoader is not the only way to load plugins. *com.fathzer.plugin.loader.PluginLoader* is an abstract class that can have multiple implementations.
85+
Another classical implementation provided by this library is *ClassLoaderPluginLoader*.
86+
It allows you to search and load plugins through a class loader.
87+
88+
A typical use is to load the plugins available on the classpath. Here is the code to do that:
89+
```java
90+
final ClassLoaderPluginLoader loader = new ClassLoaderPluginLoader();
91+
final List<AppPlugin> plugins = loader.getPlugins(AppPlugin.class);
92+
```
93+
94+
## Working with custom plugins
95+
The default implementation works with plugin defined as services useable with [ServiceLoader documentation](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html), but you can change how the plugins are discovered or how they are instantiated.
96+
97+
The plugin class names are discovered by a ClassNameBuilder. You can change the default one using the PluginLoader.withClassNameBuilder.
98+
Here is an example that use a predefined class name:
99+
```java
100+
final PluginLoader<ClassLoader> loader = new ClassLoaderPluginLoader();
101+
loader.withClassNameBuilder((c,v) -> Collections.singleton("com.fathzer.MyPlugin"));
102+
```
103+
104+
You can also change the way plugins are instantiated using the PluginLoader.withInstanceBuilder method. For instance to use a constructor with argument.
105+
Here is an example that uses a constructor with a fixed string argument:
106+
```java
107+
final PluginLoader<ClassLoader> loader = new ClassLoaderPluginLoader();
108+
final String context = ...
109+
InstanceBuilder ib = new InstanceBuilder() {
110+
@Override
111+
public <T> T get(Class<T> pluginClass) throws Exception {
112+
final Constructor<T> constructor = pluginClass.getConstructor(String.class);
113+
return constructor.newInstance(context);
114+
}
115+
};
116+
loader.withInstanceBuilder(ib);
117+
```
118+
119+
## A word about error management
120+
If a problem occurs during plugin instantiation, a *PluginInstantiationException* is throw. This is the default behaviour, but you prefer to log the error and continue to instantiate other plugins contained in a jar.
121+
You can simply customize the exception management using the *PluginLoader.withExceptionConsumer* method as in the following example:
122+
```java
123+
new ClassLoaderPluginLoader().withExceptionConsumer(e -> log.warn("An error occurred while loading plugins", e));
124+
```
125+
126+
## Advanced usage
127+
An usual need is to have a bunch of plugins that are selected by a key. For instance, you can imagine an interface that do *something* with an URI. You can have multiple implementations of the interface, one for each supported uri scheme.
128+
129+
### Plugin registry
130+
The *com.fathzer.loader.utils.PluginRegistry* maps String keys to plugin instantiations. You can register plugins, then retrieve them from their keys, , etc...
131+
132+
### Download plugins from a repository
133+
**Warning: This section is not available for java8 version of this library**.
134+
135+
Once you have a plugin registry, a basic way to populate it is to read plugins from a local jar directory.
136+
It's simple ... but how to update this local directory when a new plugin is released?
137+
138+
The *com.fathzer.plugin.loader.utils.AbtractPluginDownloader* class allows you to define your own plugin remote repository and download the required jar to a local folder.
139+
You can then use JarPluginLoader to load these plugins.
140+
141+
A repository is basically a map between a key and an URI where to download a jar.
142+
This map should be available at an URI. Let's say for instance *https://com.myApp/AppPluginRepository*.
143+
The format of serialization format of the map is free. The AbtractPluginDownloader is abstract because you have to implement the parsing of the repository URI. For the example, let say the map is stored in json format:
144+
```json
145+
{
146+
"https":"https://com.myApp/AppPlugins/http.jar",
147+
"sftp":"https://com.myApp/AppPlugins/sftp.jar"
148+
}
149+
```
150+
151+
Here is an implementation that uses jackson to parse the map:
152+
```java
153+
AbstractPluginsDownloader dl = new AbstractPluginsDownloader(URI.create("https://com.myApp/AppPluginRepository"), Paths.get("Plugins")) {
154+
@Override
155+
protected Map<String, URI> getURIMap(InputStream in) throws IOException {
156+
return new ObjectMapper().readValue(in, new TypeReference<HashMap<String, URI>>() {});
157+
}
158+
};
159+
```
160+
161+
You can then load the URI map using ```dl.getURIMap()```, or download jar plugins for some keys using ``̀ dl.download("sftp")```.
162+
163+
AbstractPluginsDownloader has many protected methods. Feel free to override them to make this class fits with your needs.

overview.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head><title>Overview</title></head>
4+
<body>Have a look at <a href="https://github.com/fathzer/plugin-loader">https://github.com/fathzer/plugin-loader</a> for usage information.</body>
5+
</html>

plugin-loader-test-plugin/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<archive>
1919
<manifestEntries>
2020
<Plugin-Class>com.fathzer.plugin.loader.test.Plugin</Plugin-Class>
21+
<Strange-names> com.fathzer.plugin.loader.test.Plugin , , another </Strange-names>
2122
<Unknown-Plugin-Class>com.fathzer.plugin.test.UnknownPlugin</Unknown-Plugin-Class>
2223
<Wrong-Constructor-Plugin>com.fathzer.plugin.test.WrongConstructorPlugin</Wrong-Constructor-Plugin>
2324
</manifestEntries>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#A comment
2+
3+
com.fathzer.plugin.loader.test.Plugin
4+
5+
#A comment

pom.xml

Lines changed: 77 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
24
<modelVersion>4.0.0</modelVersion>
3-
<groupId>com.fathzer</groupId>
5+
<parent>
6+
<groupId>com.fathzer</groupId>
7+
<artifactId>parent-pom</artifactId>
8+
<version>1.0.6</version>
9+
</parent>
410
<artifactId>plugin-loader</artifactId>
5-
<version>0.0.1</version>
11+
<version>0.0.2</version>
612

713
<name>plugin-loader</name>
814
<description>A java plugin loader.</description>
@@ -13,116 +19,113 @@
1319
<connection>https://github.com/fathzer/plugin-loader.git</connection>
1420
</scm>
1521

16-
<licenses>
17-
<license>
18-
<name>Apache License, Version 2.0</name>
19-
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
20-
<distribution>repo</distribution>
21-
<comments>A business-friendly OSS license</comments>
22-
</license>
23-
</licenses>
24-
25-
<developers>
26-
<developer>
27-
<id>Fathzer</id>
28-
<name>Jean-Marc Astesana</name>
29-
<email>admin@fathzer.com</email>
30-
</developer>
31-
</developers>
32-
3322
<properties>
3423
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
35-
<maven.compiler.target>8</maven.compiler.target>
36-
<maven.compiler.source>8</maven.compiler.source>
37-
<sonar.organization>fathzer</sonar.organization>
38-
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
24+
<jdk8-classes>${project.build.outputDirectory}_jdk8</jdk8-classes>
25+
<animal-version>1.23</animal-version>
3926
</properties>
4027

41-
<distributionManagement>
42-
<repository>
43-
<id>ReleaseFathzer</id>
44-
<name>Release fathzer.com</name>
45-
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
46-
</repository>
47-
<snapshotRepository>
48-
<id>SnapshotFathzer</id>
49-
<name>Snapshot fathzer.com</name>
50-
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
51-
<uniqueVersion>false</uniqueVersion>
52-
</snapshotRepository>
53-
</distributionManagement>
54-
5528
<dependencies>
29+
<dependency>
30+
<groupId>org.codehaus.mojo</groupId>
31+
<artifactId>animal-sniffer-annotations</artifactId>
32+
<version>${animal-version}</version>
33+
</dependency>
5634
<dependency>
5735
<groupId>org.junit.jupiter</groupId>
5836
<artifactId>junit-jupiter</artifactId>
5937
<version>5.7.2</version>
6038
<scope>test</scope>
6139
</dependency>
40+
<dependency>
41+
<groupId>org.mockito</groupId>
42+
<artifactId>mockito-junit-jupiter</artifactId>
43+
<version>5.1.1</version>
44+
<scope>test</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>com.squareup.okhttp3</groupId>
48+
<artifactId>mockwebserver</artifactId>
49+
<version>4.10.0</version>
50+
<scope>test</scope>
51+
</dependency>
6252
</dependencies>
6353

6454
<build>
6555
<plugins>
6656
<plugin>
6757
<groupId>org.apache.maven.plugins</groupId>
68-
<artifactId>maven-surefire-plugin</artifactId>
69-
<!-- JUnit 5 requires Surefire version 2.22.0 or higher -->
70-
<version>2.22.2</version>
71-
</plugin>
72-
<plugin>
73-
<groupId>org.kohsuke</groupId>
74-
<artifactId>pgp-maven-plugin</artifactId>
75-
<version>1.1</version>
58+
<artifactId>maven-compiler-plugin</artifactId>
59+
<configuration>
60+
<source>8</source>
61+
<target>8</target>
62+
<fork>true</fork>
63+
</configuration>
7664
<executions>
7765
<execution>
66+
<id>default-compile</id>
67+
<phase>compile</phase>
7868
<goals>
79-
<goal>sign</goal>
69+
<goal>compile</goal>
8070
</goals>
71+
<configuration>
72+
<release>11</release>
73+
</configuration>
8174
</execution>
82-
</executions>
83-
<configuration>
84-
<!-- For obvious security reasons, keyFile and pass phrase are not provided,
85-
they should be in file present in the user's home directory -->
86-
<secretkey>keyfile:${user.home}/fathzer_private_key.asc</secretkey>
87-
<passphrase>file:${user.home}/fathzer_key_pwd.txt</passphrase>
88-
</configuration>
89-
</plugin>
90-
91-
<plugin>
92-
<groupId>org.apache.maven.plugins</groupId>
93-
<artifactId>maven-source-plugin</artifactId>
94-
<version>3.2.1</version>
95-
<executions>
9675
<execution>
97-
<id>attach-sources</id>
76+
<id>JDK 8</id>
77+
<phase>compile</phase>
9878
<goals>
99-
<goal>jar</goal>
79+
<goal>compile</goal>
10080
</goals>
81+
<configuration>
82+
<source>8</source>
83+
<target>8</target>
84+
<fork>true</fork>
85+
<outputDirectory>${jdk8-classes}</outputDirectory>
86+
<excludes>
87+
<exclude>**/com/fathzer/plugin/loader/utils/AbstractPluginsDownloader.java</exclude>
88+
</excludes>
89+
</configuration>
10190
</execution>
10291
</executions>
10392
</plugin>
104-
10593
<plugin>
10694
<groupId>org.apache.maven.plugins</groupId>
107-
<artifactId>maven-javadoc-plugin</artifactId>
108-
<version>3.3.1</version>
109-
<configuration>
110-
<!-- see https://stackoverflow.com/questions/69320220/maven-javadoc-listed-classes-twice -->
111-
<sourcepath>${basedir}/src/main/java</sourcepath>
112-
<header>${project.version}</header>
113-
<footer>${project.version}</footer>
114-
<doclint>all</doclint>
115-
</configuration>
95+
<artifactId>maven-surefire-plugin</artifactId>
96+
<!-- JUnit 5 requires Surefire version 2.22.0 or higher -->
97+
<version>2.22.2</version>
98+
</plugin>
99+
<plugin>
100+
<groupId>org.apache.maven.plugins</groupId>
101+
<artifactId>maven-jar-plugin</artifactId>
102+
<version> 3.2.2</version>
116103
<executions>
117104
<execution>
118-
<id>javadoc_generation</id>
105+
<id>default-package-jdk8</id>
119106
<phase>package</phase>
120107
<goals>
121108
<goal>jar</goal>
122109
</goals>
110+
<configuration>
111+
<classesDirectory>${jdk8-classes}</classesDirectory>
112+
<classifier>jdk8</classifier>
113+
</configuration>
123114
</execution>
124115
</executions>
125116
</plugin>
117+
<plugin>
118+
<groupId>org.codehaus.mojo</groupId>
119+
<artifactId>animal-sniffer-maven-plugin</artifactId>
120+
<configuration>
121+
<signature>
122+
<groupId>org.codehaus.mojo.signature</groupId>
123+
<artifactId>java18</artifactId>
124+
<version>1.0</version>
125+
</signature>
126+
<ignores>/home/jma/git/plugin-loader/src/main/java/com/fathzer/plugin/loader/utils/AbstractPluginsDownloader.java</ignores>
127+
</configuration>
128+
</plugin>
126129
</plugins>
127130
</build>
128131
</project>

0 commit comments

Comments
 (0)