Basic modularity system: plugin preloading.
parent
10376954f9
commit
107bf41a76
@ -0,0 +1,30 @@
|
||||
package net.pingex.dcf.modularity;
|
||||
|
||||
import org.apache.commons.configuration2.ImmutableConfiguration;
|
||||
|
||||
/**
|
||||
* Basic declaration of a plugin
|
||||
*/
|
||||
public interface IPlugin
|
||||
{
|
||||
/**
|
||||
* Load basic parameters, instantiate some objects, load configuration
|
||||
* @param configuration Configuration from dcf.properties
|
||||
*/
|
||||
void load(ImmutableConfiguration configuration);
|
||||
|
||||
/**
|
||||
* Power up objects, shedule tasks, and so on
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Stop everything, doesn't mean we'll unload the plugin
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Unload the plugin, clean up, etc
|
||||
*/
|
||||
void unload();
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.pingex.dcf.modularity;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation containing some details about annotated {@code IPlugin}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Plugin
|
||||
{
|
||||
/**
|
||||
* Unique plugin id.
|
||||
*/
|
||||
String id();
|
||||
|
||||
/**
|
||||
* Version of the plugin.
|
||||
*/
|
||||
String version();
|
||||
|
||||
/**
|
||||
* Plugin short description
|
||||
*/
|
||||
String description() default "No description provided.";
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package net.pingex.dcf.modularity;
|
||||
|
||||
import net.pingex.dcf.core.Configuration;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* Class that load the plugins, a very powerful thingy.
|
||||
*/
|
||||
public class PluginLoader
|
||||
{
|
||||
/**
|
||||
* Singleton instance
|
||||
*/
|
||||
private static PluginLoader INSTANCE = new PluginLoader();
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private static Logger LOGGER = LogManager.getLogger(PluginLoader.class);
|
||||
|
||||
/**
|
||||
* All plugins
|
||||
*/
|
||||
Map<String, PluginWrapper> plugins;
|
||||
|
||||
/**
|
||||
* Gives the singleton instance
|
||||
* @return The singleton instance
|
||||
*/
|
||||
public static PluginLoader getInstance()
|
||||
{
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce singleton with a private constructor.
|
||||
*/
|
||||
private PluginLoader()
|
||||
{
|
||||
plugins = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover all plugins contained in .jar files in the plugins directory.
|
||||
*/
|
||||
public void discoverPluginsDirectory()
|
||||
{
|
||||
LOGGER.debug("Discovering all IPlugin classes in plugins directory.");
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
LOGGER.error("IO error while discovering plugins.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look in a .jar file for IPlugins.
|
||||
*/
|
||||
public void loadJarFile(Path target) throws IOException
|
||||
{
|
||||
LOGGER.debug("Looking up {} for plugins.", target.getFileName());
|
||||
|
||||
// Get DCF-Plugins manifest attribute
|
||||
Attributes jarAttributes = new JarFile(target.toFile()).getManifest().getMainAttributes();
|
||||
LOGGER.trace("DCF-Plugins attribute: {}", jarAttributes.getValue("DCF-Plugins"));
|
||||
if(jarAttributes.getValue("DCF-Plugins") == null)
|
||||
{
|
||||
LOGGER.debug("Jar file has no `DCF-Plugins` attribute in its manifest. Ignoring archive.");
|
||||
return;
|
||||
}
|
||||
|
||||
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()});
|
||||
|
||||
for(String item : pluginsToLoad)
|
||||
{
|
||||
LOGGER.debug("Attempting to load {}.", item);
|
||||
|
||||
Class<?> clazz;
|
||||
try
|
||||
{
|
||||
clazz = Class.forName(item, true, classLoader);
|
||||
}
|
||||
catch(ClassNotFoundException e) // Class with that name doesn't exist.
|
||||
{
|
||||
LOGGER.debug("No class with name {} was found.", item);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.pingex.dcf.modularity;
|
||||
|
||||
/**
|
||||
* Every possible plugin states
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
UNLOADED,
|
||||
|
||||
/**
|
||||
* The loader ran {@code load()}.
|
||||
*/
|
||||
LOADED,
|
||||
|
||||
/**
|
||||
* The loader ran {@code run()}.
|
||||
*/
|
||||
RUNNING,
|
||||
|
||||
/**
|
||||
* The plugin has been running once, but has now stopped.
|
||||
*/
|
||||
STOPPED,
|
||||
|
||||
/**
|
||||
* The plugin fucked up somewhere.
|
||||
*/
|
||||
FAILED
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package net.pingex.dcf.modularity;
|
||||
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* A class wrapping a {@code IPlugin} class.
|
||||
*/
|
||||
public class PluginWrapper
|
||||
{
|
||||
/**
|
||||
* Unique ID of the plugin.
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Version of this plugin.
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* Plugin short description
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* Matching plugin class.
|
||||
*/
|
||||
private Class<? extends IPlugin> pluginClass;
|
||||
|
||||
/**
|
||||
* The associated class loader.
|
||||
*/
|
||||
private URLClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* The plugin main instance.
|
||||
*/
|
||||
private IPlugin instance;
|
||||
|
||||
/**
|
||||
* The current state of this plugin.
|
||||
*/
|
||||
private PluginState state;
|
||||
|
||||
public PluginWrapper(Class<? extends IPlugin> pluginClass, URLClassLoader classLoader)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// GETTERS
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
public Class<? extends IPlugin> getPluginClass()
|
||||
{
|
||||
return pluginClass;
|
||||
}
|
||||
|
||||
public URLClassLoader getClassLoader()
|
||||
{
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
public IPlugin getInstance()
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
public PluginState getState()
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
// SETTERS
|
||||
|
||||
public void setInstance(IPlugin instance)
|
||||
{
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public void setState(PluginState state)
|
||||
{
|
||||
this.state = state;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Contains all classes needed to load modules
|
||||
*/
|
||||
package net.pingex.dcf.modularity;
|
Reference in New Issue