Plugin Development Guide

OSGI

Since version 1.1, d2 runs in an OSGI environment. A d2 plugin is nothing more than an OSGI bundle.

An OSGI bundle is a package (not in the programmatic acception of the term) that encapsulates classes, resources, native files, etc. It can do nothing alone and is intended to be deployed inside an OSGI environment. They are basically mere jar files with a special MANIFEST.MF file which describes its properties, such as its identifier, classpath, dependencies, etc. The structure of an OSGI bundle is typically such as described in the example below:

/org.decisiondeck.mybundle_1.0.0.jar
  |
  +-- org
  |    |
  |    +-- decisiondeck
  |           |
  |           +-- mybundle
  |           |     |
  |           |     +-- Class1.class
  |           |     +-- Class2.class
  |           |     +-- internal
  |           |          |
  |           |          +-- Activator.class
  |           +-- resources
  |                 |
  |                 +-- mybundle.properties
  |                 +-- messages.properties
  +-- META-INF
       |
       +-- MANIFEST.MF

OSGI is a framework that enables service oriented platforms. It provides a number of infrastructure services (it takes care of bundle isolation, integrates bundle versionning, deals with security concerns, etc.) and allows to dynamically plug and unplug services (e.g. a MCDA computation service). [OSGI] provides more in-depth information about OSGI.

Importing the d2 bundles into your workspace

You may find useful to use Eclipse’s Working Set feature to augment your workspace readibility (see Image below).

_images/working-set.png

Using working sets

If you want to work against CVS head you can use the provided psf (Project Set Files) to checkout the CVS modules (File > Import > Team > Team Project Set):

  • d2.psf will checkout all 1.1 modules (excluding Eclipse tools)

  • d2-tools.psf will only checkout the d2 Eclipse plugin (which provides a “new d2 project” wizard creation)

    Alternatively you may want to work against the binary bundles. For that you need to have already downloaded and unzipped the distribution. Open the “Import” wizard and select “Plug-in Development” > “Plugins and fragments” ; unselect “Target platform” and browse for the plugins directory under the d2 distribution folder.

Creating a d2 plugin

To facilitate the creation of a new plugin, an Eclipse plugin has been developed which integrates a new project wizard into Eclipse. To install this plugin, create a new Eclipse update site pointing to http://decision-deck.sourceforge.net/tools/ and install the 1.1 version of the starter kit plugin. You may need to restart Eclipse.

The following assumes that you’ve checked out the d2 bundles from CVS. You may want to organize your workspace by enabling Working Sets and putting all d2 bundles (but the ones you’re working on - i.e. your d2 plugin) under a d2 Working Set.

Once the plugin is installed, you can run the wizard. It consists of a single page, which asks the user for the new d2 plugin short name (e.g. Iris, Rubis, WeightedSum, etc.). Enter the name and press finish. A new project is created on your behalf, which is recognized by Eclipse as a PDE project (aka plugin project).

The created plugin provides a complete working structure example, and covers the typical usecase, from the model layer to the ui layer. The next section will uncover the plugin internals.

If you want to create a new bundle from scratch, you should use the “New > Plugin” wizard provided by Eclipse and create all the required elements manually.

Inside the plugin

A typical d2 plugin will contribute to the UI and will refine the model. It is usually composed of a few layers :

  • Model layer
  • Data access layer
  • Service layer
  • UI layer

Persistence is achieved through Hibernate and mappings are declared through annotations only.

The Model

The model layer exposes the entities which refine the global d2 model. The union of all the entities provided by the various plugins activated for a given project forms the model for that project.

The entities composing the model layer are nothing but mere POJO augmented with JPA annotations [JPA].

A Typical entity will be similar to this:

package org.decisiondeck.myplugin.model;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;
import javax.persistence.Column;
import javax.persistence.CascadeType;
import javax.persistence.FetchType;
import javax.persistence.Transient;

import org.decisiondeck.model.base.AbstractEntity;
import org.decisiondeck.model.core.Criterion;

@Entity
@Table(name="MyPluginID_MyPersistentBean")
public class MyPersistentBean extends AbstractEntity
{
    @ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.EAGER)
    @JoinColumn(name="Criterion_ID", unique=false, nullable=false, insertable=true, updatable=true)
    private Criterion criterion;

    @Column(name="Weight", unique=false, nullable=true, insertable=true, updatable=true)
    private Float weight;

    @Transient
    private int foo;

    public Criterion getCriterion()
    {
        return criterion;
    }

    public void setCriterion(Criterion criterion)
    {
        this.criterion = criterion;
    }

    public Float getWeight()
    {
        return weight;
    }

    public void setWeight(Float weight)
    {
        this.weight = weight;
    }

}

We declare here that the POJO MyPersistentBean is a persitent entity (@Entity annotation) that maps to “MyPluginID_MyPersistentBean” table (@Table annotation). Its properties criterion (@ManyToOne annotation) and weight (@Column annotation) are persistent but not the foo attribute (@Transient annotation). Also we distingate between simple properties (of primitive type) which map to database primitives (Column) and relationships that map to database relations (ManyToOne, OneToMany, OneToOne and ManyToMany annotations). Please refer to [JPA] for more information.

It is also required to register those persistent beans within the d2 environment. This is done declaratively through the plugin.xml descriptor. You need to add a “refines” element such as the one described below:

<refines>
        <entityProvider>
                <id>org.decisiondeck.myplugin</id>
                <entityNames>
                        <entityName>org.decisiondeck.myplugin.model.MyPersistentBean</entityName>
                </entityNames>
        </entityProvider>
</refines>

This tells the plugin engine that a new persistent class should be registered with Hibernate. In return Hibernate needs to be able to access this class. For that purpose you need to add the following entry in your bundle MANIFEST:

Eclipse-RegisterBuddy: org.decisiondeck.dao.core,
 org.decisiondeck.lib.hibernate

This will make your classes available to both org.decisiondeck.dao.core and org.decisiondeck.lib.hibernate bundles.

The data access layer

As said before data access is achieved through Hibernate. Thus you don’t have to deal with JDBC directly (except for the most extreme cases), but you will rather converse with the database throughout Hibernate API. You need to create a DAO (Data Access Object) class that will encapsulate the calls to Hibernate. A generic dao which covers the selection, removal and update of entities, is provided by the org.decisiondeck.dao.core bundle.

Two cases may arise : either your persistent bean depends on the project, or it doesn’t. In the first case your DAO should inherit from ProjectEntityDao, in the second case it should inherit from GenericDao. In both case, you just have to handle specific situations since all generic persistence scenarios are already dealt with in the superclass.

For instance suppose you want to retrieve all the MyPersistentBean instances whose weight property is greater than a given value, you would create such a DAO:

public class MyPersistentBeanDao extends ProjectEntityDao<MyPersistentBean>
{
    public MyPersisteBeanDao()
    {
        //required
        setManagedClass(MyPersistentBean.class);
    }

    public List<MyPersistentBean> selectHighValues(Float f, boolean strictly)
    {
        Criteria criteria = getSession().createCriteria(MyPersistentBean.class);

        if ( strictly )
        {
                criteria = criteria.add(Restrictions.gt("weight", f));
        }
        else
        {
                criteria = criteria.add(Restrictions.ge("weight", f));
        }

        //criteria.list returns a non modifiable list
        return new ArrayList<MyPersistentBean>(criteria.list());
    }
}

You could also request a generic dao:

IDao<MyPersistentBean> dao = DaoRegistry.getInstance().createGenericDao(MyPersistentBean.class);

   or, if MyPersistentBean depends on the project :

IDao<MyPersistentBean> dao = DaoRegistry.getInstance().createProjectEntityDao(MyPersistentBean.class);

You may want to read the Hibernate documentation [HIB] to learn more about the various ways to query the database.

The service layer

This layer encapsulates the various entity managers. In d2 context these managers often don’t do much more than delegating to the DAO. For instance:

public class MyPluginService
{
    public void save(MyPersistentBean myBean)
    {
        try
        {
            DaoRegistry.getInstance().getDao(MyPersistentBean.class).save(myBean);
            ModelEventManager.getInstance().fireEntityEvent(ModelEventType.UPDATED, myBean);
        }
        catch (DaoException e)
        {
            throw new ServiceException("Unable to save entity", e);
        }
    }
}

Event management

Events are triggered whenever an entity is created, modified, deleted, and about to be persisted or removed. If you need to be notified when such an event occur you simply register an IModelEventListener as shown below:

ModelEventManager.getInstance().registerListener(
        new IModelEventListener()
        {
            public Class<?>[] getEntityClasses()
            {
                return new Class[] { MyPersistentBean.class };
            }
            public void handleEntityEvent(ModelEvent event)
            {
                ...
            }
        }
);

You will be notified of any persistent changes of instances of MyPersistentBean (and only instances of that class). The parameter event passed to handleEntityEvent allows to know which entities have changed and the type of the event triggered.

ModelEventType type = event.getType();
List<AbstractEntity> changedEntities = event.getEntities();
...

A pre removal (resp. pre persist) event is triggered whenever an entity is about to be deleted (resp. persisted). When you receive such an event you can either raise a veto, or take certain actions to ensure that your model is consistent. For instance:

ModelEventManager.getInstance().registerListener(
         new IModelEventListener()
         {
             public Class<?>[] getEntityClasses()
             {
                 return new Class[] { Criterion.class } ;
             }

             public void handleEntityEvent(ModelEvent event)
             {
                 if ( event.getType() == ModelEventType.PREREMOVE )
                 {
                     if ( preventCriterionRemoval() )
                     {
                         throw new VetoException("[MyPlugin] Reason of the veto");
                     }
                     else
                     {
                         //delete or update relevant entities (or do nothing)
                         ...
                     }
                 }
             }
         }
     );
 }

The UI Layer : Configuration

Plugin configuration implies editing two files:

  • /META-INF/MANIFEST.MF

  • /plugin.xml

    In the manifest file, add org.decisiondeck.view (eventually org.decisiondeck.view.core as well) to be able to access the core classes.

    You can extend the UI through a few extension points (only similar to Eclipse ones : we don’t use Eclipse extension registry). If you want to plug a new Navigation item (in the navigation panel), add the following to your plugin.xml:

    <services>
            <service>
                    <extend>org.decisiondeck.NavigationMenuItem</extend>
                    <id>ITEM ID</id>
                    <className>YOUR CLASS NAME HERE</className>
                    <roles>
                            <role>coordinator</role>
                            <role>decision maker</role>
                    </roles>
            </service>
    </services>
    

    The registered class must implement org.decisiondeck.view.navigation.INavigationMenuItem interface. you may want to inherit from the abstract class org.decisiondeck.view.navigation.AbstractNavigationMenuItem.

    The roles list the authorized roles for the items. Roles not listed there won’t see the navigation item. For now new roles can’t be registered and only three roles exist:

    • coordinator
    • decision maker
    • evaluator

Running the platform

Open the “Run dialog”. Create a new “OSGI Framework” configuration, call, f.i., d2. In the “Bundle” tab, check the “Workspace” plugins checkbox and uncheck the “Target platform” checkbox. Then click the “Add required bundles”.

_images/run-plugins.png

In the “Arguments” tab, remove the -console program argument, and add “-Dshow_sql=true” VM argument. This last (optional) argument allows to monitor the sql query as sent by Hibernate. Run dialog, Arguments tab

_images/run-args.png

No script is provided to build a single plugin, as Eclipse already provides all you need for that. When you’re ready to release your release, just follow the procedure outlined below:

  • Right-click your plugin project, select “Export...”

  • Choose “Deployable plug-ins and fragments” under “Plug-in development”, click Next

  • Fill the directoty you want your plugin to be created in (actually a ‘plugins’folder will be created in that folder)

  • In the option tab:
    • Check “Package plugin as individual jars”
    • Check “Include source code” if you want to bundle the source code with the deliverable
  • Press Finish

    To test the artifact, just drop the created file into your decision-deck plugins folder (restart decisiondekc if necessary).