Plugins

Starting from version 1.5, Delta introduces the ability to extend its functionality using plugins. Plugins enable developers to add new functionality to Nexus Delta without the need to modify Delta itself. Plugins can introduce various new functionalities, including new API endpoints and indexing capabilities.

Note

Plugins are still an experimental feature and Delta SDKs and dependent modules(rdf, sourcing) provide no binary compatibility guarantees.

Plugin development

Plugins used by Delta need to be packaged as a .jar file containing the plugin code with all its dependencies. Delta loads plugins from .jar files located in a directory specified by DELTA_PLUGINS environment variable.

Plugins must define exactly one class which extends PluginDef trait.

The class must define following methods:

def info: PluginDescription

this method returns instance of PluginDescription which defines the plugin name and version.

def initialize(locator: Locator): Task[Plugin]

this method can be used to initialize the plugin and returns an instance of a Plugin, which can additionally define logic to stop the plugin gracefully.

def module: ModuleDef

this method must return ModuleDef from distage library. This is the only way in which plugins can use dependencies provided by core of Delta and other plugins, as well as provide dependencies which can be used by Delta and other plugins.

Example

TestPluginRoutes.scala
package ch.epfl.bluebrain.nexus.delta.testplugin

import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import ch.epfl.bluebrain.nexus.delta.sdk.Permissions
import monix.execution.Scheduler

class TestPluginRoutes(permissions: Permissions)(implicit sc: Scheduler) {
  def routes: Route =
    pathPrefix("test-plugin") {
      concat(
        get {
          complete(permissions.fetchPermissionSet.map(ps => s"${ps.mkString(",")}").runToFuture)
        }
      )
    }
}
TestPluginDef.scala
package ch.epfl.bluebrain.nexus.delta.testplugin

import ch.epfl.bluebrain.nexus.delta.sdk.model.ComponentDescription.PluginDescription
import ch.epfl.bluebrain.nexus.delta.sdk.model.Name
import ch.epfl.bluebrain.nexus.delta.sdk.plugin.{Plugin, PluginDef}
import ch.epfl.bluebrain.nexus.delta.sdk.{Permissions, PriorityRoute}
import izumi.distage.model.Locator
import izumi.distage.model.definition.ModuleDef
import monix.bio.Task
import monix.execution.Scheduler

case class TestPluginDef() extends PluginDef {

  override def module: ModuleDef =
    new ModuleDef {
      make[TestPlugin]
      make[TestPluginRoutes].from { (permissions: Permissions, scheduler: Scheduler) =>
        implicit val sc = scheduler
        new TestPluginRoutes(permissions)
      }
      many[PriorityRoute].add((routes: TestPluginRoutes) => PriorityRoute(1, routes.routes))
    }

  override val info: PluginDescription = PluginDescription(Name.unsafe("testplugin"), "0.1.0")

  override def initialize(locator: Locator): Task[Plugin] = Task.pure(locator.get[TestPlugin])

}

Delta SDK

The Delta SDK can be included as following dependencies:

  • SDK - general Delta SDK
  • SDK views - SDK with functionality related to views.

All the above dependencies should be used in provided scope and must not be bundled in the plugin.

libraryDependencies += "ch.epfl.bluebrain.nexus" %% "delta-sdk" % deltaVersion % Provided
libraryDependencies += "ch.epfl.bluebrain.nexus" %% "delta-sdk-views" % deltaVersion % Provided

Dependency injection

Delta uses distage library for dependency injection. Each plugin must define ModuleDef to create instances of its own classes. All the dependencies provided by ModuleDefs defined in Delta modules, as well as other plugins can be used here.

The plugin can also define instances of following traits/classes, which will be used in Delta:

  • PriorityRoute - allows the plugin to define Akka HTTP Route with priority. The priority is used by Delta to prioritize route evaluation
  • ScopeInitialization - allows the plugin to define hooks which will be run on organization and project creation.
  • EventExchange - enables Delta to exchange plugin events for their different representation. Needs to be defined by the plugin in order for resources created by the plugin to be indexed or available via SSE endpoints.
  • ReferenceExchange - enables Delta to exchange a resource reference for a JSON-LD value allowing Delta to handle multiple resources in a uniform way
  • MetadataContextValue - registers metadata context of this plugin into global metadata context
  • RemoteContextResolution - enables Delta to resolve static contexts defined by the plugin
  • ServiceDependency - allows the plugin to define dependencies which will be displayed in /version endpoint.
  • ApiMappings - allows the plugin to define default API mappings used to shorten URLs
  • ResourceToSchemaMappings - allows the plugin to define mapping from the resource type to schema, which can be used to interact with resources created by the plugin through /resources endpoints.
  • EntityType - allows plugin to define its entity type in order to create Akka Persistence persistence_ids.

Class loading

In order to avoid clashes between different plugins, Delta uses custom classloader to load plugin classes, which will load classes from the plugin first, then using application classloader and other plugins after that. It is therefore recommended to not include in the plugin jar any dependencies which are also provided by SDK. Libraries can be easily excluded from dependencies in sbt:

libraryDependencies       ++= Seq(
  "com.lightbend.akka" %% "akka-stream-alpakka-sse" % alpakkaVersion excludeAll (
        ExclusionRule(organization = "com.typesafe.akka", name = "akka-stream_2.13"),
        ExclusionRule(organization = "com.typesafe.akka", name = "akka-http_2.13")
      )

Configuration

Plugins should provide their default configuration in {plugin_name}.conf file, where plugin_name is the same as the one in PluginDescription. Plugins should include their config inside plugins.{plugin_name} namespace in the config. plugins.{plugin_name}.priority configuration setting defines priority of the plugin, which is used to determine order in which routes provided by plugins are evaluated.

Adding plugins to a Delta deployment

Delta loads plugins from .jar files present in a folder specified by DELTA_PLUGINS environment variable. In order to make delta discover the plugin, the .jar file of the plugin must be added(or symlinked) to that directory. In the official Delta Docker image the plugins are loaded from /opt/docker/plugins directory.

Enabling/disabling plugins

Additionally, plugins can be enabled/disabled using plugins.{plugin_name}.enabled property. Setting this property to false, will disable the given plugin.

Existing plugins

Currently, following Delta functionality is provided by plugins:

Elasticsearch plugin is required in order to provide listings in the API, other plugins can be excluded if their functionality is not needed. All the above plugins are included in the Delta Docker image.