Plugin system
The Problem
There are many cases where it is desirable to add a feature to openrocket which is useful in a specific context, but not really appropriate for inclusion in openrocket core. For this reason it is desirable to have some kind of plugin framework, as is found in many other applications.
In our case we refer to plugins as something that is installed onto the user's computer which is found by OR and provides additional functionality. The functionality might be simulation listeners, new motor types etc.
The plugin system has ties to dependency injection, and ideally one solution would cover both plugin discovery related items and dependency injection.
Type of plugins
A list of different use-cases for plugins which have different kinds of requirements:
- Simulation listeners: Tied to a specific simulation, possible to select from a menu and configurable via a UI. Configuration needs to be stored/loaded from the ORK file.
- Auxilliary tools: Plugin adds options to some menu on the main window. Plugin may interact or modify the rocket. (Similar to rocket optimization)
- Optimization modifiers/targets: Defining new types of optimization targets or parameters.
- Startup tips: New startup tip types. (Not implemented yet)
- New motor types
- New simulation / aerodynamic computation engines
Some of these may be singletons, others contain state and need to be instantiated properly.
Requirements
- Configuration
* Some plugins need configuration via a UI * How to distinguish what is part of core and what is a plugin?
- Automatic discovery
* Drop a JAR to the plugin folder and it will be used.
- Localisation support
- Dependency management
* Some plugins may depend on others and be able to automatically load them. This in turn requires that plugins have a consistent identifier / URI and version number. * I don't think we need plugin dependency management in the foreseeable future, and such a system may complicate things considerably. -Sampo
- Plugins implemented in other languages
* Should be possible to create a plugin that allows writing other plugins in scripting languages (e.g. JavaScript/Ruby)
- Simplified access to openrocket document / software state
* E.g. currently selected component
- Compatibility
* What happens when changes are made to the core that break the plugin? * Should we have some API versioning that defines which plugins are compatible?
Anti requirements
(Discuss)
Implementation
Options for discussion:
JSPF
http://code.google.com/p/jspf/
Pro:
- Has direct functionality for loading all JAR files from a certain directory as plugins
- Straightforward to use
- Can auto-inject other plugins as dependencies
- Annotation-based plugin definition
- Light-weight (JSPF core is 320K)
- Works on Android (though automatic discovery is not possible)
Cons:
- Plugin-orientated, not directly meant for DI (though can be used for that somewhat)
- Plugins are always instantiated by JSPF, you cannot register a plugin instance. Thus massive use of provider interfaces would be required.
- All plugins are singletons. Need to use provider interfaces.
(Provider interface means that you do not directly ask for a plugin, but instead ask for a plugin provider, which in turn returns 0-N plugin instances.)
ServiceLoader + home made
http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html
Pro:
- Simple, built into the JRE
- Already used in some parts of OR
- Works on Android
Con:
- JARs from plugin directory need to be added to classpath separately (not a bit thing with a custom ClassLoader)
- Does not do DI
- We need to write framework code for plugin management
- Plugin writers need to define the proper service META-INF (no annotations)
Reflections + home made
https://code.google.com/p/reflections/
Pro:
- Allows easily to search the classpath for annotations or interface implementations
- Full flexibility on plugin structure
Cons:
- JARs from plugin directory need to be added to classpath separately (not a bit thing with a custom ClassLoader)
- Does not do DI
- Unknown does it work on Android
- The core is lightweight, but requires (at least) Guava and Javassist, altogether ~2MB
Spring
Pro:
- Proper IoC framework, built for the purpose
- Supports AutoWiring of dependencies
- Should be possible to list beans matching to an interface/annotation
- Modular, we need core+beans (~1MB)
Cons:
- JARs from plugin directory need to be added to classpath separately (not a bit thing with a custom ClassLoader)
- Unknown does it work on Android
- Need some boilerplate code for pluging functionality
Others
Other options that have been considered but seem too immature or too heavy to use:
- JPF (http://jpf.sourceforge.net/), seems more customizable and
configurable than JSPF, but not sure do we need the extra muscle. Looks functional, but last activity was in 2007.
- OSGi (http://en.wikipedia.org/wiki/OSGi), even more heavy-weight
service discovery mechanism, similar to (or used by) Eclipse plug-in system.
- Java Module System (JSR 277), slated for inclusion to Java 7, but
wasn't. Seems pretty inactive (early draft from 2006) and seems to include language changes.
Plugin frameworks
Various plug-in frameworks exist for java, and may significantly reduce the time required to implement the plugin feature.
Directly coding the plug-in just requires the new interface to extend the Plugin interface and to add the annotations to the implementation class. This is a simple and light-weight solution which may well be quite suitable.
Of the alternative plug-in frameworks which exist, the most interesting is JSPF (http://code.google.com/p/jspf/). Here you can just say "load all JAR files from this directory as plug-ins" and "add all plug-ins from the current classpath". You can then query for plug-ins using a variety of methods. It seems very simple to use, it uses pojos and pure interfaces, and plug-ins themselves are made just by implementing the interface and adding some annotations.
JSPF also seems to work on Android, though the discovery system is somewhat dysfunctional, and you need to list all of the plug-in classes explicitly (http://code.google.com/p/jspf/wiki/RunningOnAndroid). This should be okay, we just need to have an additional implementation for Android that lists the necessary plug-ins for the basic functionality. I don't see it as a problem if some of the extensibility is missing on Android.
Other frameworks that have been considered:
- ServiceLoader class + startup scripting. Simple, built-in, but we
need to implement the plug-in system ourselves.
- JPF (http://jpf.sourceforge.net/), seems more customizable and
configurable than JSPF, but not sure do we need the extra muscle. Looks functional, but last activity was in 2007.
- OSGi (http://en.wikipedia.org/wiki/OSGi), even more heavy-weight
service discovery mechanism, similar to (or used by) Eclipse plug-in system.
- Java Module System (JSR 277), slated for inclusion to Java 7, but
wasn't. Seems pretty inactive (early draft from 2006) and seems to include language changes.
A Rough Interface Design (from a plugin writers point of view)
This section considers plugins from the point of view of "auxilliary tool" plugins, which is a minority use case. Take this section with a grain of salt.
General operation:
The name for each plugin will correspond to an item in the plugins menu. Subitems of each item in the plugins menu will be automatically generated as follows:
- About plugin : makes an about box, present for all plugins.
- for each 'enableable' modifier, a check box of the form [X] Enable ModifierName will be generated. When enabled, the enable method of the modifier will be called. This would then do what is necessary to modify OR to always run the plugin code, for example: make the simulation always load the simulation modifier.
- for each 'configurable' modifier, an item which opens the configuration window
- any additional menu items as defined in each modifier. For example, a rocket modifier might have an item which changes the selected component in some way.
Code design:
interface Plugin: - name, version, URI, date, author, licence, description - list of dependent plugin URI's and versions - list of conflicting plugin URI's and versions - list of modifiers
abstract class AbstractPlugin implements Plugin: * everything required for loading, setting menu, translations etc - getSetting(key), setSetting(key, value) : a simple mechanism for locally storing various settings - getFile(name), storeFile(file), rmfile(name) : a simple mechanism for locally storing cache files etc
static class MyPlugin extends AbstractPlugin: * the users implementation
files: messages_?.properties : translations to use, inside plugin jar
interface Modifier: - plugin this modifier is a member of - enableable boolean - enable(), disable() - configurable boolean - configurator - additional menu items and actions
abstract class RocketModifier implements Modifier: - enable(), disable() : - getSelectedComponent() : returns currently selected rocket component - onChange() : called when a component is changed * various other helper methods as necessary
static class MyRocketModifier: * the users implementation of something that works with or modifies the rocket design
abstract class SimulationModifier implements Modifier: * basically everything currently in simulation listeners, possibly simplified (do we need 3 separate interfaces?)
static class MySimulationModifier extends AbstractSimulationModifier: * the users implementation
abstract class configurator: a class which automatically generates a standardised configuration dialog box based on just a simple list of variables and selectors. Variables would be saved automatically when the OK button is clicked using plugin setSettings().