skip to main content

Annotation Processor

The Annotation Processor is a tool designed to simplify the development of Livebase plugins implementing OSGi services.

The Annotation Processor has been made available to simplify the creation of the files needed to configure the OSGi services exposed by the livebase plugins; its task in particular is to automatically generate the blueprint files for these services by interpreting the annotations present in the source.

Gradle project setup #

All base projects are already configured to run the Annotation Processor during development in Eclipse and concurrently with the Gradle build. If you do not use the base projects, you will have to follow the detailed procedure below.

First, open your project’s build.gradle and, if it is not in the repositories section, add jcenter() to link Gradle to the JCenter repository, which currently hosts the Annotation Processor library:

repositories {
  //...
  jcenter()
  //...
}

Now, include the relevant library with the following code:

def processorLib = 'com.fhoster.livebase:cloudlet-spi-annotation-processor:+'
dependencies {
  //...
  compileOnly processorLib
  annotationProcessor processorLib
  //...
}

You will need to add its identifier (stored here in the processorLib variable for convenience) to the dependencies section, using both the compileOnly and annotationProcessor configurations to perform compile-time inclusion and link annotation processing to the build operation. Note the use of + in the library version segment: this instructs Gradle to always take the latest available version.

The next step is to configure the Annotation Processor by specifying the output directory of the OSGi blueprints that will be generated:

def blueprintsDir = "generated"
sourceSets.main.output.resourcesDir = blueprintsDir
compileJava.options.annotationProcessorGeneratedSourcesDirectory = file("${projectDir}/${blueprintsDir}")

The code snippet creates a new generated directory (whose name is stored in the blueprintsDir variable) under the project root, and then designates it as the destination for blueprints.

To ensure that the generated files are removed each time the clean task is run from Gradle, add the following line:

clean.doLast { project.delete(blueprintsDir) }

The removal will be launched as the last operation of the task.

Using the Annotation Processor #

To use the Annotation Processor, decorate the handler implementations defined for the Cloudlet with one of the available annotations:

  • @CloudletEventHandler : for classes that implement the SPI interfaces referring to the handlers defined in the model (e.g. FormActionHandler, DBInsertHandler, etc.);
  • @CloudletScheduledTask : for classes that implement the SPICloudletScheduledTask interface; the annotation also has the String attribute defaultCronExpression, to be defined with the expression CRON that sets the task execution frequency.

You can also define custom REST or GraphQL resources, and decorate them with the @CloudletRestletServerResource annotation to allow generation of their endpoints.

Let’s take an example: suppose you’ve defined a FormActionHandler named Handler1 in your Cloudlet model; the SPI will contain the related interface named SpiClass1FormActionHandlerHandler1.

After implementing the logic of this handler, add the @CloudletEventHandler annotation to the class:

import com.fhoster.livebase.CloudletEventHandler;
import com.fhoster.livebase.cloudlet.SpiClass1FormActionHandlerHandler1;

@CloudletEventHandler
public class MyFormImpl implements SpiClass1FormActionHandlerHandler1 {
  ...
}

For a Scheduled Task, you will need to add the @CloudletScheduledTask annotation by defining its defaultCronExpression attribute. The following code allows you to make a Scheduled Task that runs every minute:

import com.fhoster.livebase.CloudletScheduledTask;
import com.fhoster.livebase.cloudlet.SpiCloudletScheduledTask;

@CloudletScheduledTask(defaultCronExpression = "* * * * *")
public class MyScheduledTask implements SpiCloudletScheduledTask {
  ...
}

Running the Annotation Processor #

To start generating files, run the gradle build command from a shell after positioning at the root of your project: the Annotation Processor will generate an OSGi blueprint for each annotated class, whose name is in the format plugin.<ClassName>.blueprint.xml; for example, for a class named MyHandlerImpl, we’ll have a blueprint named plugin.MyHandlerImpl.blueprint.xml.

If you followed the initial setup steps as detailed in the Gradle Project Setup section, all generated blueprints will be placed in the /<ProjectRoot>/generated/OSGI-INF/blueprint directory. If you want to change the destination directory, you can change the value of the blueprintsDir variable.

The generated files will also be included in the jar resulting from the build, in the /OSGI-INF/blueprint directory.

Using the Annotation Processor with Eclipse #

If you use Eclipse, it is possible to activate real-time blueprint generation by adding the following code snippet:

plugins {
  id 'eclipse'
  id  'net.ltgt.apt-eclipse' version '0.21'
}

project.eclipse.jdt.apt.aptEnabled = true
project.eclipse.jdt.apt.reconcileEnabled = true
project.eclipse.jdt.apt.genSrcDir = blueprintsDir
}

Run the gradle eclipse command from an open shell on the project root, then import the project into Eclipse. Finally, right-click on the imported project and select Gradle > Refresh Gradle Project to include the blueprintsDir directory in the project classpath…

Using annotations with injectable services #

When writing code for a Handler, it is possible to refer to some services offered by the Cloudlet Framework by including them in its constructor. The Annotation Processor takes care of their dependency injection during blueprint generation.

The available services are:

  • CloudletIdGenerator**: allows you to generate unique IDs for a table record; essential in all scenarios in which we want to add new objects on the database ensuring a unique ID for all of them;
  • CloudletDataSource**: allows access to the Cloudlet datasource, i.e. the interface to its database, to run a query directly;
  • CloudletMailSystem**: allows to send emails;
  • InventitySessionFactory**/**CloudletEntitySessionFactory**: allows you to open work sessions on Cloudlet objects;
  • EntityLockManager**: allows to manage locks on entities representing Cloudlet objects;
  • SPICloudletSMSSender**: allows the Cloudlet to send SMS notifications. Unlike the other services, this one does not have its own implementation and it will be up to the developer to make one;
  • CloudletPluginActivator**: allows you to intercept the activation event of a plugin and execute a custom callback.

It is also possible to access the BundleContext, i.e. the OSGi context of the Cloudlet, to manipulate the bundles it contains.

For example, the following class…

import com.fhoster.livebase.CloudletEventHandler;
import com.fhoster.livebase.cloudlet.SpiClass1FormActionHandlerHandler1;
import com.fhoster.livebase.cloudlet.CloudletIdGenerator;

@CloudletEventHandler
public class MyFormImpl implements SpiClass1FormActionHandlerHandler1 {
  private CloudletIdGenerator idgen;

  public MyFormImpl(CloudletIdGenerator idgen) {
    this.idgen = idgen
  }
  ...
}

… corresponds to the following blueprint:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
  <reference id="idGenerator1"
             interface="com.fhoster.livebase.cloudlet.CloudletIdGenerator"
             ext:proxy-method="classes" />
  <bean id="bid.plugin.myformimpl" class="plugin.MyFormImpl">
    <argument ref="idGenerator1"/>
  </bean>

  <service ref="bid.plugin.myformimpl" id="sid.plugin.myformimpl"
           interface="com.fhoster.livebase.cloudlet.SpiClass1FormActionHandlerHandler1">
  </service>
</blueprint>

References #