Some drastic changes have been introduced in Version 1.1. As a result, although we tried to minimize their impact, D2 1.1 is not backward compatible with the 1.0.x frame. This document will detail the changes and the rationale behind them, and will try to address the migration issue. Hopefully you will see that making your plugin compatible with the 1.1 version is pretty straightforward, and creating a whole new plugin has become much easier.
In previous versions each project was stored in its own database. A single database is now used to store all the projects. This allows simpler project management, and using a single database is more intuitive. In order to ease the migration and allow the developer to be oblivious of the project we introduced an abstract entity: org.decisiondeck.model.core.AbstractProjectEntity which references a project. Top level elements in version 1.0.x should now inherit from this class. We tried to make this change as unintrusive as possible (see below). This change induced several side effects:
Hibernate is now used to query the database. The custom sql layer was not sufficient to handle the complex mapping scenarios that were bound to occur, and still implied a lot of redundant custom code. This layer has been dropped in favor of Hibernate. Besides making the whole persistence mechanism less intrusive (though please note that there's no such thing as "transparent persistence"), it allows to greatly simplify the persistence layer(s). In the simplest case the plugin developer has no persistence code to write : s/he can just request a generic DAO from the DaoRegistry (DaoFactory in 1.0.x) which already handles generic operations, such as save, delete, select, find, count. For instance:
MyPersistentBean pojo = new MyPersistentBean(); pojo.setProperty(...) GenericDao<MyPersistentBean> dao = DaoRegistry.getInstance().createGenericDao(MyPersistentBean.class); dao.save(pojo);
If your bean is a "top level" one (i.e. depends on the project and inherits from AbstractProjectEntity), you should request a generic ProjectEntityDao :
ProjectEntityDao<MyPersistentBean> dao = DaoRegistry.getInstance().createProjectEntityDao(MyPersistentBean.class); ... dao.save(pojo);
This DAO automatically handles the project relationship (i.e. if the project attribute is not set in your pojo, it will be automatically set) : you shouldn't have to care about it.
AlternativeEvaluation has been dropped. this association classes hadn't real advantages - it only made things more complicate. The relationship between Alternative and Evaluation is now handled collection manipulation. See for instance EvaluationService which exposes the following operations :
Evaluation getEvaluation(User, AbstractAlternative, Criterion); int getEvaluationCount(AbstractAlternative); int getEvaluationCount(Criterion); int getEvaluationCount(User); List<Evaluation> getEvaluations(User);
Relational constraints are more strongly enforced, and you should more cautious when deleting/updating entities.
In version 1.0.x you had to declare the relational structure your plugin supported through xml configuration. The plugin manager created the structure for you. The database schema update is now taken care of by Hibernate. So you don't have to declare your schema anymore in the plugin.xml file. However you have to declare the mapping in your bean through JPA annotations (see below).
Actually the plugin descriptor is drastically simpler and now mainly deals with only functionnal configuration. The steps involved in a plugin setup (at development time) and construction are outlined below.
Core D2 entities are now treated as any entities (almost). And the database structure is created dynamically if needed. That means that there's no further need to configure the database prior to the platform execution. The only prerequisite is to have a running Mysql server and aty least a database (even empty). There still remains a dependency on MySql which should be get rid of by introducing another level of abstraction (something like a connectivity provider), but that's another story.
Hibernate introduction raised a number of latent problems: Hibernate needs to know about your custom classes. Other said, your classes must be in Hibernate classpath. Although it would have been possible to achieve with the previous classloading mechanism, it would have required a lot of work. Decision Deck now runs against an OSGI framework implementation (Equinox) and dependency managements is not anymore taken care of by the simple plugin engine.
This also allows not only to simplify the plugin descriptor but also to simplify the development process : no more struggle with build files nor custom Eclipse builders. However not all OSGI features have been used yet, plugin services are still handled through the plugin engine (as opposed to OSGI services, or Eclipse extension points). A plugin is contributed to the D2 platform as an OSGI bundle. To make its contributions available the bundle should be started. To minimize the changes in this version, and also to maximize final user obliviousness plugins are automatically started when D2 starts. This is acceptable since the number of plugins, as a matter of fact, will not be excessive.
To register a plugin with the plugin engine when the bundle starts you just need to make your activator inherits from AbstractPluginActivator, f.i.:
public class WeightedSumActivator extends AbstractPluginActivator { }
That's it. Please note that this Activator is a very technical thing, and that plugin initialization logic still remains in the Plugin instance (the exact same one that you used previously - declared in plugin.xml).
In the refactoring process, projects have been renamed to match an eclipse plugins like notation. For instance d2-view becomes org.decisiondeck.view, d2-dao becomes org.decisiondeck.dao, and so on.. Core dao implementations have been in their own project: org.decisiondeck.dao.core. As "new" project, their version restarts at 1.0.0 - which is the bundle version. OSGI allows fine-grain dependencies management and as a consequence, it is possible to run two concurrent versions of the same bundle (say, org.decisiondeck.dao.core version 1.0.0 and org.decisiondeck.dao.core version 1.0.1) in the same execution process. So to prevent future conflicts in your plugin, you should better
Due to a number of reason, development lifecycle has been reported to ba a pain. It was a bit scattered, and the learning curve was quite dreadful, forcing a huge amount of prerequisites on the developer. To remedy this issue, we took a different approach. You can now get started as soon as you import existing projects into your Eclipse workspace. Maven is may not anymore involved in the building process except for building the documentation site (actually the projects have been yet re-mavenized), and the CVS structure has been flattened : all D2 1.1 bundle are located under CVS module 1.1.
A new isLongRunning() operation has been added to IKernelAction interface, that simply returns a boolean. If true, then the action is run in a new-spawned thread to prevent the platform to freeze.
To remain consistent with the aforementionned changes concerning project storage, the action labelled "Save a copy" has been renamed to "Backup". Also the menu item labelled as "New" as been moved under "Open" so that the submenu is never empty (as it is the case when running the platform against an empty database).
This section provides a cost-to-coast example of migrating a plugin to the 1.1 architecture. It is assumed that you develop within Eclipse.
A few steps must be performed to migrate your plugin to D2 1.1. To avoid potential collisions, you may want to work in a fresh new workspace.
If your project doesn't follow the reverse dns naming convention, please rename it for the sake of uniformity (although this isn't required). For instance : org.decisiondeck.methods.iris.
Please refer to the plugin guide to read more about the options that are offered to you.
This is achieved through Eclipse import feature (File/Import.. > Existing projects into workspace). Be sure to check the "Copy project into workspace" option.
Open your project Java Build path property page and remove all project references and referenced libraries (either external or internal). Obviously, when you close the dialog the project won't build : don't worry, this is expected.
In the project contextual menu open the "PDE Tools" submenu and select "Convert projects to plug-in Projects...". Select your project and press Finish. Eclipse updates your project metadata (.project file) and creates a few files : META-INF/MANIFEST.MF and build.properties.
Open the MANIFEST file. In the "Overview" tab, check that the "ID" is correct, and provide a better "Name" for your plugin.
Open the "Dependencies" tab. For each project that were previously referenced in the Java Build Path, add a "Required Plug-in" entry (button "Add..")
Open the "Runtime" tab. If you used external libraries, and those libraries are not already provided by antoher plugin (see for instance, org.decisiondeck.lib.*) and you don't want to share those libraries, create a lib (for instance) folder at the root of the project, and copy your libraries there ; then add a new entry in the "Classpath" list (bottom right of the page) using the "Add..." button.
In the same tab, in the "Exported packages" section, select all the packages you want to expose to the outside world (only the selected packages will be accessible by other plugins).
Last, if your plugin contributes to the model (by providing new entities) - and it will probably do, you need to do some manual tweakings. Open the MANIFEST.MF tab. Add the following lines at the end of the file:
Eclipse-RegisterBuddy: org.decisiondeck.dao.core, org.decisiondeck.lib.hibernate
This makes your plugin classes (see "Exported classes" above) visible to Hibernate and Dao Core plugins. This is required because of the dynamic classloading model of Hibernate. Please note that you need an empty line at the end of the file (anyway Eclipse will scold you if there isn't).
In the MANIFEST.MF editor, Overview tab, click the "Activator" link to create a new Activator for your plugin. Make it inherit from AbstractPluginActivator (provided by the org.decisiondeck.plugin.manager bundle).
This step basically consists in annotating your entities. For each persistent property, you need to feed Hibernate with the correct mapping information. For instance (example taken from the project generated by the Eclipse "New Decision-Deck Project" Wizard):
@Entity @Table(name="UNIQUE_PREFIX_A") public class A extends AbstractProjectEntity { @OneToMany(cascade=CascadeType.ALL, mappedBy="a", fetch=FetchType.LAZY) private List<B> coll = new ArrayList<B>(); @ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.EAGER) @JoinColumn(name="User_ID", unique=false, nullable=false, insertable=true, updatable=true) private User user; @Column(name="Value", unique=false, nullable=true, insertable=true, updatable=true) private Float value; ..... setter/getter }
You may want to read the JPA documentation and Hibernate documentation (see References).
Be careful to note reference Hibernate annotations but rather JPA ones. Hibernate ones may be used to refine JPA, but can't be substituted.
You need to reimplement all your data access operations. Hopefully this will be quick. Your best bet is to inherit from either GenericDao or ProjectEntityDao (if the managed entity is an AbstractProjectEntity - see above).
Generic operations (Create, Read, Update, Delete) are all already implemented in the aforementionned superclasses. So you just need to implement those custom queries. You may want to prefer Hibernate Criteria API over the query API.
Please note that the Query API doesn't support persistence by reachibility - which means that a query such as "delete from Bar e where e.foo=:foo" won't cascade, for cascading sake you will need to access the session :
getSession().delete(myPersistentObject);
This step basically consists in resolving build errors that may arise after all the courageous changes you've done.
Remove all XML elements but id, version, className, and services elements.
If your refine the model by providing new entities, add a refines element such as:
<refines> <entityProvider> <id>org.decisiondeck.iris</id> <entityNames> <!-- Add all your persistent entities here (referenced by qualified name). For instance : --> <entityName>org.decisiondeck.monplugin.model.MyPersistentBean</entityName> .... </entityNames> </entityProvider> </refines>
Here's a concrete example taken from the WeightedSum plugin:
Please refer to the plugin guide for more information on running the platform from within Eclipse.
Once done, you can test your plugin : run the config. Some minor tweaks may need to be done, but that's basically it.
Before checking in your plugin, you may want to tag/branch in CVS so that the 1.0.x version of your plugin can be easily retrieved.