The goal of this project is to supply an update mechanism for PF4J. It's an open source (Apache license) lightweight (around 15KB) extension for PF4J, with minimal dependencies (only pf4j and gson).
- UpdateManager allows you to inspect the repositories (local, remote) for updates, available (new) plugins. Also this class allows you to install available plugins, update or uninstall existing plugins.
- UpdateRepository defines a local/remote repository (pluggable)
- PluginInfo defines the plugin information for each repository's plugin
- PluginRelease defines a plugin release
- FileDownloader defines a pluggable way of downloading from a repository
- FileVerifier defines a pluggable way of verifying downloaded files, e.g. checksum verification
In your pom.xml you must define the dependencies to PF4J artifacts with:
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-update</artifactId>
<version>${pf4j-update.version}</version>
</dependency>
where ${pf4j-update.version} is the last pf4j-update version.
You may want to check for the latest released version using Maven Search
It's very simple to add pf4j-update in your application. The whole code of below snippet is available on Github.
public static void main(String[] args) {
...
// create plugin manager
PluginManager pluginManager = new DefaultPluginManager();
pluginManager.loadPlugins();
// create update manager
UpdateManager updateManager = new UpdateManager(pluginManager);
// >> keep system up-to-date <<
boolean systemUpToDate = true;
// check for updates
if (updateManager.hasUpdates()) {
List<PluginInfo> updates = updateManager.getUpdates();
log.debug("Found {} updates", updates.size());
for (PluginInfo plugin : updates) {
log.debug("Found update for plugin '{}'", plugin.id);
PluginInfo.PluginRelease lastRelease = updateManager.getLastPluginRelease(plugin.id);
String lastVersion = lastRelease.version;
String installedVersion = pluginManager.getPlugin(plugin.id).getDescriptor().getVersion();
log.debug("Update plugin '{}' from version {} to version {}", plugin.id, installedVersion, lastVersion);
boolean updated = updateManager.updatePlugin(plugin.id, lastVersion);
if (updated) {
log.debug("Updated plugin '{}'", plugin.id);
} else {
log.error("Cannot update plugin '{}'", plugin.id);
systemUpToDate = false;
}
}
} else {
log.debug("No updates found");
}
// check for available (new) plugins
if (updateManager.hasAvailablePlugins()) {
List<PluginInfo> availablePlugins = updateManager.getAvailablePlugins();
log.debug("Found {} available plugins", availablePlugins.size());
for (PluginInfo plugin : availablePlugins) {
log.debug("Found available plugin '{}'", plugin.id);
PluginInfo.PluginRelease lastRelease = updateManager.getLastPluginRelease(plugin.id);
String lastVersion = lastRelease.version;
log.debug("Install plugin '{}' with version {}", plugin.id, lastVersion);
boolean installed = updateManager.installPlugin(plugin.id, lastVersion);
if (installed) {
log.debug("Installed plugin '{}'", plugin.id);
} else {
log.error("Cannot install plugin '{}'", plugin.id);
systemUpToDate = false;
}
}
} else {
log.debug("No available plugins found");
}
if (systemUpToDate) {
log.debug("System up-to-date");
}
...
}
The library has very few components. The main component is the UpdateManager.
This class contains methods for repositories inspection
public List<UpdateRepository.PluginInfo> getAvailablePlugins();
public boolean hasAvailablePlugins();
public List<UpdateRepository.PluginInfo> getUpdates();
public boolean hasUpdates();
public List<UpdateRepository.PluginInfo> getPlugins();
public List<UpdateRepository> getRepositories();
and methods for plugin handling
public boolean installPlugin(String url);
public boolean updatePlugin(String id, String url);
public boolean uninstallPlugin(String id);
UpdateManager can work with multiple repositories (local and remote).
All repositories are either defined in a repositories.json
file or
provided in UpdateManager's constructor.
Below I defined two repository: localhost and folder.
[
{
"id": "localhost",
"url": "http://localhost:8081/"
},
{
"id": "folder",
"url": "file:/home/decebal/work/pf4j-update/downloads/"
}
]
Each repository has a unique id and a URL.
In the root of this project you have a repositories.json
file used by
the test applications.
For more information please see the test sources (UpdateTest, ...). It's a good idea to run these tests and to see the results.
The project is made for customization and extension to your own needs. Here are some examples:
First you can supply to UpdateManager
your custom location and name
of repositories.json
if you want it to live somewhere else.
If you need even more control, UpdateManager
accepts repositories in
constructor and through setters.
Implement your own UpdateRepository
, FileDownloader
and FileVerifier
s
to handle your own custom repsitory structures, authentication, checksum
verifications etc.
For full control, subclass UpdateManager
and override relevant methods.
Each repository exposes multiple plugins using a plugins.json
file.
Below I registered two plugins: welcome-plugin and hello-plugin.
[
{
"id": "welcome-plugin",
"description": "Welcome plugin",
"releases": [
{
"version": "0.8.0",
"date": "Jun 5, 2014 9:00:35 PM",
"url": "pf4j-demo-plugin1/0.8.0/pf4j-demo-plugin1-0.8.0.zip"
},
{
"version": "0.9.0",
"date": "Jun 25, 2014 9:58:35 PM",
"url": "pf4j-demo-plugin1/0.9.0/pf4j-demo-plugin1-0.9.0.zip"
}
]
},
{
"id": "hello-plugin",
"description": "Hello plugin",
"releases": [
{
"version": "0.8.0",
"date": "Jun 5, 2014 9:12:35 PM",
"url": "pf4j-demo-plugin2/0.8.0/pf4j-demo-plugin2-0.8.0.zip"
},
{
"version": "0.9.0",
"date": "Jun 25, 2014 9:58:35 PM",
"url": "pf4j-demo-plugin2/0.9.0/pf4j-demo-plugin2-0.9.0.zip"
}
]
}
]
Fields per plugin
Property | Format | Description |
---|---|---|
id | string | Unique id, mandatory |
name | string | Display name (short) |
description | string | Describe your plugin |
provider | string | Name of plugin provider |
releases | List | List of releases (minimum one) |
Fields per release
Property | Format | Description |
---|---|---|
version | X.Y.Z | Version of release (SemVer format) |
date | date | Release date, ISO8601 or yyyy-MM-dd format |
requires | Expression | SemVer expression, e.g. ">=2.0.0" |
url | URL-string | Link to zip, either absolute or relative URL |
sha512sum | <sha512-digest> or <hash-file URL> or ".sha512" |
String with SHA-512 HEX digest of file content URL to file with SHA-512 string Fetch SHA-512 file next to plugin, with .sha512 file suffix |
New properties may appear in the future.
The last (current) release of the plugin is calculated taking into account by the version property. In our example, the last release for each plugin is the release with version 0.9.0.
We encourage using yyyy-MM-dd
format for release date. Localized US format
as in the examples above will also work. If the date is not parsable, it
will be set to epoch (1970-01-01) and print a warning in logs.
NOTE: The requires
property was a simple X.Y.Z string in versions
up to 0.3.0, interpreted as >=X.Y.Z
. You may want to update your old
plugins.json
files to the new syntax.
URL from repositories.json
: http://localhost:8081/
Relative URL in plugins.json
: pf4j-demo-plugin2/0.8.0/pf4j-demo-plugin2-0.8.0.zip
Resulting UpdateRepository.PluginRelease.url
: http://localhost:8081/pf4j-demo-plugin2/0.8.0/pf4j-demo-plugin2-0.8.0.zip
In the downloads folder of the project you have a repository (plugins.json and artifacts - plugins archives) used by the test applications. The structure of the repository is:
- plugin1
- 0.8.0
- plugin1.zip
- 0.9.0
- plugin1.zip
- 0.8.0
- plugin2
- 0.8.0
- plugin2.zip
- 0.9.0
- plugin2.zip
- 0.8.0
- plugins.json
For each plugin you have a folder (plugin1, plugin2) that contains subfolder for each version (0.8.0, 0.9.0). In each version folder you have the plugin archive (.zip) according to PF4J specification.
Much of the conversation between developers and users is managed through mailing list.
This project will be maintained under the Semantic Versioning guidelines as much as possible.
Releases will be numbered with the follow format:
<major>.<minor>.<patch>
And constructed with the following guidelines:
- Breaking backward compatibility bumps the major
- New additions without breaking backward compatibility bumps the minor
- Bug fixes and misc changes bump the patch
For more information on SemVer, please visit http://semver.org/.