Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Inspector Gadget - Plugin Loader & Testing Assistant

5.00/5 (1 vote)
26 May 2014CPOL1 min read 7.2K   46  
A framework for plug-in loading and a way to load plug-ins in development environment that doesn't require redeployment of the plug-ins

Introduction

A framework for plug-in loading and a way to load plug-ins in a development environment that doesn't require redeployment of the plug-ins after each change.

Background

A plug-in is a component (jar) that doesn't compile with the host application, meaning that the host application operates independently of the plug-ins, making it possible to add and update plug-ins dynamically without needing to make changes to the host application.

This architecture complicates the development process when you develop both the plug-in and the host application, since whenever you make a change in the plug-in, it requires to redeploy it on the host application.

The DNA of working in TDD/BDD methodologies is running tests all the time, which means redeploy for each test run.

Therefore, we are loading the plug-ins from their target path in development, instead of loading them from their deployment location.

This way, whenever a change is compiled in the plug-in, on the next run of the host application, it will contain the change, like a regular dependency.

Plug-in Architecture

Using the Code

The following code demonstrates how to add libraries to classpath and load plug-ins in runtime in production and development.

Starting with the Tests

Java
/**
 * Verifies plug-ins were loaded from production location
 */
public class TestPluginLoader extends TestPluginLoaderBase {
    
    @Test
    public void testLoad() throws InstantiationException, IllegalAccessException {
        
        testLoad(new PluginLoader());
    }
}
/**
 * Verifies plug-ins were loaded from development location (Bin folder).
 */
public class TestBinPluginLoader extends TestPluginLoaderBase {
    
    @Test
    public void testLoad() throws InstantiationException, IllegalAccessException {
        
        testLoad(new BinPluginLoader());
    }
}
public abstract class TestPluginLoaderBase {
    
    private final ClasspathCaretaker _classpathCaretaker = new ClasspathCaretaker();
    
    @Before
    public void setUp() {
        
        _classpathCaretaker.save();
    }
    
    @After
    public void teardown() {
        
        _classpathCaretaker.restore();
    }
    
    protected void testLoad(PluginLoaderBase loader) throws InstantiationException,
            IllegalAccessException {
        
        Set<Class<? extends Plugin>> plugins = loader.load(Paths.get("plugins"));
        Collection<String> expected = new ArrayList<>();
        expected.add("plugin-helicopter");
        expected.add("plugin-rollerblade");
        ClasspathUtils.print();
        Assert.assertEquals(plugins.toString(), expected.size(), plugins.size());
        for (String currName : expected) {
            Assert.assertTrue(isExist(plugins, currName));
        }
    }
    
    private boolean isExist(Set<Class<? extends Plugin>> plugins, String name)
            throws InstantiationException, IllegalAccessException {
        
        boolean ret = false;
        for (Class<? extends Plugin> currPlugin : plugins) {
            if (currPlugin.newInstance().getId().equals(name)) {
                ret = true;
                break;
            }
        }
        
        return ret;
    }
}

Implementation

Java
/**
 * Loads plug-ins from a provided location.
 */
public class PluginLoader extends PluginLoaderBase {
    
    @Override
    public Set<Class<? extends Plugin>> load(Path pluginsDir) {
        
        Path pluginDir = getPluginDir(pluginsDir);
        new ClasspathAppender().add(pluginDir, ".jar");
        
        return getPlugins();
    }
    
    private Path getPluginDir(Path pluginsDir) {
        
        Path ret = DirectoryUtils.getDirectory(pluginsDir.toString());
        if (ret == null) {
            throw new RuntimeException(String.format(
                    "Failed to load plugins, directory not found on: %s",
                    pluginsDir));
        }
        
        return ret;
    }
}
/**
 * Loads plug-ins from bin folders.
 */
public class BinPluginLoader extends PluginLoaderBase {
    
    @Override
    public Set<Class<? extends Plugin>> load(Path pluginsDir) {
        
        final String FILTER = "plugin-*";
        Path root = DirectoryUtils.getDirectory("inspector-gadget-src");
        Path suffix = Paths.get("bin");
        new ClasspathAppender().add(root, FILTER, suffix);
        
        return getPlugins();
    }
}
public abstract class PluginLoaderBase {
    
    public abstract Set<Class<? extends Plugin>> load(Path pluginsDir);
    
    /**
     * Finds all classes that implements {@link Plugin} using {@link ResourceFinder}
     */
    protected Set<Class<? extends Plugin>> getPlugins() {
        
        Set<Class<? extends Plugin>> ret = null;
        ResourceFinder finder = new ResourceFinder("META-INF/services/");
        try {
            ret = RuntimeUtils.cast(new HashSet<>(finder.findAllImplementations(Plugin.class)));
        } catch (Throwable thrown) {
            throw new RuntimeException(thrown);
        }
        
        return ret;
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)