From fcef6deeeb960f3bff5b56bf0f92f8b3d6371c82 Mon Sep 17 00:00:00 2001 From: Pingex Date: Thu, 21 Jul 2016 22:07:12 +0200 Subject: [PATCH] Mostly working plugin loader. --- .../dcf/DiscordCommandableFramework.java | 4 + .../net/pingex/dcf/modularity/IPlugin.java | 6 +- .../pingex/dcf/modularity/PluginLoader.java | 197 ++++++++++++++++-- .../pingex/dcf/modularity/PluginState.java | 5 - .../pingex/dcf/modularity/PluginWrapper.java | 6 +- 5 files changed, 188 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/pingex/dcf/DiscordCommandableFramework.java b/src/main/java/net/pingex/dcf/DiscordCommandableFramework.java index 1c7cbf1..ca0d400 100644 --- a/src/main/java/net/pingex/dcf/DiscordCommandableFramework.java +++ b/src/main/java/net/pingex/dcf/DiscordCommandableFramework.java @@ -32,11 +32,15 @@ public class DiscordCommandableFramework // Load plugins PluginLoader.getInstance().discoverPluginsDirectory(); + PluginLoader.getInstance().bulkLoadPlugins(); // Set up initial connection ClientBuilder builder = new ClientBuilder(); if(Configuration.isConnectionToken()) builder.withToken(Configuration.CONNECTION_TOKEN); else builder.withLogin(Configuration.CONNECTION_USERNAME, Configuration.CONNECTION_PASSWORD); GatewayConnectionsManager.getInstance().registerConnection(builder); + + // Run plugins + PluginLoader.getInstance().bulkRunPlugins(); } } diff --git a/src/main/java/net/pingex/dcf/modularity/IPlugin.java b/src/main/java/net/pingex/dcf/modularity/IPlugin.java index 1340cde..020f7c9 100644 --- a/src/main/java/net/pingex/dcf/modularity/IPlugin.java +++ b/src/main/java/net/pingex/dcf/modularity/IPlugin.java @@ -11,12 +11,12 @@ public interface IPlugin * Load basic parameters, instantiate some objects, load configuration * @param configuration Configuration from dcf.properties */ - void load(ImmutableConfiguration configuration); + void load(ImmutableConfiguration configuration) throws Throwable; /** - * Power up objects, shedule tasks, and so on + * Power up objects, schedule tasks, and so on */ - void run(); + void run() throws Throwable; /** * Stop everything, doesn't mean we'll unload the plugin diff --git a/src/main/java/net/pingex/dcf/modularity/PluginLoader.java b/src/main/java/net/pingex/dcf/modularity/PluginLoader.java index 5f892e7..34ed22b 100644 --- a/src/main/java/net/pingex/dcf/modularity/PluginLoader.java +++ b/src/main/java/net/pingex/dcf/modularity/PluginLoader.java @@ -12,8 +12,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarFile; +import java.util.stream.Collectors; /** * Class that load the plugins, a very powerful thingy. @@ -33,7 +35,7 @@ public class PluginLoader /** * All plugins */ - Map plugins; + private Map plugins; /** * Gives the singleton instance @@ -59,32 +61,27 @@ public class PluginLoader { LOGGER.debug("Discovering all IPlugin classes in plugins directory."); + int counter = 0; try { - Files.walk(Paths.get(Configuration.PLUGINS_DIR)) + Set files = Files.walk(Paths.get(Configuration.PLUGINS_DIR)) .filter(Files::isRegularFile) .filter(path -> FilenameUtils.getExtension(path.toString()).equals("jar")) - .forEach(path -> { - try - { - loadJarFile(path); - } - catch(IOException e) - { - LOGGER.catching(e); - } - }); + .collect(Collectors.toSet()); + for(Path i : files) counter += loadJarFile(i); } catch(IOException e) { LOGGER.error("IO error while discovering plugins.", e); } + + LOGGER.debug("Loaded {} plugins from {}", counter, Paths.get(Configuration.PLUGINS_DIR).toAbsolutePath()); } /** - * Look in a .jar file for IPlugins. + * Look in a .jar file for IPlugins and preload them. */ - public void loadJarFile(Path target) throws IOException + public int loadJarFile(Path target) throws IOException { LOGGER.debug("Looking up {} for plugins.", target.getFileName()); @@ -94,13 +91,14 @@ public class PluginLoader if(jarAttributes.getValue("DCF-Plugins") == null) { LOGGER.debug("Jar file has no `DCF-Plugins` attribute in its manifest. Ignoring archive."); - return; + return 0; } String[] pluginsToLoad = jarAttributes.getValue("DCF-Plugins").split(" "); LOGGER.debug("Manifest returned {} plugins to load.", pluginsToLoad.length); URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{target.toUri().toURL()}); + int counter = 0; for(String item : pluginsToLoad) { LOGGER.debug("Attempting to load {}.", item); @@ -113,18 +111,179 @@ public class PluginLoader catch(ClassNotFoundException e) // Class with that name doesn't exist. { LOGGER.debug("No class with name {} was found.", item); - return; + continue; } // Class doesn't implement IPlugin, or doesn't have @Plugin annotation. if(!clazz.isAnnotationPresent(Plugin.class) || !IPlugin.class.isAssignableFrom(clazz)) { LOGGER.debug("Class does not implement IPlugin or doesn't gave @Plugin annotation."); - return; + continue; + } + + try + { + plugins.put(clazz.getAnnotation(Plugin.class).id(), new PluginWrapper(clazz.asSubclass(IPlugin.class), classLoader)); + LOGGER.debug("Plugin {} {} preloaded.", clazz.getAnnotation(Plugin.class).id(), clazz.getAnnotation(Plugin.class).version()); + counter++; } + catch(IllegalAccessException | InstantiationException e) + { + LOGGER.warn("Plugin " + clazz.getAnnotation(Plugin.class).id() + " failed to preload.", e); + } + } + + LOGGER.debug("Loaded up {} plugins from {}.", counter, target.getFileName()); + return counter; + } + + /** + * Load all plugins that aren't. + */ + public void bulkLoadPlugins() + { + LOGGER.info("Bulk loading plugins."); + plugins.entrySet().stream() + .filter(i -> i.getValue().getState().equals(PluginState.UNLOADED)) + .forEach(i -> loadPlugin(i.getKey())); + } + + /** + * Load a plugin. + * @param id The unique ID of the plugin. + */ + public void loadPlugin(String id) + { + // Check for plugin existence. + if(!plugins.containsKey(id)) + { + LOGGER.debug("Plugin with ID {} doesn't exist.", id); + return; + } + + LOGGER.debug("Loading plugin {}.", id); + PluginWrapper plugin = plugins.get(id); + + // Check if plugin state is UNLOADED + if(!plugin.getState().equals(PluginState.UNLOADED)) + { + LOGGER.debug("Current plugin state does not allow it to be loaded."); + return; + } + + try + { + plugin.getInstance().load(Configuration.getStore()); + plugin.setState(PluginState.LOADED); + LOGGER.info("Loaded plugin {}.", id); + } + catch(Throwable throwable) + { + LOGGER.warn("Plugin " + id + " failed to load. Entering FAILED state.", throwable); + plugin.setState(PluginState.FAILED); + } + } + + /** + * Run all plugins that aren't + */ + public void bulkRunPlugins() + { + LOGGER.info("Bulk running plugins."); + plugins.entrySet().stream() + .filter(i -> i.getValue().getState().equals(PluginState.LOADED) || i.getValue().getState().equals(PluginState.STOPPED)) + .forEach(i -> runPlugin(i.getKey())); + } + + /** + * Run a plugin. + * @param id The unique ID of the plugin. + */ + public void runPlugin(String id) + { + // Check for plugin existence. + if(!plugins.containsKey(id)) + { + LOGGER.debug("Plugin with ID {} doesn't exist.", id); + return; + } + + LOGGER.debug("Attempting to run plugin {}.", id); + PluginWrapper plugin = plugins.get(id); + + // Check if plugin state is LOADED or STOPPED + if(!plugin.getState().equals(PluginState.LOADED) && !plugin.getState().equals(PluginState.STOPPED)) + { + LOGGER.debug("Current plugin state does not allow it to run."); + return; + } + + try + { + plugin.getInstance().run(); + LOGGER.info("Plugin {} is now running. (last state: {})", id, plugin.getState()); + plugin.setState(PluginState.RUNNING); + } + catch(Throwable throwable) + { + LOGGER.warn("Plugin " + id + " failed to run. Entering FAILED state. (last state: " + plugin.getState() + ")", throwable); + plugin.setState(PluginState.FAILED); + } + } + + /** + * Stop a plugin from running. + * @param id The unique ID of the plugin. + */ + public void stopPlugin(String id) + { + // Check for plugin existence. + if(!plugins.containsKey(id)) + { + LOGGER.debug("Plugin with ID {} doesn't exist.", id); + return; + } - LOGGER.debug("Plugin {} {} preloaded.", clazz.getAnnotation(Plugin.class).id(), clazz.getAnnotation(Plugin.class).version()); - plugins.put(clazz.getAnnotation(Plugin.class).id(), new PluginWrapper(clazz.asSubclass(IPlugin.class), classLoader)); + LOGGER.debug("Stopping plugin {}.", id); + PluginWrapper plugin = plugins.get(id); + + // Check if plugin state is RUNNING + if(!plugin.getState().equals(PluginState.RUNNING)) + { + LOGGER.debug("Current plugin state does not allow it to stop."); + return; } + + plugin.getInstance().stop(); + LOGGER.info("Stopped plugin {}.", id); + plugin.setState(PluginState.STOPPED); + } + + /** + * Unload a plugin. + * @param id The unique ID of the plugin. + */ + public void unloadPlugin(String id) + { + // Check for plugin existence. + if(!plugins.containsKey(id)) + { + LOGGER.debug("Plugin with ID {} doesn't exist.", id); + return; + } + + LOGGER.debug("Unloading plugin {}.", id); + PluginWrapper plugin = plugins.get(id); + + // Check if plugin state is STOPPED + if(!plugin.getState().equals(PluginState.STOPPED)) + { + LOGGER.debug("Current plugin state does not allow it to be unloaded."); + return; + } + + plugin.getInstance().unload(); + LOGGER.info("Unloaded plugin {}.", id); + plugin.setState(PluginState.UNLOADED); } } \ No newline at end of file diff --git a/src/main/java/net/pingex/dcf/modularity/PluginState.java b/src/main/java/net/pingex/dcf/modularity/PluginState.java index 2b441d9..d50660c 100644 --- a/src/main/java/net/pingex/dcf/modularity/PluginState.java +++ b/src/main/java/net/pingex/dcf/modularity/PluginState.java @@ -5,11 +5,6 @@ package net.pingex.dcf.modularity; */ public enum PluginState { - /** - * The plugin does not have an associated {@code IPlugin} instance. - */ - VOID, - /** * The plugin is unloaded, ie. the loader did not run {@code load()}, or executed {@code unload()} on the associated instance. */ diff --git a/src/main/java/net/pingex/dcf/modularity/PluginWrapper.java b/src/main/java/net/pingex/dcf/modularity/PluginWrapper.java index c5a3ba9..788229f 100644 --- a/src/main/java/net/pingex/dcf/modularity/PluginWrapper.java +++ b/src/main/java/net/pingex/dcf/modularity/PluginWrapper.java @@ -42,15 +42,15 @@ public class PluginWrapper */ private PluginState state; - public PluginWrapper(Class pluginClass, URLClassLoader classLoader) + public PluginWrapper(Class pluginClass, URLClassLoader classLoader) throws IllegalAccessException, InstantiationException { this.id = pluginClass.getAnnotation(Plugin.class).id(); this.version = pluginClass.getAnnotation(Plugin.class).version(); this.description = pluginClass.getAnnotation(Plugin.class).description(); this.pluginClass = pluginClass; this.classLoader = classLoader; - this.instance = null; - this.state = PluginState.VOID; + this.instance = pluginClass.newInstance(); + this.state = PluginState.UNLOADED; } // GETTERS