BioMoby Dashboard for developers

This document explains how developers can add new panels and new viewers to the Dashboard.

How to make a project-specific Dashboard

Dashboard is configurable in two ways: users can select what panels should be loaded at the beginning - and Dashboard remembers it. Or, some Dashboard characteristics can be made more permanent - such as a project-specific Dashboard logo. This section describes how to make these more permanent changes.

The best is to start by looking into src/config/dashboard directory. This directory contains default configuration (the one that is shown in the screen-shots elsewhere in this document) and has a reasonable detailed comments about various properties.

The bottom line is that you have to create (anywhere) your own directory with the same structure as src/config/dashboard has, and then start Dashboard with an additional property dashboard.config.dir. For example, assuming that I have created a new configuration in /home/senger/GCP/dashboard:

./build-dev.sh -Ddashboard.config.dir=/home/senger/GCP/dashboard dashboard
Coincidentally (), I have created such directory, indeed, and here is a screen-shots how of a new Dashboard looks like:

The mentioned configuration is available from the GCP CropForge.

What does this additional property do? It packs the given directory to a jar file build/lib/dashboard-config.jar - where it is used from by the Dashboard. Therefore, you can also have a bunch of such jar config files for various configurations, and swapping them manually will give you the same effect as using the property dashboard.config.dir.

How to add new panels

Yes, you can develop your own panels and add there into Dashboard. You can then decide whether you wish to share your panels with others, or keep it only for your project. Both is acceptable, of course.

The main message is: Please do develop your own panels!.

Here is a quick start:

  1. Your panel must implement DashboardPanel interface
  2. You need to add the full name of your implementing class into the file src/config/dashboard/META-INF/services/org.biomoby.service.dashboard.DashboardPanel.
  3. Optionally, and only if you need to get information from other panels (such as list of services, etc.), you need to read about PropertyChannel class.

Let's look at more details:

Implementing interface org.biomoby.service.dashboard.DashboardPanel

The API documentation has all the details. You can just follow them, and that's it. However, it is an advantage not to start from scratch, but to extend an AbstractPanel, a mother of all basic Dashboard panels. It has several convenient methods. For example, all text fields created through its createText() method have history values attached and remembered in user preferences.

This is a typical starting point:

public class ExamplePanel
    extends AbstractPanel {

    /*********************************************************************
     * Default constructor.
     ********************************************************************/
    public ExamplePanel() {
	super();
	panelIconFileName = "images/smallTutorial.gif";
    }
The AbstractPanel uses the global variable panelIconFileName (if it is not empty) to load an icon associated with your panel - so you do not need to do anything else regarding panel icon (meaning: you do not need to implement method getIcon() required by the DashboardPanel interface). The best place for the directory with images is a subdirectory of the directory where your panel implementation is sitting.

The most important is method getComponent - here is the core of your panel:

    public JComponent getComponent (PropertyChannel aPropertyChannel) {
 	setPropertyChannel (aPropertyChannel);
	registryModel = createRegistryModel();

	if (pComponent != null) return pComponent;
	pComponent = new JPanel();

	JLabel label = new JLabel ("This is a Tutorial Panel. It does nothing...");
	label.setFont (new Font ("Serif", Font.BOLD, 30));

	pComponent.add (label);
	return pComponent;
    }
The code above shows additional features and help of the parent panel (if you choose to use them, of course): And this is how your panel looks now in the Dashboard:

You still need to implement few other methods - but they are trivial:

    public String getName() {
	return "Example Panel";
    }

    public String getDescription() {
	return
	    "A panel that does nothing. It is used in the " +
	    "documentation for developers as a tutorial.";
    }

    public boolean loadOnlyOnDemand() {
	return true;
    }
The result of the getName(), you have already seen in the picture above. Dashboard propagates automatically the result of getDescription to the Help --> About window (below left), and to the Help --> Example Panel (below right):

The help on the right is obviously not too helpful. A better place for a richer help file - where you can use most of the HTML - is a help sub-directory under the directory where your panel implementation class is located. The file must have the same name as the last part of your class name, and .html ending. Therefore, after creating a file ExamplePanel.html, the Help --> Example Panel gives us more:

There may be few slightly more complicated things to consider when you are writing a real-value panel - especially notification about changes in other panels, and using the SwingWorker class to do time-consuming tasks without stopping responsiveness of the GUI. Please consult other existing panel, or send me an email. I may also improve this documentation in the future...

Now - when your panel is ready - you need to know...

How to add a new panel to Dashboard

Adding a panel to a Dashboard is done by using Java SPI (Service Provider Interface) mechanism, supported by toolkit from Apache commons - discovery. Its usage is straightforward:

Open file src/config/dashboard/META-INF/services/org.biomoby.service.dashboard.DashboardPanel and add there a line with a full name of the class implementing new panel. This is how the file may look like:

org.biomoby.service.dashboard.RegistryPanel
org.biomoby.service.dashboard.RegistrationPanel
org.biomoby.service.dashboard.GeneratorPanel
org.biomoby.service.dashboard.SimpleClientPanel
org.biomoby.service.dashboard.StatisticsPanel
org.biomoby.service.dashboard.DebuggingPanel
org.biomoby.service.dashboard.ExamplePanel
And that's it. Next time when you start Dashboard, your panel is there. Well, not immediately, because you wrote method loadOnlyOnDemand() as returning true. That's why you need to go to Settings --> Panel selection and check in your Example Panel.

How to add new viewers

Yes, you can develop your own viewers for specialized Biomoby data types, and add there into Dashboard.

The main message is: Please do develop your own viewers!. Or tell us about useful viewers developed by an open-source community, and somebody can add it to Dashboard.

Here is a quick start:

  1. Your viewer must implement Renderer interface
  2. You need to add the full name of your implementing class into the file src/config/dashboard/META-INF/services/org.biomoby.service.dashboard.renderers.Renderer.
  3. You need to add the names of the Biomoby data types your viewer can render, into XML file renderers.conf.xml (usually in src/main/org/biomoby/service/dashboard/renderers/ directory).

The bottom line is that viewers are specific for some Biomoby data types, or for a group of Biomoby data types. However, in order to stay within spirit of the Biomoby data type hierarchy, any viewer that claims that it can display a data type "X" must be also able to display all sub-types of "X". Dashboard provides support for that.

Another issue is that Biomoby data objects can be top-level objects, or they can be nested as members in other Biomoby objects. Should viewers automatically display also member objects? At the moment, Dashboard does not provide any general help for it (even though it does not prevent to make this decision in your viewer) - my opinion is that an object should be displayed as a whole, or a viewer should decide what to display and what to leave. An example is a viewer for images (class Base64Image) that is configurable to display just some members of other objects if they represent an image.

Let's look at more details:

Implementing interface org.biomoby.service.dashboard.renderers.Renderer

There are only five methods to implement, two of them are just one-liners. Even though it is recommended to extend your viewers from the AbstractRenderer, or from the AbstractMobyRenderer. The latter one is a class containing common utilities for renderers that process Biomoby data types - which is the usual case.

As an example, let's follow the code for Base64Image, an image viewer understanding various image formats (png, jpg, gif), all of them encoded as Base64 strings. Java has a pretty strong support for these images, so the code will be very short.

The usual constructor has two parameters: a name is used in the list of available viewers, and a file name with an icon representing this viewer. The icon is loaded and return by the getIcon() method by the parent, no need to worry about it anymore.

public class Base64Image
    extends AbstractMobyRenderer {

    public Base64Image() {
        super ("Image", "images/image.gif");
    }
The tricky (but still easy to follow) part is a method that answers the question what data this viewer can display (render):
public boolean canHandle (String criterion, Object value) {
    return (
	criterion.equals (MOBY_TYPE) &&
	isSubclassOfKnown (value.toString())
	);
}
The method should return true if this viewer can handle (render) data whose type complies with given value within given criterion. It looks obvious - but there are two issues attached to it: first is, what criteria are available, and second is, what questions (what criteria) will be this viewer asked.

The value in the method depends on the criterion. For example, if used criterion is MIME_TYPE, the value can be "text/xml". Or if the criterion is MOBY_TYPE (which is the most used case, anyway), the value is a name of a Biomoby data type, such as "b64_encoded_gif".

A suggested list of criteria is in Renderer interface.

Dashboard (concretely its ResultsPanel) calls all available viewers with the following criteria (this is important to know):

  1. It looks first for viewers dealing with the whole service response, by using criterion CLASS_NAME. The value is always java.lang.String because that is how a Biomoby response looks like. This is the place when a built-in viewer Plain Text will be always recognized. It is hardly to imagine what other viewer to plug-in here.

  2. Next, still looking for viewers dealing with the whole response, it uses criterion MIME_TYPE with the value text/xml. The built-in viewer that is always picked up here is an XML Tree. Therefore, if you plan to write, for example, an "XML Pretty Formatter", use for it the following canHandle() method:
     public boolean canHandle (String criterion, Object value) {
        return (
    	criterion.equals (MIME_TYPE) &&
    	value.toString().equals ("text/xml")
    	);
    }
    
  3. Then the ResultsPanel converts the whole XML response to the Java objects MobyPackage, MobyJob, MobySimple etc., and asks for the viewers that can handle them (still as a whole response, not the individual results, or their individual data types). Here is always picked-up the built-in viewer Simple Moby Text. Here is the place where you can plug-in viewers for Service Notes, for example, by using this canHandle() method:
    public boolean canHandle (String criterion, Object value) {
        return (
        criterion.equals (MOBY_TYPE)
        );
    }
    
    All the viewers found so far will be grouped into one pop-down menu labeled All

  4. After the previous step, everything is about individual Biomoby data types. The ResultsPanel will separate the whole response to individual jobs, and them into individual outputs. For each of them, it asks using criterion MOBY_TYPE with value being a name of the data object stored in the Simple, or a name of the first element stored in the Collection.

    You could put into your canHandle method directly the name of the data type you can render, but it is not recommended - because it would not recognize sub-types of the data type that you can render. Therefore, the recommended way is to use this (as was already shown for the image viewer above):

    public boolean canHandle (String criterion, Object value) {
        return (
    	criterion.equals (MOBY_TYPE) &&
    	isSubclassOfKnown (value.toString())
    	);
    }
    
    The method isSubclassOfKnown() is implemented in the AbstractMobyRenderer and it uses external configuration file connecting together viewer class names and Biomoby data types, in a more flexible way than to hard-code it directly in the Java code. This configuration file is described a bit later.

Now, we can continue with the core method of your viewer - the one that actually produces a component that displays things:

public JComponent getComponent (DataContainer data)
    throws MobyException {

    // deal with collections
    if (data.getData().getClass().isArray())
	return new TabCollectionRenderer (this).getComponent (data);

    // the real job
    try {
	String encoded = traverseToString (data.getData());
	byte[] imageData = Base64.decode (encoded);
        ImageIcon theImage = new ImageIcon (imageData);
	return new JScrollPane (new JLabel (theImage));
    } catch (Error e) {
	throw new MobyException
	    ("Problem in renderer '" + getName() + "'.", e);
    }
}
The input parameter contains data to be rendered. It contains also something else (metadata) but that will be shown at the end of this tutorial as an advanced feature (see the Pedigree Viewer below). You get data by calling data.getData().

An interesting method is traverseToString() (again implemented in the parent AbstractMobyRenderer). Remember that all values are always stored in Biomoby primitive types, and each primitive type (as any other member) is accessible through its known article name. In Biomoby Java objects, the article names are represented by method names. Therefore, in order to get to a primitive value, you need to know also a method name. Sometimes, however, you need to do it recursively. Consider this example:

Data type NCBI_Blast_XML_Gif
   HAS A b64_encoded_jpeg (accessible by method getMoby_hitGraph)
       which again HAS A String (accessible by get_content)
Once the method names are known (and they are, because they are in an external XML configuration file - that we will see soon), the real content can be easily extracted. And that's what the method traverseToString() does for you.

Every viewer should be prepared to deal both with Biomoby single (simple) data object, and with a collection of them. Fortunately, it is easy because there are two classes that you can pass the collection to, together with a reference to your viewer. The class BoxCollectionRenderer will call for each element of the collection back your viewer, and will arrange all components returned in a vertical box layout. The other one, TabCollectionRenderer will do the same but will arrange the component into tabbed pane. In the code example above, the is used and the result looks like this (produced by service getDragonSimpleAnnotatedImages called with an id="cho"):

The last method is for saving data to a local file:

public boolean save2File (DataContainer data, File file)
    throws MobyException {

    // deal with collections
    if (data.getData().getClass().isArray())
        return new TabCollectionRenderer (this).save2File (data, file);

    String encoded = traverseToString (data.getData());
    byte[] imageData = Base64.decode (encoded);
    return super.save2File (new DataContainer (imageData), file);
}
You can see that it has the same pattern as with the graphical component itself: it delegates most of the task either to a collection (here, it does not matter which collection, box or tab, you use), and to its parent (the real saving).

Before concluding this section, one example of a more complex viewer - a pedigree viewer for germplasm (produced by service getGermplasmPedigreeTree with the input id="50533"):

The complexity is not in the graphics itself, but in the fact that one service call returns only three generations of the pedigree tree. Further expansion of the tree requires to call the same service again, with a different input - and that is what this viewer does. It uses metadata in the data container, where the ResultPanel wisely put a PropertyChannel instance where this viewer can find what service to call with all details. Look directly in the ICISPedigreeTree.java for more details.

Configuring viewers

Each viewer that renders specific Biomoby data types, needs to tell which data types it renders, and how to extract data from them. The extraction process (using method names) was described above. Here is how and where to store the information itself.

There is a file renderers.conf.xml in the src/main/org/biomoby/service/dashboard/renderers/ directory:

<renderers>
  <renderer class="org.biomoby.service.dashboard.renderers.Base64Image">
    <moby_type name="Image_PNG" method="get_imagedata"/>
    <moby_type name="b64_encoded_gif" method="get_content"/>
    <moby_type name="b64_Encoded_PNG" method="get_content"/>
    <moby_type name="b64_encoded_jpeg" method="get_content"/>
    <moby_type name="NCBI_Blast_XML_Gif" method="getMoby_hitGraph"/>
  </renderer>
  <renderer class="org.biomoby.service.dashboard.renderers.ICISPedigreeTree">
    <moby_type name="GCP_GermplasmPedigreeTree" method="get_pedigree_xml"/>
  </renderer>
</renderers>
Add there the class of your viewer, and the name (or names) of the recognized Biomoby data types, and methods for extraction. The method names are derived from the article names , and can be found in the generated javadoc API for Java Moby objects.

Last, but not least:

How to add a new viewer to be recongized by Dashboard

Adding a viewer to a Dashboard is done (again, as was for adding panels) by using Java SPI (Service Provider Interface) mechanism, supported by toolkit from Apache commons - discovery. Its usage is straightforward:

Open file src/config/dashboard/META-INF/services/org.biomoby.service.dashboard.renderers.Renderer and add there a line with a full name of the class implementing new viewer. This is how the file may look like:

org.biomoby.service.dashboard.renderers.PlainText
org.biomoby.service.dashboard.renderers.PlainMobyText
org.biomoby.service.dashboard.renderers.Base64Image
org.biomoby.service.dashboard.renderers.XMLTreeRenderer
org.biomoby.service.dashboard.renderers.ICISPedigreeTree
And that's it. Next time when a result from a service corresponds to one of these viewers (or to more of them) they will be used and their graphics components will be shown.

How to add new registries

The registry browser panel has a list of well-known BioMoby registries - which simplifies browsing more registries. The shown registries come from a default list which is hard coded in the class RegistriesList.java. If you wish to have your registry permanently visible by all Dashboard user, edit this class and commit it back.

However, if you provide your own registry, or if you test a new registry, there is another way how to add new registry (or registries) to the list shown in Dashboard:

  1. Create and compile a class that implements Registries interface. The recommended way is to do it by extending the default implementation (class org.biomoby.registry.meta.RegistriesList), and calling its constructor that adds new registries. An example of such class is available in src/main/org/biomoby/registry/meta/ExampleList:
    package org.biomoby.registry.meta;
    public class ExampleList
        extends RegistriesList {
    
        public ExampleList() {
    	super (new Registry[] {
    	    new Registry
    	    ("Example",
    	     "http://endpoint.does.not.exist/MOBY-Central.pl",
    	     "http://endpoint.does.not.exist/MOBY/Central",
    	     "Example Land",
    	     "A contact (example.list@example-land.net)",
    	     true,
    	     "This is an example how to add your own registry.")
    	    });
        }
    }   
    
  2. Your new class will be found by SPI mechanism (as other plug-ins to Dashboard). Therefore, enter the full name of your new class to the file src/config/dashboard/META-INF/services/org.biomoby.registry.meta.Registries.

    After restarting dashboard, your new registry will appear in the registry browser panel. You can experiment first with the provided ExampleList class:

    Before After


Martin Senger
Last modified: Mon Oct 2 18:54:15 2006