Extending PF4J to scan for @Extensions

I’ve recently been working with PF4J to create an MVP for a new JADAPTIVE project that is due for launch later this year. The project implements a Java SFTP server with Maverick Synergy that allows users to mount external file systems into a virtual file system.

PF4J is a framework for building pluggable Java applications. It enables the separation of application logic across plugins, with each plugin having a dedicated ClassLoader to help with the management of dependencies. Your application interacts with plugins through a standard API; you can declare extension points in your API and then implement these in plugins.

For example, since I am dealing with external systems, I need to link to third-party libraries from Google and Amazon, amongst others. Anyone that has dealt with these libraries will know they have extensive and changing dependencies, and sometimes its a problem to try to include them all in the same classpath.

With PF4J, I can declare an interface for each file scheme to implement and declare this as an extension point by extending the ExtensionPoint interface.

public interface FileScheme extends ExtensionPoint {
...
}

With this interface shared across all plugins through my application API, I can then implement that interface within any of my plugins. To do this, I mark the Java class with the @Extension annotation.

@Extension 
public class S3FileScheme implements FileScheme {
...
}

PF4J, in its default mode, uses annotation processing at compile time to create extensions.idx files, which it places in classpath resources. I found this a little cumbersome when working in development and using Eclipse/Maven. I decided to take a different approach, and the great thing about PF4J is that it is very extensible.

To find extensions, PF4J uses the ExtensionFinder interface, and you can override how this is created in the PluginManager instance your application uses.

@Override
protected ExtensionFinder createExtensionFinder() {
     return new ScanningExtensionFinder(this);
}

I’ve been using ClassGraph to lookup custom annotations, so being familiar with this, I created a ScanningExtensionFinder that looks for @Extension annotations under each plugins package. We implement this by iterating over each plugin and scanning the plugins own classpath.

List plugins = pluginManager.getPlugins();
for (PluginWrapper plugin : plugins) {
...
}

There are a couple of key points to note to ensure you configure the scanner correctly. First, make sure you are only scanning on the plugins own classloader.

.addClassLoader(plugin.getPluginClassLoader())

Secondly, I ensure that each plugin has a unique package that does not overlap with others. This requires setting a white list package on the scanner.

.whitelistPackages(plugin.getPlugin().getClass().getPackage().getName())

We execute the scanner and look for the Extension annotation, adding any classes that are annotated with it to our results.

try (ScanResult scanResult = new ClassGraph()                 
     .enableAllInfo()  
     .addClassLoader(plugin.getPluginClassLoader())
     .whitelistPackages(plugin.getPlugin().getClass().getPackage().getName())   
     .scan()) {              
          for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Extension.class.getName())) {
               bucket.add(classInfo.getName());
          }
}

The full class implementation can be found on my github project at ScanningExtensionFinder.java