-
Notifications
You must be signed in to change notification settings - Fork 235
How to use PluginManager
Available since: WebLaF v1.2.9 release
Required Java version: Java 6 update 30 or any later
Module: plugin
PluginManager
provides a simple and convenient way to load, store and use plugins within your application. It handles lots of possible load problems and also provides tools to filter out unwanted plugins.
Plugins can be loaded in a few different ways:
- From JAR file
- From directory (recursively or not)
- From the code
Each of these ways has a little different approach, but in the end all of them use the same logic to initialize and store loaded plugins.
First of all - Plugin
and PluginManager
are abstract classes and you will have to create your own ones on top of them. Let's start with it.
Plugin
class is a simple base for any kind of plugins you might want to create for your application. It contains base information about the loaded plugin:
-
PluginManager
that loaded thisPlugin
-
DetectedPlugin
that contains information about plugin file and from plugin descriptor -
InitializationStrategy
that affects the order in which detected plugins are sorted
DetectedPlugin
is a tricky class used within PluginManager
to operate plugins, I will get back to it and describe it a bit later.
Plugin
class also has a few methods to access its information fields directly, without making additional method calls.
So let's make our own Plugin
extension now, first of all an abstract one to provide some core methods we want to see implemented in each specific plugin - this basically our custom plugin API:
public abstract class MyAbstractPlugin extends Plugin
{
public abstract void applicationStart ();
public abstract void applicationShutdown ();
}
Its not necessary to create this one, but in the longer term you might find this really useful if all of your different plugins have something similar.
Now let's create one simple "implementation" for it:
public class MyPlugin extends MyAbstractPlugin
{
@Override
public void applicationStart ()
{
System.out.println ( "Application start" );
}
@Override
public void applicationShutdown ()
{
System.out.println ( "Application shutdown" );
}
}
Note that all plugins must have an empty constructor (with no arguments), otherwise PluginManager
won't be able to construct your Plugin
extension.
This is it, let's move to PluginManager
now.
Base PluginManager
class has a few possible constructors - you are free to choose whether you need some of them or even if you want something else. I'll go with the standard one for this example:
public class MyPluginManager extends PluginManager<MyAbstractPlugin>
{
public MyPluginManager ()
{
super ( "plugins" );
}
@Override
protected String getPluginDescriptorFile ()
{
return "my-plugin.xml";
}
public void applicationStart ()
{
for ( final MyAbstractPlugin plugin : getAvailablePlugins () )
{
plugin.applicationStart ();
}
}
public void applicationShutdown ()
{
for ( final MyAbstractPlugin plugin : getAvailablePlugins () )
{
plugin.applicationShutdown ();
}
}
}
As you can see it uses MyAbstractPlugin
as a plugin type so that you don't need to cast plugins retrieved from MyPluginManager
to that specific type. Of course if you want to get MyPlugin
and not just MyAbstractPlugin
you will have to either cast the retrieved plugin or create a separate PluginManager
with MyPlugin
as type.
I have also added a few methods to call our plugins API on all available plugins. It's a good idea to add them here and not make those calls somewhere else, so that PluginManager
proxies all requests to the available plugins.
Before we move forward let me explain what exactly are "detected" and "available" plugin lists:
-
final List<DetectedPlugin<MyAbstractPlugin>> plugins = getDetectedPlugins ();
This is a list of plugins detected byPluginManager
. They might be either successfully loaded or not, but they all have a proper descriptor file which was read by thePluginManager
(I will tell what is that descriptor which I have mentioned twice already a bit later). This list is never cleared and contains all plugins that went though thisPluginManager
. -
final List<MyAbstractPlugin> plugins = getAvailablePlugins ();
This is a list of detected and successfully loaded or programmatically added plugins. Under "successfully loaded" I mean that theirPlugin
classes specified in their descriptors were properly constructed using reflection. Under "programmatically added" I mean thatPlugin
was constructed in your own code and simply passed toPluginManager
to keep track of it.
Now when we have our own PluginManager
and Plugin
API and implementation its time for the final step before we can start loading our plugins.
Plugin descriptor is a simple XML file that contains serialized version of PluginInformation
class which contains various information about specific plugin - title, type, description, main class etc. Plugin descriptor can be placed anywhere you like within the plugin JAR.
Note that if you are adding Plugin
programmatically you don't need one, but you will have to provide PluginInformation
manually - either when registering your Plugin
within PluginManager
or inside the Plugin
class itself by overriding getPluginInformation
method. I will describe how plugins can be registered programmatically a bit later.
So let's write a custom my-plugin.xml
descriptor now - yes, it should be named exactly my-plugin.xml
as we have mentioned it in MyPluginManager.getPluginDescriptorFile
method. Here we go:
<PluginInformation>
<id>my.plugin</id>
<title>My Plugin</title>
<description>My own plugin implementation</description>
<version major="1" minor="0" />
<disableable>true</disableable>
<mainClass>my.app.MyPlugin</mainClass>
</PluginInformation>
This is a simple version of descriptor that skips plugin type (its not really necessary, its just an additional plugin filtering measure) and plugin libraries which should be added into classpath (well, we don't have any so we just skip them).
Now we just need to write some sample application to launch our PluginManager
and see whether it works or not, let's make a simple and straightforward one without any UI:
public class MyApplication
{
public static void main ( final String[] args )
{
// Initializing PluginManager
final MyPluginManager pluginManager = new MyPluginManager ();
// Initializing plugins from the "plugins" directory
pluginManager.scanPluginsDirectory ();
// Calling start methods on available plugins
pluginManager.applicationStart ();
// Some custom application body
System.out.println ( "Application running..." );
ThreadUtils.sleepSafely ( 5000 );
System.out.println ( "Application finished" );
// Calling shutdown methods on available plugins
pluginManager.applicationShutdown ();
}
}
The main line in this code which triggers plugins loading is pluginManager.scanPluginsDirectory ();
- it forces PluginManager
to check previously specified directory (we have provided it in the constructor) for plugins and after that tries to initialize all of them.
There are a few other methods you can use to either check specific directories:
public void scanPluginsDirectory ()
public void scanPluginsDirectory ( final boolean checkRecursively )
public void scanPluginsDirectory ( final String pluginsDirectoryPath )
public void scanPluginsDirectory ( final String pluginsDirectoryPath, final boolean checkRecursively )
or specific plugin JARs:
public void scanPlugin ( final URL pluginFileURL )
public void scanPlugin ( final String pluginFile )
public void scanPlugin ( final File pluginFile )
We have all we need now to start our application and test the custom Plugin
, we just need to be careful about paths since we have hardcoded path to plugins directory and descriptor name in MyPluginManager
and MyPlugin
class canonical name in descriptor.
Here is how our application structure should look like:
Structure
- .../application.jar
- .../plugins/plugin.jar
application.jar
- my/app/MyApplication.class
- my/app/MyPluginManager.class
- my/app/MyAbstractPlugin.class
plugin.jar
- my/app/MyPlugin.class
- my/app/resources/my-plugin.xml
And working directory should be set to the application.jar
location since we have specified relative plugins folder path.
Anyway, this should be enough to launch the application and see the plugin output in the log meaning that plugin was successfully loaded and used!
If you have an implementation of MyAbstractPlugin
that is already constructed in the application code or you just don't want to make it a separate JAR - you can still register it within the PluginManager
to keep track of it and handle it in the same other plugins are handled.
There are basically two methods within PluginManager
which allow registering plugin instances:
public void registerPlugin ( final T plugin )
public void registerPlugin ( final T plugin, final PluginInformation information, final ImageIcon logo )
As you might find from the code - if you are using the first method you will have to provide proper PluginInformation
in your Plugin
class:
public void registerPlugin ( final T plugin )
{
registerPlugin ( plugin, plugin.getPluginInformation (), plugin.getPluginLogo () );
}
Logo can be null.
For each Plugin
registered this way there will be DetectedPlugin
created with null
plugin folder and name since this plugin doesn't have any local file to load it from.
Also be aware that such plugins will be added directly into available plugins list and will be returned together with other plugins which might have been loaded from JARs.
This is also a pretty important topic to mention - PluginManager
doesn't ignore loaded plugins IDs and versions, it uses them to ensure that the same plugin is not loaded twice and that only the latest available version of plugin is being loaded. Of course that applies only to the one-time pass of the scanning within which similar or same plugins are found.
This is the base filtering system implemented inside the PluginManager. You can also add your own filter on top of that. You will need this one to filter out unwanted files from loading into detected plugins list:
public void setFileFilter ( final FileFilter filter )
And this one to filter out unwanted detected plugins from initialization:
public void setPluginFilter ( final Filter<DetectedPlugin<T>> pluginFilter )
As I have mentioned before - PluginManager
uses Reflection to construct any given Plugin
using its empty constructor. But there is one more important thing - how exactly plugin's JAR and its dependencies are handled and loaded into classpath.
By default PluginManager
adds plugin's JAR and its dependencies straight into the main application classpath without creating additional class loaders. You can read here how this done.
There is also a second option which is switched off by default - new class loader creation for each separate plugin. It can be enabled using this method:
public void setCreateNewClassLoader ( final boolean createNewClassLoader )
But be aware that you have to be careful with class usage within your plugin in that case.
This is basically all you need to know about PluginManager
to successfully use it within your own project.
Plugins API approach I have used as an example in this article is only an option - you can always choose your own way to handle different plugins within your application.
If you found any mistakes or inconsistency in this article, feel that it is lacking explanation or simply want to request an additional wiki article covering some topic:
I will do my best to answer and provide assistance as soon as possible!