Framework
This document outlines the operation of the geWorkbench framework. This information could be of use to plugin developers who desire a deeper understanding of geWorkbench.
Contents
Component Registry
The central class to the geWorkbench framework is org.geworkbench.engine.management.ComponentRegistry. This class is responsible for maintaining look-up tables of all active plugins (known herein as components). It also keeps track of the publish/subscribe behavior of the plugins. The ComponentRegistry makes use of CGLIB (http://cglib.sourceforge.net) to intercept methods and apply framework functionality. This will be covered in detail in the following sections.
Note the following definitions:
- Plugin
- The implementation of a geWorkbench add-on.
- Component
- An instance of a plugin active in geWorkbench. Note that it is possible for there to be zero or more components for each plugin. Typically, however, there will be just zero or one components for each plugin.
Component Instantiation
An XML configuration file specifies which plugins should be instantiated in to components. This is parsed by a set of Apache Digester directives (http://jakarta.apache.org/commons/digester). Also parsed is a plugin-specific configuration file. This file has the same name and path as the component class file, but it has a .cwb.xml extension. This file includes some metadata about the plugin, such as the location of its help files, the area of the visual interface that the component should be installed, and what menu items it responds to. This metadata is stored in a class called org.geworkbench.engine.config.PluginDescriptor. The configuration engine creates the PluginDescriptor object and passes it on to the ComponentRegistry. There, the component is instantiated and a CGLIB wrapper is applied. The ComponentRegistry stores the component and its associated descriptor for later use. It passes back the instantiated component to the configuration engine. In the case that the component is an instance of a visual plugin, the configuration engine ensures that the component is added to the appropriate visual area.
Component Resources
Plugins consist of Java class files, optional library files and optional resource files. These files are created independently by groups of developers. It is possible that some files of one plugin could conflict with some file of another plugin. This is particularly true of library files. For example, one plugin may use a more recent version of a popular library than another plugin. To prevent such unwanted dependency issues, components are instantiated with their own classloader. This classloader resolves classes and resources in the following order:
- The classes directory of the plugin.
- The lib directory of the plugin.
- Defers to the main classloader of geWorkbench.
This resolution scheme is similar to web application containers (such as Tomcat). The org.geworkbench.engine.management.ComponentResource class holds the classloader data for each component, and the link between components and ComponentResource instances is maintained by the ComponentRegistry.
Publish/Subscribe
Components communicate via a publish/subscribe model. Plugins will include annotated methods that indicate their publish or subscribe behavior. Below is the method signature for a typical subscribe method:
@Subscribe public void receive(ProjectEvent event, Object source) { ... }
Note that subscribe methods must take two parameters, and the second parameter must be of type java.lang.Object. Below is the method signature for a typical publish method:
@Publish public ProjectEvent publishProjectEvent() { ... }
Upon instantiation of a component, the ComponentRegistry finds all methods that are annotated with org.geworkbench.engine.management.Subscribe. It maintains a data structure that maps each subscribe method to the type of its first parameter. For example, the above example would link the receive method to the org.geworkbench.events.ProjectEvent type. Furthermore, this link is polymorphic; that is, a type that extends ProjectEvent will also be linked to this method.
When a publish method is called in a component, the ComponentRegistry will intercept the call (via CGLIB). It will allow the method call to proceed normally. The type of the returned value is then examined. Each component that has a subscribe method that is linked to a type that is assignable from the published type will have that method called, with the publish object as the first parameter, and the component that published the object as the second parameter.
Synchronization Models
Normally, the above publish/subscribe sequence is processed serially by the same thread that called the publish method. In many situations, this behavior is acceptable. However, occasionally special handling is required for the subscribe methods. For example, a subscribe method for a visual plugin may need to be run on the Swing event-dispatching thread. Another example would be a subscribe method that results in a fairly long-running computation. It maybe desirable that this subscribe method is called in the background rather than on the publishing thread.
The author of the plugin can specify a synchronization model as a parameter to the subscribe method in this case. A syncrhonization model is any class that implements the org.geworkbench.engine.management.SynchModel interface. Four implementations are provided with geWorkbench (all in the org.geworkbench.engine.management package):
- Synchronous - The default, runs the subscribe method synchronously on the publishing thread.
- Asynchronous - Runs the subscribe method in the background on one thread from a thread pool.
- Overflow - Similar to Asynchronous, this model runs subscribe methods on a background thread. However, if multiple subscribe events for a given component and method accrue during the processing of a subscribe event for that component and method, then all but the last of these events will be dropped. This is very useful in situations where a component may receive many rapid updates to a state from other components, but each update contains complete information about the state at that time, rendering previous state updates obsolete.
- SwingModel - This ensures that the subscribe method is run on the Swing event-dispatching thread.
Here is an example of how to specify a synchronization model for a subscribe method:
@Subscribe(Asynchronous.class) public void receive(ProjectEvent event, Object source) { ... }
The Project Panel and Data Sets
While the Project Panel is itself a geWorkbench plugin, it has some special properties that merit a mention. The Project Panel need not be the only source of project selection events, but this is typically the case. In this way, it becomes the central point of organization for the application.
All entries in the Project Panel below the level of Project nodes are data sets that implement the interface org.geworkbench.bison.datastructure.biocollections.DSDataSet. These nodes are created in one of two ways. The first is that the Project Panel was used to load a data set from disk or some other network resource. In this case, a non-visual plugin extending org.geworkbench.engine.parsers.FileFormat is used to parse the data and construct a data set. The second is that the Project Panel receives a org.geworkbench.events.ProjectNodeAddedEvent from another component. This events are usually generated as the result of an analysis by the publishing component.
The conceppt of data sets is central to geWorkbench development. Many plugins can be characterized by the types of data sets they process and the types of data sets that they produce.
Visual Plugins and GUIFramework
Many plugins have a visual representation. This visual intention is advertised to the framework by the plugin implementing org.geworkbench.engine.config.VisualPlugin. Some plugins, such as file parsers and analyses, do not require a visual representation, and so do not implement this interface.
Those that implement VisualPlugin will have extra directives included in the configuration file to indicate how they should be laid out in the interface. These directives are passed on from the configuration engine to the rendering framework. The rendering framework is itself configurable, and is a class that extends org.geworkbench.engine.config.GUIFramework. The default and most full-featured implementation of GUIFramework is org.geworkbench.engine.skin.Skin. This implementation supports four layout areas (Project, Selection, Command and Visual). It also supports an annotation system for determining which visual components to display based on the selected data set in the Project Panel.
Visual plugins may annotate their class definition with org.geworkbench.engine.management.AcceptTypes. If the annotation is omitted, then the visual plugin will always be rendered by Skin. The annotation takes an array of class objects, each of which must be assignable to DSDataSet. For example, here is how the Color Mosaic Panel is annotated:
@AcceptTypes({DSMicroarraySet.class, DSSignificanceResultSet.class}) public class ColorMosaicPanel implements VisualPlugin ...
When the Project Panel generates a ProjectEvent resulting from a data set node being selected, the type of the data set is examined by Skin. Each component that has a type in its @AcceptTypes annotation that is assignable from the selected data set type is made visible in the interface. Additionally, all unannotated components are also made visible. Annotated components without a type assignable from the selected d ata set are hidden. This way, the user is only presented with relevant components upon selection of a data set node.