Functional specifications
Proof of concept. Decide where, which and how it should be possible to add plugins.
Also check out
PluginFramework for more ideas.
Resources
Technical interpretation
There are four areas that need to be explored:
- The frontend plugins; we need a way to let other people extend our panels/components and add views.
- Design a plugin component that adds/replaces a component and adds a tab to a tabbedpanel
- Add a generic way to add components to specific extension points (forms for example) and include the model in the event.
- The Backend plugins; the backend plugins need a way to register new Dao's for the event/task storage. A first step is already taken with the DaoFactory.
- Design a Factory that takes new types of calendars and a way to store the registrations.
- Create a dummy dao to test this
- The locale packages. We need to check out how to register new packages and get them available. Also there must be decided whether to make it possible to upload packages with translated markup files and switch between them or if we can use propety-files for all locale specific matters.
- Set up a page that switches between languages and date formats depending on a locale set by a form.
- Create a setup where additional locale files can be added in a seperate directory
- We must provide a way to install new plugins in an intuitive way. The most ideal solution would be to have a directory on the server (configurable offcourse) were you can put the extension packages, these will be eveluated on startup.
- A package manifest must be thougth off that can be used to validate the package contents (xsd and file checks)
- A package layout must defined to allow for the different types off packages to have a logical layout.
- An architecture must be designed that allows for the classes to be registered, loaded by the classloader and called on the rigth time.
Budget/Hours
| task |
estimate |
todo |
spent |
developer |
| Frontend plugin system |
Create an event system for the frontend plugins |
8 |
0 |
9 |
Ivo |
Generic extension point |
4 |
0 |
4 |
Ivo |
| Backend plugin system |
Extend the DaoFactory |
2 |
0 |
1 |
Ivo |
Dummy dao |
3 |
0 |
2 |
Ivo |
| Internationalization |
Decide on localization support ( only property files or html aswell) |
3 |
0 |
3 |
Ivo |
Set up a page that switches the locale |
3 |
0 |
2 |
Ivo |
Add external localization files and show them in the page |
5 |
0 |
4 |
Ivo |
| Plugin architecture |
Xsd for plugin manifest |
4 |
0 |
4 |
Ivo |
Directory layout |
2 |
0 |
1 |
Ivo |
| Plugin validation (xsd/file presence) |
8 |
3 |
3 |
Ivo |
| Classloading + plugin registration |
12 |
4 |
12 |
Ivo |
| Example plugin |
2 |
2 |
0 |
Ivo |
| Total |
56 |
9 |
45 |
The last functionality is moved to PluginArchitectureStory
Notes
Frontend plugin system
The plugin system is made up by five parts
- The ExtensionEvent send to the ExtensionListener by the ExtensionHandler. The ExtensionEvent consists of:
- The source ExtensionHandler List additionalResources
- A List of Ids for the replaceablecomponents the source offers.
- A List of arbitrary resources; Listeners used in the Component for example.
- A list of ExtensionPoints?
- The ExtensionHandler implemented by baseclasses for the Components. In the Poc I've implemented it in the AbstractPanel subclassed by the Panels that offer plugability. The ExtensionHandler offers.
- Methods to remove/register an ExtensionListener
- A method to replace existing Components on the source.
- A method to add new Components
- A method that can be overidden by the AbstractPanel's subclass to offer the possibility to add a new Tab
- A helper methods that offer a list of replaceable components to add to and such
- The ExtensionListener This interface is implemented by plugin classes that want to add new functionality. The Listener has two method that recieves an ExtensionEvent. One before the target panel renders and one after.
- The ExtensionPoint defines an extension point, its component and a possible model if it is inbedded in a Form for example.
- The Extension is send to the ExtensionListener to add a new component. This class holds the new component ans a reference to the extensionpoint it belongs to.
In the poc I've tested each of the three plugin possibilities (Adding/replacing components and adding a tab) and one addition to a form. The extended Panels double as the plugins in this case seeing that there isn't a way to register plugins as of yet:
The cylce is as follows:
- An ExtensionListener registers itself with the ExtensionHandler
- The ExtensionHandler's sublass is created.
- Before attaching the component, onAttach is called in the ExtensionHandler:
- The events are fired and the ExtensionListeners get the chance to add functionality before the panel renders through the implemented addExtensionsBeforeComponentSetup() method
- The subclass' own components are added
- The events are fired again but now the addExtensionsAfterComponentSetup() is called
- The Components added/replaced by the ExtensionListener are handled
- The component renders
Backend plugin system
The backend plugins can be registered in the
DaoFactory. At the moment only through the
applicationContext.xml untill a plugin architecture is devised. The registration methods check for double registration keys/invalid registrations and throw exceptions when nessecary.
Localization support
The localization support can be added by using a property file to specify all the labels and such in. In addition to this its also possible to translate entire pages, wicket will look the rigth one up according to the selected locale automatically.
Additional translations can be added through plugin files. At the moment I configured a couple of directories (Hardcoded) in which resources like html and property files can be placed.
involved files:
- WebIcalWebApplication Holds a list of additional resource lookup paths (relative from webroot or full paths). These can be be added through the applicationContext.xml or lateron through the plugin system.
- BasePage switches between Locales.
- CalendarAddEditPanel adds a Label with a StringResourceModel? witch looks up the value in the resource files.
- A hardcoded plugin folder Holds a couple of property files and translations of entire pages/components. Note that they must include the full package structure.
- applicationContext.xml configures a list of pluginfolders. Later on these should also be created by the plugin registration system.
Plugin registration system
The plugin registration must handle a couple of things.
- Configuration of the plugin paths ( configured in the applicationContext.xml )
- Unpacking the plugin packages
- Checking the plugin manifest
- Adding resource lookup paths
- Registering new classes with the classloader
- Registering the frontend plugins with the plugin handlers (AbstractBasePanel and AbstractBasePage subclasses)
- Registering the backend plugins with the DaoFactory
The components:
- PluginSystemInitializer - A class that handles all the unpacking and registering
Class Loading
To load in the classes from outside the classpath three things were needed:
- A PluginClassResolver this class is registered with wicket as the default IClassResolver and gets called upon to locate classes. This ClassResolver first tries a DefaultClassResolver before using our own implementation. Classes are registered in a HashMap containing the className and full path (eg /some/dir/Class.class).
- A PluginClassLoader which is called by the PluginClassResolver to read in the classfile and define a Class.
- A PluginClassNotFoundRuntimeException which is thrown by the PluginClassResolver with a wrapped ClassNotFoundException containing the error.
Zipfile extraction
Plugin manifest
In order to let the plugins describe themselves a plguin manifest file is needed. We set up jaxb to support the manifest (an xml file) and validate using sax. The components used:
- The xsd used to validate the manifest
- The PluginManifestReader first parses and validates the manifest according to the xsd and then unmarshalls it using jaxb
There is a
example plugin manifest included in svn
Directory structure
The standard plugin structure looks as follows:
Zipfile root:
- META-INF
- resources
- your
- package
- resource1.html
- resource2.property
- classes
- your
- package
- YourExtension.class
- AnotherExtension.class
Plugin initialisation/validation
During startup a bean is created by spring that initializes the plugin system. A couple of steps are taken:
- The manifest is validated to the schema
- The manifest is unmarshalled with jaxb
- The Package is validated
- file references are checked
- classes are checked
- Frontend/Backend plugins are checked
- Class files are checked for the right Interface implementations
The components:
Discussion
Frontend plugin system
- Some plugins migth require some more from the source components like models and listeners. What makes a good way to expose these to the plugin. For now I've added a List in the ExtensionEvent where arbitrary objects can be placed, but this is probately not the cleanest solution. Models associated specificly with an added ExtensionPoint? are handled correctly now.
- It is simple to add components in a single level this way. It is still to be tested if a component like a panel wich includes its own sub-components can be added correctly aswell.
--
IvoVanDongen - 22 Oct 2006