Backend API

Any plugin should contain all the backend and the frontend code to provide a full feature, but for clarity we will split the documentation in those two parts.

Live API

Intelie Live's backend is mainly written in Java.

You can develop a plugin using any language that runs in the JVM.

To develop a plugin, you need to import the live-api library into your project, using Maven, Gradle or another build automation tool. The scope of the dependency should be provided because Intelie Live will already provide that code in runtime.

An example using Maven is described below.

<dependency>
    <groupId>net.intelie.live</groupId>
    <artifactId>live-api</artifactId>
    <version>${live-api-version}</version>
    <scope>provided</scope>
</dependency>

The properties used on the examples here can be defined in the file .mvn/maven.config, as shown in the following example.

$ cat .mvn/maven.config
-Dlive-api-version=2.25.5
-DbuildID=${CI_PIPELINE_ID}

Manifest and entry point

Intelie Live plugins must have a manifest, that can be generated by Maven as the example below shows.

 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.0.2</version>
    <configuration>
        <archive>
            <manifestEntries>
                <name>${project.name}</name>
                <version>${project.version}</version>
                <build>${buildID}</build>
                <author>company</author>
                <entryPoint>my.company.plugin.Main</entryPoint>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

The Manifest contains an entry named entryPoint. This represents the class that will be executed by Intelie Live every time the plugin starts or stops. This class should implement the LivePlugin interface.

public class Main implements LivePlugin {

    @Override
    public void start(Live live) throws Exception {
      // anything here will execute under the plugin lifecycle
    }
}

The Live object represents the Live platform itself and ensures you could take advantage on all the power of Live as Extensions, REST Web Services, Storage Providers, Query Providers, Event Lobby, Web Applications and much more.

The LivePlugin interface doesn't offer a stop method because Live releases any resources previously created by the user plugin automatically.

Plugin dependency

Any plugin may depend on other plugins. This makes it possible for any plugin to use services and components provided by other plugins.

Intelie Live controls a dependency graph that makes dependent plugins restart when their dependencies are started. If a runtime dependency is missing, the plugin will be stopped and be marked as invalid until all the dependencies are available again.

A dependency can also be declared as optional. The plugin will start normally even if an optional dependency artifact is missing at runtime. In that case, the plugin should handle manually an eventual NoClassDefFoundError that can be risen if a class of a missing optional dependency is accessed.

Since version 3.31.0, Live adopts the Maven Version Range specification for the dependency format. See more about it here.

Defining the dependency

This requires that both the compile time and the runtime dependencies are described.

For example, to define two dependencies to plugin-annotations and plugin-healthcheck, using Maven, you should define the compile time dependencies as follows.

<dependency>
    <groupId>net.intelie.live</groupId>
    <artifactId>plugin-annotations</artifactId>
    <version>1.0.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>net.intelie.live</groupId>
    <artifactId>plugin-healthcheck</artifactId>
    <version>${live-api-version}</version>
    <scope>provided</scope>
</dependency>

All compile time dependencies to other plugins must have the provided scope.

Runtime dependencies are defined on a Manifest's requirePlugins entry, as shown in the example below.

 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.0.2</version>
    <configuration>
        <archive>
            <manifestEntries>
                <name>${project.name}</name>
                <version>${project.version}</version>
                <build>${buildID}</build>
                <author>company</author>
                <entryPoint>my.company.plugin.Main</entryPoint>
                <expectedLiveVersion>${live-api-version}</expectedLiveVersion>
                <requirePlugins>plugin-annotations@1.0.0,plugin-healtcheck@${live-api-version};optional</requirePlugins>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

In our example, as the plugin-healthcheck is provided together with Intelie Live platform, its version will be the same as Live's version, defined in our variable live-api-version. It's also marked as optional. That means that our plugin should start normally even if plugin-healthcheck artifact is missing at runtime.

Range version requirements in Manifest files should be double quoted:

<requirePlugins>plugin-healthcheck, plugin-annotations@"(,1.0],[1.2,)"</requirePlugins>

However, if defining dependencies in a YAML manifest, such as in packages, then quotes should not be used.

requirePlugins:
  - plugin-healthcheck
  - plugin-annotations@(,1.0],[1.2,)

Range version requirements can also be defined as variables:

$ cat .mvn/maven.config
-Dlive-api-version=2.25.5
-Dplugin-annotations-version=[1.2,2.3)
-DbuildID=${CI_PIPELINE_ID}

The same quoting rules described previously should be used to decide whether or not to quote the variable interpolation (e.g.: ${plugin-annotations-version}).

Exporting components

To provide services and components to other plugins, a plugin may export the package containing those elements.

For example, the plugin-annotations exports a package using the following code.

live.exportPackage("net.intelie.live.plugins.annotations.api");

To register a service, one should use the following code.

live.system().registerPluginService(AnnotationService.class, annotationService);

Note that a Plugin Service there's no special meaning, i.e. a POJO can be a service.

Importing components

To use a component provided by another plugin, one should require the service by its class.

In our example, we should use the code below to require the AnnotationService.

live.system().getPluginService(AnnotationService.class);

Handling optional dependencies

When an optional dependency is defined, the plugin developer should handle the case when that dependency artifact is missing at runtime.

It is recomended that the entry-point (main) class of the plugin doesn't make direct references to classes contained in an optional dependency. In some specific cases, the class loader can try to load classes in an eager way, which can cause errors before the main plugin class is called. Separating all accesses in a different class will avoid this problem.

The following example handles the access to plugin-healthcheck , which can be missing at runtime:

try {
    new HealthcheckDependencyResolver().accept(live);
} catch (NoClassDefFoundError e) {
    LOGGER.warn("Optional dependency plugin-healthcheck not found.");
}

Note that the code above doesn't make direct references to the to plugin-healthcheck classes, and can be put in the plugin main class. The code bellow handles and directly references plugin-healthcheck classes.

private static class HealthcheckDependencyResolver implements Consumer<Live> {

    @Override
    public void accept(Live live) {
        try {
            HealthcheckMonitorService monitorService = Objects.requireNonNull(live.system().getPluginService(HealthcheckMonitorService.class));
            monitorService.registerMonitor(live, new MyMonitor());
        } catch (Exception e) {
            LOGGER.error("Unable to register healthcheck monitor {}", AnnotationsMonitor.class.getSimpleName());
        }
    }
}

Last updated