How to use PluginManager

Mikle edited this page Jul 10, 2014 · 1 revision

Available since: WebLaF v1.28 release
Required Java version: Java 6 update 30 or any later
Module: core


What is it for?

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.


How to use it?

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.

Custom Plugin

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 this Plugin
  • 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 MyPlugin 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.

Custom PluginManager

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 by PluginManager. They might be either successfully loaded or not, but they all have a proper descriptor file which was read by the PluginManager (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 this PluginManager.

  • final List<MyAbstractPlugin> plugins = getAvailablePlugins ();
    This is a list of detected and successfully loaded or programmatically added plugins. Under "successfully loaded" I mean that their Plugin classes specified in their descriptors were properly constructed using reflection. Under "programmatically added" I mean that Plugin was constructed in your own code and simply passed to PluginManager 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

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).

Application

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 )
Preparing to launch

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!

Add plugins programmatically

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.

Plugin versions and filtering

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 )
Plugin construction

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.


Summary

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.