[[PageOutline]] = Plugin Development Guide = DeepaMehta is made to be extensible by 3rd-party developers. Developers extend DeepaMehta by developing plugins (resp. "modules" resp. "applications" which is all synonymous). This guide teaches you how to develop DeepaMehta plugins. == Build DeepaMehta from source == The best way to develop DeepaMehta plugins is to build DeepaMehta from source first. This way you get a hot-deploy environment, that is DeepaMehta redeploys your plugin automatically once you compile it. This is very handy while plugin development. Requirements: * **Java 6** (newer versions //might// work as well, older versions do //not// work) * **Maven 3** (older versions do //not// work) * **Git** Build DeepaMehta from source: {{{ #!sh $ git clone git://github.com/jri/deepamehta.git $ cd deepamehta $ mvn install -P all }}} This builds all components of the DeepaMehta Standard Distribution and installs them in your local Maven repository. You'll see a lot of information logged, cumulating in: {{{ #!txt ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 53.515s ... }}} == The plugin turn-around cycle == This section illustrates how to begin a plugin project, how to build and how to deploy a plugin, and how to redeploy the plugin once you made changes in its source code. In other words, this section illustrates the plugin development turn-around cycle. Let's start with a very simple plugin called //DeepaMehta 4 Tagging//. This plugin will just create a new topic type called `Tag`. Once the plugin is activated the topic type will appear in the DeepaMehta Webclient's //Create// menu, so you can create tag topics and associate them with arbitrary topics. And you will be able to fulltext search for tags. Developing a plugin whose only purpose is to provide new topic type definitions requires no Java or JavaScript coding. All is declarative, mainly in JSON format. Of course the topic type could be created interactively as well, by using the DeepaMehta Webclient's type editor. However, being packaged as a plugin means you can distribute it. When other DeepaMehta users install your plugin they can use your type definitions. === Begin a plugin project === From the developer's view a DeepaMehta plugin is just a directory on your hard disc. The directory can have an arbitrary name and exist at an arbitrary location. By convention the plugin directory begins with `dm4-` as it is aimed to the DeepaMehta 4 platform. The directory content adheres to a certain directory structure and file name conventions. The files are text files (xml, json, properties, java, js, css) and resources like images. To create the //DeepaMehta 4 Tagging// plugin setup a directory structure as follows: {{{ #!txt dm4-tagging/ pom.xml src/ main/ resources/ migrations/ migration1.json plugin.properties }}} Create the file **`pom.xml`** with this content: {{{ #!xml 4.0.0 DeepaMehta 4 Tagging org.mydomain.dm4 tagging 0.1-SNAPSHOT bundle de.deepamehta deepamehta-plugin-parent 4.1.1-SNAPSHOT }}} Create the file **`migration1.json`**: {{{ #!js { topic_types: [ { value: "Tag", uri: "domain.tagging.tag", data_type_uri: "dm4.core.text", index_mode_uris: ["dm4.core.fulltext"], view_config_topics: [ { type_uri: "dm4.webclient.view_config", composite: { dm4.webclient.show_in_create_menu: true } } ] } ] } }}} Create the file **`plugin.properties`**: {{{ #!txt requiredPluginMigrationNr=1 importModels=de.deepamehta.webclient }}} === Setup for Hot-Deployment === In order to let DeepaMehta hot-deploy the plugin you must include it in DeepaMehta's hot-deployment list. In DeepaMehta's **`pom.xml`**: add the plugin's `target` directory (here: `/home/myhome/deepamehta-dev/dm4-tagging/target`) to the `felix.fileinstall.dir` property's `CDATA` section. Important: don't forget to append a comma to the previous line: {{{ #!xml ... ... }}} Now start DeepaMehta. In the directory `deepamehta` (where you've build): {{{ #!sh $ mvn pax:run }}} This starts DeepaMehta in development mode, that is with hot-deployment activated. You'll see a lot of information logged, cumulating with: {{{ #!txt ... Apr 6, 2013 11:21:20 PM de.deepamehta.core.impl.PluginManager checkAllPluginsActivated INFO: ### Bundles total: 32, DeepaMehta plugins: 16, Activated: 16 Apr 6, 2013 11:21:20 PM de.deepamehta.core.impl.PluginManager activatePlugin INFO: ########## All Plugins Activated ########## Apr 6, 2013 11:21:20 PM de.deepamehta.plugins.webclient.WebclientPlugin allPluginsActive INFO: ### Launching webclient (url="http://localhost:8080/de.deepamehta.webclient/") ... }}} Then a browser windows opens automatically and displays the DeepaMehta Webclient. The terminal is now occupied by the //Gogo// shell. Press the return key some times and you'll see its `g!` prompt. Type the `lb` command to get the list of activated bundles: {{{ #!sh g! lb }}} The output looks like this: {{{ #!txt START LEVEL 6 ID|State |Level|Name 0|Active | 0|System Bundle (3.2.1) ... 14|Active | 5|DeepaMehta 4 Help (4.1.1.SNAPSHOT) 15|Active | 5|DeepaMehta 4 Topicmaps (4.1.1.SNAPSHOT) 16|Active | 5|DeepaMehta 4 Webservice (4.1.1.SNAPSHOT) 17|Active | 5|DeepaMehta 4 Files (4.1.1.SNAPSHOT) 18|Active | 5|DeepaMehta 4 Geomaps (4.1.1.SNAPSHOT) 19|Active | 5|DeepaMehta 4 Storage - Neo4j (4.1.1.SNAPSHOT) 20|Active | 5|DeepaMehta 4 Core (4.1.1.SNAPSHOT) 21|Active | 5|DeepaMehta 4 Access Control (4.1.1.SNAPSHOT) 22|Active | 5|DeepaMehta 4 Webclient (4.1.1.SNAPSHOT) 23|Active | 5|DeepaMehta 4 Webbrowser (4.1.1.SNAPSHOT) 24|Active | 5|DeepaMehta 4 Type Search (4.1.1.SNAPSHOT) 25|Active | 5|DeepaMehta 4 Workspaces (4.1.1.SNAPSHOT) 26|Active | 5|DeepaMehta 4 Notes (4.1.1.SNAPSHOT) 27|Active | 5|DeepaMehta 4 Type Editor (4.1.1.SNAPSHOT) 28|Active | 5|DeepaMehta 4 Contacts (4.1.1.SNAPSHOT) 29|Active | 5|DeepaMehta 4 Facets (4.1.1.SNAPSHOT) 30|Active | 5|DeepaMehta 4 File Manager (4.1.1.SNAPSHOT) 31|Active | 5|DeepaMehta 4 Icon Picker (4.1.1.SNAPSHOT) }}} The //DeepaMehta 4 Tagging// plugin does not yet appear in that list as it is not yet build. === Build the plugin === In another terminal: {{{ #!sh $ cd dm4-tagging $ mvn clean package }}} This builds the plugin. After some seconds you'll see: {{{ #!txt ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.988s ... }}} Once build, DeepaMehta hot-deploys the plugin automatically. In the terminal where you've started DeepaMehta the logging informs you about plugin activation: {{{ #!txt Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl readConfigFile INFO: Reading config file "/plugin.properties" for plugin "DeepaMehta 4 Tagging" Apr 6, 2013 11:38:40 PM de.deepamehta.core.osgi.PluginActivator start INFO: ========== Starting plugin "DeepaMehta 4 Tagging" ========== Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl createPluginServiceTrackers INFO: Tracking plugin services for plugin "DeepaMehta 4 Tagging" ABORTED -- no consumed services declared Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl addService INFO: Adding DeepaMehta 4 core service to plugin "DeepaMehta 4 Tagging" Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl addService INFO: Adding Web Publishing service to plugin "DeepaMehta 4 Tagging" Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl registerWebResources INFO: Registering Web resources of plugin "DeepaMehta 4 Tagging" ABORTED -- no Web resources provided Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl registerRestResources INFO: Registering REST resources of plugin "DeepaMehta 4 Tagging" ABORTED -- no REST resources provided Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl registerRestResources INFO: Registering provider classes of plugin "DeepaMehta 4 Tagging" ABORTED -- no provider classes provided Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl addService INFO: Adding Event Admin service to plugin "DeepaMehta 4 Tagging" Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginManager activatePlugin INFO: ----- Activating plugin "DeepaMehta 4 Tagging" ----- Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl createPluginTopicIfNotExists INFO: Installing plugin "DeepaMehta 4 Tagging" in the database Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.MigrationManager runPluginMigrations INFO: Running 1 migrations for plugin "DeepaMehta 4 Tagging" (migrationNr=0, requiredMigrationNr=1) Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.MigrationManager$MigrationInfo readMigrationConfigFile INFO: Reading migration config file "/migrations/migration1.properties" ABORTED -- file does not exist Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.MigrationManager runMigration INFO: Running migration 1 of plugin "DeepaMehta 4 Tagging" (runMode=ALWAYS, isCleanInstall=true) Apr 6, 2013 11:38:40 PM de.deepamehta.core.util.DeepaMehtaUtils readMigrationFile INFO: Reading migration file "/migrations/migration1.json" Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.MigrationManager runMigration INFO: Completing migration 1 of plugin "DeepaMehta 4 Tagging" Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.MigrationManager runMigration INFO: Updating migration number (1) Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl registerListeners INFO: Registering listeners of plugin "DeepaMehta 4 Tagging" at DeepaMehta 4 core service ABORTED -- no listeners implemented Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginImpl registerPluginService INFO: Registering OSGi service of plugin "DeepaMehta 4 Tagging" ABORTED -- no OSGi service provided Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginManager activatePlugin INFO: ----- Activation of plugin "DeepaMehta 4 Tagging" complete ----- Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginManager checkAllPluginsActivated INFO: ### Bundles total: 33, DeepaMehta plugins: 17, Activated: 17 Apr 6, 2013 11:38:40 PM de.deepamehta.core.impl.PluginManager activatePlugin INFO: ########## All Plugins Activated ########## Apr 6, 2013 11:38:40 PM de.deepamehta.plugins.webclient.WebclientPlugin allPluginsActive INFO: ### Launching webclient (url="http://localhost:8080/de.deepamehta.webclient/") ABORTED -- already launched ... }}} When you type again `lb` in the DeepaMehta terminal you'll see the //DeepaMehta 4 Tagging// plugin now appears in the list of activated bundles: {{{ #!txt START LEVEL 6 ID|State |Level|Name 0|Active | 0|System Bundle (3.2.1) ... 30|Active | 5|DeepaMehta 4 File Manager (4.1.1.SNAPSHOT) 31|Active | 5|DeepaMehta 4 Icon Picker (4.1.1.SNAPSHOT) 32|Active | 5|DeepaMehta 4 Tagging (0.1.0.SNAPSHOT) }}} === Try out the plugin === Now you can try out the plugin. In the DeepaMehta Webclient login as user "admin" and leave the password field empty. The //Create// menu appears and when you open it you'll see the new type //Tag// listed. Thus, you can create tags now. Additionally you can associate tags to your content topics, search for tags, and navigate along the tag associations, just as you do with other topics. The result so far: the //DeepaMehta 4 Tagging// plugin provides a new topic type definition or, in other words: a data model. All the active operations on the other hand like create, edit, search, delete, associate, and navigate are provided by the DeepaMehta Webclient at a generic level, and are applicable to your new topic type as well. === Redeploy the plugin === Once you've made any changes to the plugin files, you have to build the plugin again. Just like before in the plugin terminal: {{{ #!sh $ mvn clean package }}} Once building is complete the changed plugin is redeployed automatically. You'll notice activity in the DeepaMehta terminal: {{{ #!txt Apr 8, 2013 1:10:40 AM de.deepamehta.core.osgi.PluginActivator stop INFO: ========== Stopping plugin "DeepaMehta 4 Tagging" ========== Apr 8, 2013 1:10:40 AM de.deepamehta.core.impl.PluginImpl removeService INFO: Removing DeepaMehta 4 core service from plugin "DeepaMehta 4 Tagging" Apr 8, 2013 1:10:40 AM de.deepamehta.core.impl.PluginImpl removeService INFO: Removing Web Publishing service from plugin "DeepaMehta 4 Tagging" Apr 8, 2013 1:10:40 AM de.deepamehta.core.impl.PluginImpl removeService INFO: Removing Event Admin service from plugin "DeepaMehta 4 Tagging" ... ... Apr 8, 2013 1:10:44 AM de.deepamehta.core.osgi.PluginActivator start INFO: ========== Starting plugin "DeepaMehta 4 Tagging" ========== ... ... Apr 8, 2013 1:10:44 AM de.deepamehta.core.impl.PluginManager activatePlugin INFO: ----- Activating plugin "DeepaMehta 4 Tagging" ----- Apr 8, 2013 1:10:44 AM de.deepamehta.core.impl.PluginImpl createPluginTopicIfNotExists INFO: Installing plugin "DeepaMehta 4 Tagging" in the database ABORTED -- already installed Apr 8, 2013 1:10:44 AM de.deepamehta.core.impl.MigrationManager runPluginMigrations INFO: Running migrations for plugin "DeepaMehta 4 Tagging" ABORTED -- everything up-to-date (migrationNr=1) ... ... Apr 8, 2013 1:10:44 AM de.deepamehta.core.impl.PluginManager activatePlugin INFO: ----- Activation of plugin "DeepaMehta 4 Tagging" complete ----- Apr 8, 2013 1:10:44 AM de.deepamehta.core.impl.PluginManager checkAllPluginsActivated INFO: ### Bundles total: 33, DeepaMehta plugins: 17, Activated: 17 Apr 8, 2013 1:10:44 AM de.deepamehta.core.impl.PluginManager activatePlugin INFO: ########## All Plugins Activated ########## Apr 8, 2013 1:10:44 AM de.deepamehta.plugins.webclient.WebclientPlugin allPluginsActive INFO: ### Launching webclient (url="http://localhost:8080/de.deepamehta.webclient/") ABORTED -- already launched ... }}} In contrast to the initial build of the plugin you can recognize some differences in this log: * The old version of the plugin currently deployed is stopped. * The new version of the plugin is deployed (that is //started// and //activated//) right away. * The plugin is //not// installed again in the database as already done while initial build. * The migration is //not// run again as already done while initial build. To ensure the DeepaMehta Webclient is aware of the changed plugin press the browser's reload button. === Stopping DeepaMehta === To stop DeepaMehta, in the Gogo shell type: {{{ #!sh g! stop 0 }}} This stops all bundles, shuts down the webserver, and the database. == Migrations == A //migration// is a sequence of database operations that is executed exactly once in the lifetime of a particular DeepaMehta installation. You as a developer are responsible for equipping your plugin with the required migrations. Migrations serve several purposes: 1. Define the plugin's data model. That is, storing new topic type definitions and association type definitions in the database. E.g. a //Books// plugin might define the types //Book//, //Title//, and //Author//. 2. A newer version of your plugin might extend or modify the data model defined by the previous version of your plugin. The migration of the updated plugin change the stored type definitions //and// transforms existing content if necessary. 3. The application logic of a newer version of your plugin changes in a way it is not compatible anymore with the existing database content. The migration must transform the existing content then. So, the purpose expressed in points 2. and 3. is to make your plugin //upgradable//. That is, keeping existing database content //in-snyc// with the plugin logic. By providing the corresponding migrations you make your plugin //compatible// with the previous plugin version. === The migration machinery === Each plugin comes with its own data model. For each plugin DeepaMehta keeps track what data model version is currently installed. It does so by storing the version of the installed data model in the database as well. The data model version is an integer number that starts at 0 and is increased consecutively: 0, 1, 2, and so on. Each version number (except 0) corresponds with a particular migration. The migration with number //n// is responsible for transforming the database content from version //n-1// to version //n//. You as the developer know 2 things about your plugin: a) Which plugin version relies on which data model version, and b) How to transform the database content in order to advance from a given data model version to the next. So, when you ship your plugin you must equip it with 2 things: * The information what data model version the plugin relies on. * All the migrations required to update to that data model version. The relationship between plugin version and data model version might look as follows: ||= Plugin Version =||= Data Model Version =|| || 0.1 || 2 || || 0.2 || 5 || || 0.2.1 || 5 || || 0.3 || 6 || If e.g. version 0.1 of the plugin is currently installed, the database holds "2" as the current data model version. When the user updates to version 0.3 of the plugin, DeepaMehta's migration machinery will recognize that data model version 2 is present but version 6 is required. As a consequence DeepaMehta will consecutively run migrations 3 through 6. Once completed, the database holds "6" as the current data model version. Thus, the users database will always be compatible with the installed version of the plugin. Furthermore, the user is free to skip versions when upgrading the plugin. === Plugin configuration === If your plugin comes with its own data model you must tell DeepaMehta the data model version it relies on. To do so, set the `requiredPluginMigrationNr` configuration property in the `plugin.properties` file, e.g.: {{{ #!txt requiredPluginMigrationNr=2 }}} DeepaMehta's migration machinery takes charge of running the plugin's migrations up to that configured number. If your plugin comes with no data model, you can specify `0` resp. omit the `requiredPluginMigrationNr` property as `0` is its default value. Usually each plugin has its own `plugin.properties` file. It allows the developer to configure certain aspects of the plugin. The name of the `plugin.properties` file and its path within the plugin directory is fixed: {{{ #!txt dm4-myplugin/src/main/resources/plugin.properties }}} If no `plugin.properties` file is present, the default configuration values apply. === The two kinds of migrations === As you've already learned, migrations serve different (but related) purposes: some just //create// new type definitions and others //modify// existing type definitions and/or transform existing database content. To support the developer with these different tasks DeepaMehta offers two kinds of migrations: * A **Declarative Migration** is a JSON file that declares 4 kinds of things: topic types, association types, topics, associations. Use a declarative migration to let DeepaMehta create new types and instances in the database. Use a declarative migration to let your plugin setup the initial type definitions. With a declarative migration you can only create new things. You can't modify existing things. All you do with a declarative migration you could achieve with an imperative migration as well, but as long as you just want create new things, it is more convenient to do it declaratively. * An **Imperative Migration** is a Java class that has access to the //DeepaMehta Core Service//. Thus, you can perform arbitrary database operations like creation, retrieval, update, deletion. Use an imperative migration when (a later version of) your plugin needs to modify existing type definitions and/or transform existing database content. The developer can equip a plugin with an arbitrary number of both, declarative migrations and imperative migrations. === Directory structure === In order to let DeepaMehta find the plugin's migration files, you must adhere to a fixed directory structure and file names. Each migration file must contain its number, so DeepaMehta can run them consecutively. A declarative migration must be named `migration.json` and must be located in the plugin's `src/main/resources/migrations/` directory. An imperative migration must be named `Migration.java` and must be located in the plugin's `src/main/java//migrations/` directory. Example: {{{ #!txt dm4-myplugin/ src/ main/ java/ org/ mydomain/ deepamehta4/ myplugin/ migrations/ Migration2.java Migration5.java resources/ migrations/ migration1.json migration3.json migration4.json migration6.json plugin.properties }}} This example plugin would have set `requiredPluginMigrationNr` to 6 (configured in `plugin.properties`), so 6 migrations are involved. 4 are declarative and 2 are imperative here. Important: for each number between 1 and `requiredPluginMigrationNr` exactly one migration file must exist. That is //either// a declarative migration file //or// an imperative migration file. It would be invalid if for a given number a) no migration file exists, or b) two migration files exist (one declarative and one imperative). In these cases the DeepaMehta migration machinery throws an error and the plugin is not activated. === Writing a declarative migration === A declarative migration is a JSON file with exactly one JSON Object in it. In a declarative migration you can define 4 things: topic types, association types, topics, associations. The general format is: {{{ #!js { topic_types: [ ... ], assoc_types: [ ... ], topics: [ ... ], associations: [ ... ] } }}} Each of the 4 sections is optional. As an example see the (simplified) migration that defines the //Note// topic type. This migration is part of the //DeepaMehta 4 Notes// plugin: {{{ #!js { topic_types: [ { value: "Title", uri: "dm4.notes.title", data_type_uri: "dm4.core.text", index_mode_uris: ["dm4.core.fulltext"] }, { value: "Text", uri: "dm4.notes.text", data_type_uri: "dm4.core.html", index_mode_uris: ["dm4.core.fulltext"] }, { value: "Note", uri: "dm4.notes.note", data_type_uri: "dm4.core.composite", assoc_defs: [ { child_type_uri: "dm4.notes.title", child_cardinality_uri: "dm4.core.one", assoc_type_uri: "dm4.core.composition_def" }, { child_type_uri: "dm4.notes.text", child_cardinality_uri: "dm4.core.one", assoc_type_uri: "dm4.core.composition_def" } ], view_config_topics: [ { type_uri: "dm4.webclient.view_config", composite: { dm4.webclient.icon: "/de.deepamehta.notes/images/yellow-ball.png", dm4.webclient.show_in_create_menu: true } } ] } ] } }}} As you see, this migration defines 3 topic types (and no other things): //Title// and //Text// are 2 simple types, and //Note// is a composite type. A Note is composed of one Title and one Text. === Writing an imperative migration === An imperative migration is a Java class that is derived from `de.deepamehta.core.service.Migration` and that overrides the `run()` method. The `run()` method is called by DeepaMehta to run the migration. Within the migration you have access to the //DeepaMehta Core Service// through the `dms` object. By the means of the DeepaMehta Core Service you can perform arbitrary database operations. Typically this involves importing further objects from the `de.deepamehta.core` API. As an example see a migration that comes with the //DeepaMehta 4 Topicmaps// plugin: {{{ #!java package de.deepamehta.plugins.topicmaps.migrations; import de.deepamehta.core.TopicType; import de.deepamehta.core.model.AssociationDefinitionModel; import de.deepamehta.core.service.Migration; public class Migration3 extends Migration { @Override public void run() { TopicType type = dms.getTopicType("dm4.topicmaps.topicmap", null); type.addAssocDef(new AssociationDefinitionModel("dm4.core.composition_def", "dm4.topicmaps.topicmap", "dm4.topicmaps.state", "dm4.core.one", "dm4.core.one")); } } }}} Here an association definition is added to the //Topicmap// type subsequently. == The server side == What a DeepaMehta plugin can do at the server side: * **Listen to DeepaMehta Core events**. In particular situations the DeepaMehta Core fires events, e.g. before and after it creates a new topic in the database. Your plugin can listen to these events and react in its own way. Thus, the //DeepaMehta 4 Workspaces// plugin e.g. ensures that each new topic is assigned to a workspace. * **Providing a service**. Your plugin can make its business logic, that is its service methods, accessible by other plugins (via OSGi) and/or by external applications (via HTTP/REST). Example: the service provided by the //DeepaMehta 4 Topicmaps// plugin includes methods to add a topic to a topicmap or to change the topic's coordinates within a topicmap. * **Consuming services provided by other plugins**. Example: in order to investigate a topic's workspace assignments and the current user's memberships the //DeepaMehta 4 Access Control// plugin consumes the service provided by the //DeepaMehta 4 Workspaces// plugin. * **Access the DeepaMehta Core Service**. The DeepaMehta Core Service provides the basic database operations (create, retrieve, update, delete) to deal with the DeepaMehta Core objects: Topics, Associations, Topic Types, Association Types. Weather a DeepaMehta plugin has a server side part at all depends on the nature of the plugin. Plugins without a server side part include those which e.g. just define a data model or just provide a custom (JavaScript) renderer. === The plugin main file === You must write a //plugin main file// if your plugin needs to a) listen to DeepaMehta Core events and/or b) provide a service. The plugin main file contains the event handlers resp. the service implementation then. The plugin main file must be located directly in the plugin's `src/main/java//` directory. By convention the plugin main class ends with `Plugin`. Example: {{{ #!txt dm4-mycoolplugin/ src/ main/ java/ org/ mydomain/ deepamehta4/ mycoolplugin/ MyCoolPlugin.java }}} Here the plugin package is `org.mydomain.deepamehta4.mycoolplugin` and the plugin main class is `MyCoolPlugin`. A plugin main file is a Java class that is derived from `de.deepamehta.core.osgi.PluginActivator`. The smallest possible plugin main file looks like this: {{{ #!java package org.mydomain.deepamehta4.mycoolplugin; import de.deepamehta.core.osgi.PluginActivator; public class MyCoolPlugin extends PluginActivator { } }}} 3 things are illustrated here: * The plugin should be packaged in an unique namespace. * The `PluginActivator` class needs to be imported. * The plugin main class must be derived from `PluginActivator` and must be public. Furthermore when writing a plugin main file you must add 2 entries in the plugin's **`pom.xml`**: 1. a element to include the `deepamehta-core` dependency. This brings you the `PluginActivator` class. 2. a element to configure the Maven Bundle Plugin. It needs to know what your plugin main class is. You must specify the fully-qualified class name. {{{ #!xml 4.0.0 My Cool Plugin org.mydomain.dm4 my-cool-plugin 0.1-SNAPSHOT bundle de.deepamehta deepamehta-plugin-parent 4.1.1-SNAPSHOT de.deepamehta deepamehta-core 4.1.1-SNAPSHOT org.apache.felix maven-bundle-plugin org.mydomain.deepamehta4.mycoolplugin.MyCoolPlugin }}} === Listen to DeepaMehta Core events === In particular situations the DeepaMehta Core fires events, e.g. before and after it creates a new topic in the database. Your plugin can listen to these events and react in its own way. Listening to a DeepaMehta Core event means implementing the corresponding listener interface. A listener interface consist of just one method: the //listener method//. That method is called by the DeepaMehta Core when the event is fired. The listener interfaces are located in package `de.deepamehta.core.service.event`. To listen to a DeepaMehta Core event, in the plugin main class you must: * Import the listener interface. * Declare the plugin main class implements that interface. * Implement the listener method. Use the `@Override` annotation. * Import the classes appearing in the listener method arguments. Example: {{{ #!java package org.mydomain.deepamehta4.mycoolplugin; import de.deepamehta.core.Topic; import de.deepamehta.core.model.TopicModel; import de.deepamehta.core.osgi.PluginActivator; import de.deepamehta.core.service.ClientState; import de.deepamehta.core.service.Directives; import de.deepamehta.core.service.event.PostCreateTopicListener; import de.deepamehta.core.service.event.PostUpdateTopicListener; import java.util.logging.Logger; public class MyCoolPlugin extends PluginActivator implements PostCreateTopicListener, PostUpdateTopicListener { private Logger log = Logger.getLogger(getClass().getName()); @Override public void postCreateTopic(Topic topic, ClientState clientState, Directives directives) { log.info("### Topic created: " + topic); } @Override public void postUpdateTopic(Topic topic, TopicModel newModel, TopicModel oldModel, ClientState clientState, Directives directives) { log.info("### Topic updated: " + topic + "\nOld topic: " + oldModel); } } }}} This example plugin listens to 2 DeepaMehta Core events: `POST_CREATE_TOPIC` and `POST_UPDATE_TOPIC`. These particular events are fired //after// the DeepaMehta Core has created resp. updated a topic. The DeepaMehta Core passes the created/updated topic to the respective listener method. In case of "update" the previous topic content (`oldModel`) is also passed to enable the plugin to investigate what exactly has changed. The example plugin just logs the created resp. updated topic. In case of "update" the previous topic content is logged as well. A [[DeepaMehtaCoreEvents|list of all DeepaMehta Core events]] is available in the reference section. === Providing a service === Your plugin can make its business logic, that is its service methods, accessible by other plugins (via OSGi) and/or by external applications (via HTTP/REST). ==== The service interface ==== For a plugin to provide a service you must define a //service interface//. The service interface contains all the method signatures that make up the service. When other plugins consume your plugin's service they do so via its service interface. The service interface //must// be located in the plugin's `src/main/java//service/` directory. By convention the name of the service interface ends with `...Service`. The service interface must be declared `public`. The service interface is a regular Java interface that extends `de.deepamehta.core.service.PluginService`. PluginService is a common base interface for all plugin services. It's just a marker interface. As an example look at the //Topicmaps// plugin (part of the DeepaMehta Standard Distribution): {{{ #!txt dm4-topicmaps/ src/ main/ java/ de/ deepamehta/ plugins/ topicmaps/ service/ TopicmapsService.java }}} The service interface of the //Topicmaps// plugin is named `TopicmapsService`. The plugin package is `de.deepamehta.plugins.topicmaps`. The //Topicmaps// service interface looks like this: {{{ #!java package de.deepamehta.plugins.topicmaps.service; import de.deepamehta.plugins.topicmaps.TopicmapRenderer; import de.deepamehta.plugins.topicmaps.model.ClusterCoords; import de.deepamehta.plugins.topicmaps.model.Topicmap; import de.deepamehta.core.Topic; import de.deepamehta.core.service.ClientState; import de.deepamehta.core.service.PluginService; public interface TopicmapsService extends PluginService { Topic createTopicmap(String name, String topicmapRendererUri, ClientState clientState); Topic createTopicmap(String name, String uri, String topicmapRendererUri, ClientState clientState); // --- Topicmap getTopicmap(long topicmapId, ClientState clientState); // --- void addTopicToTopicmap(long topicmapId, long topicId, int x, int y); void addAssociationToTopicmap(long topicmapId, long assocId); void moveTopic(long topicmapId, long topicId, int x, int y); void setTopicVisibility(long topicmapId, long topicId, boolean visibility); void removeAssociationFromTopicmap(long topicmapId, long assocId); void moveCluster(long topicmapId, ClusterCoords coords); void setTopicmapTranslation(long topicmapId, int trans_x, int trans_y); // --- void registerTopicmapRenderer(TopicmapRenderer renderer); } }}} You see the Topicmaps service consist of methods to create topicmaps, retrieve topicmaps, and manipulate topicmaps. As any plugin service the Topicmaps service must be derived from `PluginService`.