vai al contenuto principale

Annotation Processor

L'Annotation Processor è uno strumento pensato per facilitare lo sviluppo dei plugin Livebase che implementano servizi OSGi

L’Annotation Processor è stato reso disponibile per facilitare la creazione dei file necessari a configurare i servizi OSGi esposti dai plugin livebase; il suo compito in particolare è quello di generare automaticamente i file blueprint per questi servizi, interpretando le annotazioni presenti nel sorgente.

Setup del progetto Gradle #

Tutti i progetti base sono già configurati per eseguire l’Annotation Processor durante lo sviluppo in Eclipse e in concomitanza con il build Gradle. Se non utilizzi i progetti base, dovrai seguire la procedura dettagliata qui di seguito.

Innanzitutto apri il build.gradle del tuo progetto e, se non è presente nella sezione repositories, aggiungi jcenter() per collegare Gradle al repository JCenter, che attualmente ospita la libreria dell’Annotation Processor:

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

Adesso, includi la relativa libreria con il seguente codice:

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

Dovrai aggiungerne l’identificatore (qui memorizzato per comodità nella variabile processorLib) alla sezione dependencies, utilizzando entrambe le configurazioni compileOnly e annotationProcessor per effettuare l’inclusione a tempo di compilazione e collegare il processamento delle annotazioni all’operazione di build. Nota l’uso del + nel segmento relativo alla versione della libreria: questo ordinerà a Gradle di prendere sempre l’ultima versione disponibile.

Il passo successivo è configurare l’Annotation Processor specificando la directory di output dei blueprint OSGi che verranno generati:

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

Il frammento di codice crea una nuova cartella generated (il cui nome è memorizzato nella variabile blueprintsDir) sotto la root del progetto, per poi designarla come destinazione dei blueprint.

Per far sì che i file generati vengano rimossi ogni volta che viene lanciato il task clean da Gradle, aggiungi la seguente riga:

clean.doLast { project.delete(blueprintsDir) }

La rimozione sarà lanciata come ultima operazione del task.

Uso dell’Annotation Processor #

Per servirti dell’Annotation Processor, decora le implementazioni degli Handler definiti per la Cloudlet con una delle annotazioni disponibili:

  • @CloudletEventHandler : per le classi che implementano le interfacce dello SPI riferite agli handler definiti nel modello (es. FormActionHandler, DBInsertHandler, ecc.);
  • @CloudletScheduledTask : per le classi che implementano l’interfaccia SPICloudletScheduledTask; l’annotazione è anche dotata dell’attributo di tipo String defaultCronExpression, da definire con l’espressione CRON che stabilirà la frequenza di esecuzione del task.

Puoi anche definire delle risorse REST o GraphQL personalizzate, e decorarle con l’annotazione @CloudletRestletServerResource per consentire la generazione dei relativi endpoint.

Facciamo un esempio: supponi di aver definito un FormActionHandler denominato Handler1 nel modello della tua Cloudlet; lo SPI conterrà la relativa interfaccia denominata SpiClass1FormActionHandlerHandler1.

Dopo aver implementato la logica di questo handler, aggiungi l’annotazione @CloudletEventHandler alla classe:

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

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

Per uno Scheduled Task, dovrai aggiungere l’annotazione @CloudletScheduledTask definendone l’attributo defaultCronExpression. Il seguente codice permette di realizzare uno Scheduled Task che si esegue ogni minuto:

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

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

Esecuzione dell’Annotation Processor #

Per lanciare la generazione dei file, esegui il comando gradle build da una shell dopo esserti posizionato sulla root del tuo progetto: l’Annotation Processor genererà un blueprint OSGi per ogni classe annotata, il cui nome è nel formato plugin.<ClassName>.blueprint.xml; ad esempio, per una classe di nome MyHandlerImpl, avremo un blueprint di nome plugin.MyHandlerImpl.blueprint.xml.

Se hai seguito i passaggi iniziali di configurazione come dettagliato nella sezione Setup del progetto Gradle, tutti i blueprint generati saranno inseriti nella directory /<ProjectRoot>/generated/OSGI-INF/blueprint. Se desideri modificare la directory di destinazione, puoi agire sul valore della variabile blueprintsDir.

I file generati saranno inclusi anche nel jar risultante dalla build, nella directory /OSGI-INF/blueprint.

Uso dell’Annotation Processor con Eclipse #

Se utilizzi Eclipse, è possibile attivare la generazione in tempo reale dei blueprint aggiungendo il seguente frammento di codice:

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
}

Esegui il comando gradle eclipse da una shell aperta sulla root del progetto, quindi importa il progetto in Eclipse. Infine, fai clic destro sul progetto importato e seleziona Gradle > Refresh Gradle Project per includere la directory blueprintsDir nel classpath del progetto.

Uso delle annotazioni con servizi iniettabili #

Durante la scrittura del codice per un Handler, è possibile fare riferimento ad alcuni servizi offerti del Cloudlet Framework includendoli nel suo costruttore. L’Annotation Processor si occuperà della loro dependency injection durante la generazione dei blueprint.

I servizi disponibili sono:

  • CloudletIdGenerator: permette di generare ID univoci per un record di tabella; essenziale in tutti gli scenari in cui vogliamo aggiungere nuovi oggetti sul database garantendo per tutti un ID univoco;
  • CloudletDataSource: permette di accedere al datasource della Cloudlet, ovvero all’interfaccia verso il suo database, per effettuarvi query in modo diretto;
  • CloudletMailSystem: permette di inviare email;
  • EntitySessionFactory/CloudletEntitySessionFactory: permettono di aprire sessioni di lavoro sugli oggetti della Cloudlet;
  • EntityLockManager: permette di gestire i lock sulle entità che rappresentano oggetti della Cloudlet;
  • SPICloudletSMSSender: permette alla Cloudlet di inviare notifiche tramite SMS. A differenza degli altri servizi, questo non ha un’implementazione propria e sarà compito dello sviluppatore realizzarne una;
  • CloudletPluginActivator: permette di intercettare l’evento di attivazione di un plugin ed eseguire una callback personalizzata.

È inoltre possibile accedere al BundleContext, ovvero al context OSGi della Cloudlet, per manipolare i bundle in esso presenti.

Per esempio, la seguente classe…

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
  }
  ...
}

…corrisponde al seguente 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>

Riferimenti #